If only DLLs can get DllMain notifications, how can an EXE receive a notification when a thread is created (for example)?


When a DLL is loaded, it receives a DLL_PROCESS_ATTACH notification, and when it is unloaded (or when the process terminates), it gets a DLL_PROCESS_DETACH notification. DLLs also receive DLL_THREAD_ATTACH notifications when a thread is created and DLL_THREAD_DETACH notifications when a thread exits. But what if you are an EXE? EXEs don't have a Dll­Main, so there is no way to receive these notifications.

The trick here is to hire a lackey.

Create a helper DLL, called, say, LACKEY.DLL. Your EXE links to the lackey, and the lackey's job is to forward all Dll­Main notifications back to your EXE. The DLL would naturally have to have a way for your EXE to provide the callback address, so you might have a function Register­Lackey­Callback.

typedef BOOL (CALLBACK *LACKEYNOTIFICATION)(DWORD dwReason);

LACKEYNOTIFICATION g_lackeyNotification;

void RegisterLackeyCallback(LACKEYNOTIFICATION lackeyNotification)
{
 g_lackeyNotification = lackeyNotification;
}

BOOL WINAPI DllMain(
    HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpReserved)
{
 if (g_lackeyNotification) g_lackeyNotification(dwReason);
 return TRUE;
}

Of course, it is rather extravagant to hire a lackey just for this one task, so you will probably just add lackey responsibilities to some other DLL you've written.

I don't know if there's a name for this design pattern, so I'm just going to call it the hired lackey pattern.

Comments (18)
  1. Joshua says:

    I call this boilerplate adapter.

  2. Henke37 says:

    Then using CreateRemoteThread must be the injected lackey pattern.

  3. Alex Cohn says:

    I understand this only makes sense for DLL_THREAD_* notifications, and it may be very useful if your EXE allows plugins that may create and terminate threads. You can add a simple global object with constructor and destructor, to have an equivalent of PROCESS_ATTACH and PROCESS_DETACH.

  4. KJK::Hyperion says:

    Or use a TLS routine. It has almost the same signature as DllMain, except it returns void (it can't veto operations), it's called inside the loader lock like a DllMain, and the Microsoft linker and C runtime already come with built-in (if undocumented) support to add your own TLS routines

  5. Alex Cohn says:

    @KJK::Hyperion : thanks for the hint, I found this description of TLS callbacks (http://www.nynaeve.net/?p=183) fascinating.

    But void return should not be a problem, because "When the system calls the DllMain function with any value other than DLL_PROCESS_ATTACH, the return value is ignored"(msdn.microsoft.com/…/ms682583(v=vs.85).aspx).

  6. igorsk says:

    Yes, I'm curious why Raymond did not mention TLS callbacks. I can't quite imagine him not knowing about them.

  7. Rick C says:

    Raymond frequently builds on a topic over several columns.  A quick search for "thread local storage old new thing" shows he's mentioned TLS before.  This post may well simply be the entre.

  8. Adam Rosenfield says:

    I suspect that Raymond hasn't mentioned TLS callbacks because they're undocumented implementation details that well-behaved programs shouldn't rely on.

  9. KC says:

    Not to nitpick too much, but shouldn't Lakey return the value from calling g_lackeyNotification?  The sample always returns TRUE

  10. foo says:

    If the lackey feels unappreciated and wants to take some time off it can insert a sneaky call to DisableThreadLibraryCalls() at the start of its DllMain.

  11. kme says:

    What's the difference between a lackey, a flunky and a stooge?

  12. Myria says:

    @Adam Rosenfield: TLS callbacks are documented in Microsoft's published PE file format specification.  Also, PIMAGE_TLS_CALLBACK is an exposed typedef in WinNT.h.

  13. Hildar says:

    TLS callbacks are also red flags to malware scanners, since malware has been known to sit in them so it can run before a breakpoint on main().

    No one legitimately uses feature X? Better stop people ever using feature X!

  14. Steve Macpherson says:

    …and you can always LoadLibrary() on the EXE itself if you especially want to – which would avoid having to distribute a DLL with your EXE.

  15. Joshua says:

    @SteveMacphearson: With the consequence of can't link against libc (and I'm sure there's more).

  16. Hildar says:

    According to the documentation, no equivalent of DllMain is run if you LoadLibrary a .exe. So it wouldn't even work.

  17. Joshua says:

    @Hildar: A better read of the documentation is required. It will call the entry point of the .EXE expecting to find _DllMainCRTStartup there.

  18. alegr1 says:

    >It will call the entry point of the .EXE expecting to find _DllMainCRTStartup there.

    Only if IMAGE_FILE_DLL flag is present. Which makes it not an EXE.

Comments are closed.

Skip to main content