The thread that gets the DLL_PROCESS_DETACH notification is not necessarily the one that got the DLL_PROCESS_ATTACH notification


The thread that gets the DLL_PROCESS_DETACH notification is not necessarily the one that got the DLL_PROCESS_ATTACH notification. This is obvious if you think about it, because the thread that got the DLL_PROCESS_ATTACH notification might not even exist any longer when the DLL is unloaded. How can something that doesn't exist send a notification?

Even so, many people fail to realize this. You can't do anything with thread affinity in your DLL_PROCESS_ATTACH or DLL_PROCESS_DETACH handler since you have no guarantee about which thread will be called upon to handle these process notifications. Of course, you're not supposed to be doing anything particularly interesting in your DLL_PROCESS_ATTACH handler anyway, but things with thread affinity are doubly bad.

The classic example of this, which I'm told the Developer Support team run into with alarming frequency, is a DLL that creates a window in its DLL_PROCESS_ATTACH handler and destroys it in its DLL_PROCESS_DETACH handler. Now, creating a window in DllMain is already a horrifically bad idea since arbitrary code can run during the creation of a window (for example, there may be a global hook), but the lack of a thread guarantee makes it downright insane. The DLL calls DestroyWindow in its DLL_PROCESS_DETACH handler, but since that notification comes in on a thread different from the one that received the DLL_PROCESS_ATTACH notification, the attempt to destroy the window fails since you must call DestroyWindow from the same thread that created it.

Result: The DLL's attempt to destroy its window fails, a message comes in, and the process crashes since the window procedure no longer exists.

Comments (11)
  1. John says:

    This is why the only thing I ever do in DllMain is InitializeCriticalSection and DeleteCriticalSection; pretty much anything else can be lazy-initialized if needed.

  2. Tihiy says:

    >must call DestroyWindow from the same window

    you meant same thread?

    [Fixed, thanks. -Raymond]
  3. Ben Voigt [C++ MVP] says:

    Given the problem of global hooks running arbitrary code, is it possible to safely create a window from the DLL_THREAD_ATTACH notification (note THREAD not PROCESS)?

    [Was never possible to do safely. Global hooks are just one of many problems. -Raymond]
  4. Worf says:

    Which brings up the question – what is the purpose of DllMain, and what would someone normally use it for?

    If it’s not a replacement for Init()/Deinit() type functions, what should one use it for?

    (Especially since there’s a default stub if you don’t implement one…)

  5. Jeff says:

    @Worf

    DllMain can be used to initialize/release a few basic low-level primitives that are explicitly designed to be called during DllMain.  MSDN gives examples:

    "For example, DllMain can create synchronization objects such as critical sections and mutexes, and use TLS."

    However, if you don’t have any use for it, then by all means just pretend DllMain doesn’t exist.  (Do still call DisableThreadLibraryCalls though).

    Additionally, your programming language’s runtime will take care of bookkeeping in or around DllMain.  For example: the C runtime initializes global variables, and the C++ runtime calls the class constructors for global objects (a compelling reason to avoid having global C++ objects).

  6. Daev says:

    Also, if you have an "atexit function" in your DLL written in C, and that DLL is linked with the static version of the C run-time library, then the atexit function is automatically called from DllMain during DLL_PROCESS_DETACH.

    So don’t do anything complicated in an atexit function — in particular, don’t call functions which are in some other DLL, ’cause that other DLL might have already been unloaded.  (There’s no way to guarantee that your DLL gets detached before any DLLs it might be using.)

    This bit me, because I had an atexit function which closed any open sockets in case of process termination.  But sockets are in the WinSock DLL … which wasn’t there anymore.  Crash!

  7. W says:

    The contract of dllmain is one of the most problematic things in Win32. Especially since many programmers don’t even realize they are implementing dllmain since their language abstracts that away(static constructors/destructors in C++, initialization/finalization sections in Delphi).

    I’m sure I violated it quite often before learning about it. Probably I’m still violating it in my current programs as it is almost impossible to write code which does not call any external functions.

  8. Mike Dimmick says:

    It’s one of these things that started out with good intentions, of being a place where you would do initialisation and cleanup. However, the implementation imposed some restrictions, and over time it’s become clear that those restrictions stop you doing anything actually useful in it.

    There are other good intentions in Windows – the IsBadXxxPtr family spring to mind. Again, the consequences are more serious than were anticipated when they were designed.

  9. Anonymous says:

    Two other useful things to do in a DllMain:

    * Stash the first argument to DLL_PROCESS_ATTACH somewhere, it is useful.

    * Call DisableThreadLibraryCalls() so you will not be bothered again until the DLL_PROCESS_DETACH.

  10. 640k says:

    Unmanaged code is so last millennium.

  11. you must call DestroyWindow from the same thread that created it

    I’ll bite.  Why?

Comments are closed.

Skip to main content