If you return FALSE from DLL_PROCESS_ATTACH, will you get a DLL_PROCESS_DETACH?


If you return FALSE from DLL_PROCESS_ATTACH, will you get a DLL_PROCESS_DETACH?

Yes.

No.

...

Yes.

All three answers are correct, for different formulations of the question.

From the kernel's point of view, the answer is a simple Yes. If a DLL's entry point returns FALSE to the DLL_PROCESS_ATTACH notification, it will receive a DLL_PROCESS_DETACH notification.

However, most C and C++ programs do not use the raw DLL entry point. Instead, they use the C runtime entry point, which will have a name something like DllMainCRTStartup. That entry point function does work to manage the C runtime library and calls your entry point (which you've probably called DllMain) to see what you think.

If you compiled your program prior to around 2002 and your DllMain function returns FALSE in response to the DLL_PROCESS_ATTACH notification, then the C runtime code says, "Oh, well, I guess I'm not running after all" and shuts itself down. When the kernel calls the C runtime entry point with the DLL_PROCESS_DETACH notification, the C runtime says, "Oh, I'm already shut down, thanks for asking" and returns immediately, which means that your entry point is not called with the DLL_PROCESS_DETACH notification. In other words, if you wrote your program prior to around 2002, the answer is No.

Sometime in 2002 or maybe 2003, the C runtime folks changed the behavior. If your DllMain function returns FALSE in response to the DLL_PROCESS_ATTACH notification, you will nevertheless get the DLL_PROCESS_DETACH notification. In other words, if you wrote your program after around 2002 or maybe 2003, then the answer is Yes. Why change? Maybe they wanted to match the kernel behavior more closely, maybe they considered their previous behavior a bug. You'll have to ask them.

What does this mean for you, the programmer? Some people may look at this and conclude, "Well, now that I know how each of the specific scenarios works, I can rely on knowing the behavior that results from the scenario I'm in. For example, since I'm using Visual Studio 2008, the answer is Yes." But I think that's the wrong conclusion, because you usually do not have total control over how your program is compiled and linked. You may share your code with another project, and that other project may not know that you are relying on the behavior of a specific version of Visual Studio 2008; they will compile your program with Borland C++ version 5.5,¹ and now your program is subtly broken. My recommendation is to write your DllMain function so that it works correctly regardless of which scenario it ends up used in. (And since you shouldn't be doing much in your DllMain function anyway, this shouldn't be too much of a burden.)

Footnote

¹I do not know what the behavior of Borland C++ version 5.5 is with respect to returning FALSE from DllMain. I didn't feel like doing the research to find a compiler whose behavior is different from Visual Studio 2008, so I just picked one at random. I have a 50/50 chance of being right.

Comments (28)
  1. Dog says:

    >I have a 50/50 chance of being right.

    So you’re saying that exactly half of all Windows compilers have the same behaviour as VS 2008 then?

    [That was clearly a casual remark and should be interpreted as such. -Raymond]
  2. gedoe says:

    @dog You forgot to finish your name, it should be dog…. Your friendly troll….

  3. Rick C says:

    I’m assuming Raymond’s 50% comes from the "either a thing will happen, or it won’t, therefore all events have a 50% probability of happening" line of reasoning.

  4. Pierre B. says:

    Thanks Raymond for sharing this subtle and hard-to-find fact. Too bad too many people try to turn useful informative post into trolling fest.

  5. Maurits says:

    Rick C, I have a lottery ticket I’d like to sell you.  Since you will either win, or not, you would logically be willing to pay up to 49% of the jackpot for this particular ticket.  I’ll give you a whale of deal and let you have it for only 10% of the jackpot.

  6. don'tfeedtrolls says:

    Why must nitpickers ruin everything?  It’s a 50/50 chance because for any random compiler it either does or doesn’t send the notification.  The compilers are independent.

  7. Peter Ritchie says:

    Hmm, a failed attach means the dll is not unattached…

    The MSDN documentation seems to suggest the detach occurs (when attach returns FALSE) only if the attach was a result of a LoadLibrary.

  8. Leo Davidson says:

    Thanks for mentioning this!

    Moments before reading this I had added a DeleteCriticalSection into a DLL_PROCESS_DETACH block. I didn’t realise they had changed the rules. I have a lot of DLLs to update!

    I’ve got to wonder what was going through the mind of whoever made the change to the CRT. It crashes old code that was correct and new code cannot rely on the new behaviour (as you explained). That’s a lose-lose situation. All for the "win" of making old code that was incorrect code now sometimes correct. <slaps forehead>

  9. JamesNT says:

    Visual Studio is the only way to go.  Who cares about Borland?

    JamesNT

  10. Ulric says:

    why do people want to return false.

  11. Starfish says:

    Why? Maybe it’s rampant pessimism?

  12. Seen in a footnote to what is actually an interesting and useful piece of information concerning DLL_PROCESS_DETACH notifications, at least if you’re a Win32 programmer: Footnote ¹I do not know what the behavior of Borland C++ version 5.5 is with…

  13. Yuhong Bao says:

    "Visual Studio is the only way to go.  Who cares about Borland?"

    Well, Borland C++ Builder still exists today as CodeGear C++Builder, and Watcom is now open source as Open Watcom.

    http://www.codegear.com/products/cppbuilder

    http://www.openwatcom.org/index.php/Main_Page

    BTW, there are articles on the Open Watcom wiki relating to history. VGA, Windows 3.x, and A20 all have articles.

  14. Yuhong Bao says:

    BTW, the C runtime library dates in not only this article but also another article are kind of cryptic. Version numbers like 7.0 and 7.1 would be much better.

  15. Dean Harding says:

    "Version numbers like 7.0 and 7.1 would be much better."

    Why? The point of the blog was to advise against trying to rely on whether you’d get the DLL_PROCESS_DETACH or not, and giving specific version numbers would more easily allow people go against that advice.

  16. Marc says:

    Just moved from Borland to VS 2008… mmm shiny icons :)

  17. MadQ says:

    @Peter Ritchie: But the DLL was attached. If you return FALSE from DllMain, you’re just saying "never mind, please detach me now."

  18. Igor Levicki says:

    Hmm, it is funny how you are calling C runtime wrapper bugfix a behavior change. Must be microspeak kicking in.

    [The interpretation of the value returned from DllMain changed. And you say this isn’t a behavior change? -Raymond]
  19. Miral says:

    @Leo: As Raymond pointed out, the difference is only really a major problem if you have people compiling your DLL’s source code with different versions of the compiler (or different compilers altogether).  For any other cases it’s just a simple compiler migration issue to be aware of, but not an ongoing problem.

    Thus it’s only really a big deal for open source developers and large teams that share codebases (and individually compile common libraries) — and even then I would think that most teams mandate a particular compiler…?

    "[The interpretation of the value returned from DllMain changed. And you say this isn’t a behavior change? -Raymond]"

    The interpretation of the return value didn’t change — it still means "oops, leave me alone"; the change was outside of that, when the DLL entry-point is reentered for a different operation.  It’s still a behavioural change, though, there’s no question about that :)

  20. fedup says:

    Igor are you a PITA in real life, or only when you comment in blogs?

  21. BorlandUser says:

    C:temp>copy con test_dll.cpp

    #include <windows.h>

    BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)

    {

           DWORD w;

           HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);

           switch (fdwReason) {

           case DLL_PROCESS_ATTACH:

                   WriteFile(hOutput, "DLL_PROCESS_ATTACHrn", 20, &w, NULL);

                   break;

           case DLL_PROCESS_DETACH:

                   WriteFile(hOutput, "DLL_PROCESS_DETACHrn", 20, &w, NULL);

                   break;

           }

           return FALSE;

    }

    int main()

    {

           LoadLibrary("test_dll.dll");

    }

    ^Z

           1 Datei(en) kopiert.

    C:temp>bcc32 -w- -WD test_dll

    Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland

    test_dll.cpp:

    Turbo Incremental Link 5.00 Copyright (c) 1997, 2000 Borland

    C:temp>bcc32 -w- test_dll

    Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland

    test_dll.cpp:

    Turbo Incremental Link 5.00 Copyright (c) 1997, 2000 Borland

    C:temp>test_dll

    DLL_PROCESS_ATTACH

    DLL_PROCESS_DETACH

  22. acq naq says:

    Thanks BorlandUser. For reference, I get

    C:temp>test_dll.exe

    DLL_PROCESS_ATTACH

    C:temp>

    using VC6 which matches Raymond’s description of VC.

  23. Yuhong Bao says:

    "Why? The point of the blog was to advise against trying to rely on whether you’d get the DLL_PROCESS_DETACH or not, and giving specific version numbers would more easily allow people go against that advice."

    Except that the problem isn’t just in this blog article. I remember reading another blog article that has the same problem

  24. mikeb says:

    > C runtime library dates in not only this article but also another article are kind of cryptic. Version numbers like 7.0 and 7.1 would be much better. <<

    Since I’m not Raymond and don’t know Raymond, I can only guess (which I suppose I shouldn’t do, but here goes anyway):

    Raymond probably doesn’t know which versions exhibit which behavior and doesn’t care to know.  He just knows that it changed at some point and decided it was an interesting tidbit to blog about.

    If someone has a need to know further details,  presumably they have the compilers (otherwise, why would they need to know) and can do the checking just as easily as Raymond could, using exactly the same methods as Raymond would.

    Of course, there’s also still the reasoning that Dean Harding brought up.

  25. Alexander Grigoriev says:

    If people are returning FALSE from PROCESS_ATTACH, they deserve all the PITA. A point to remember: your DLL is NOT the most important in the world, to veto the whole process launch.

  26. Igor Levicki says:

    Raymond said: “The interpretation of the value returned from DllMain changed. And you say this isn’t a behavior change?”

    All I am saying is that you are using pretty name for a bugfix.

    fedup said: “Igor are you a PITA in real life, or only when you comment in blogs?”

    For you I am both — I hate stupid questions from anonymous cowards.

    Alexander Grigoriev said : “A point to remember: your DLL is NOT the most important in the world, to veto the whole process launch.”

    So if the DLL fails to allocate memory for say TLS it should return TRUE and initialize its variables with some random values and pointers?

    [A bugfix is still a behavior change. Some people might consider this a regression rather than a bugfix. -Raymond]
  27. I think this is a bit counter intuitive. How can you detach from something you weren’t attached to in the first place?

    I see it the same way I see C++ constructors. If you throw an exception in a constructor, your destructor will never run since the object was never created.

  28. Alexandre Grigoriev says:

    [Igor Levitsky]"So if the DLL fails to allocate memory for say TLS it should return TRUE and initialize its variables with some random values and pointers?"

    Unless you have to deal with legacy non-thread-aware-crap, like CRTL does, why would you need TLS? There are things you can do, but you should know better to avoid, such as using TLS. There are things you can get away most of the time, but they will come to bite you.

    For example, a certain DLL that interfaced to a image capture board. If no board was installed, the DLL would display a message box and return FALSE from DllMain. Now what if you still want to run your program with alternate capture device? Welcome to LoadLibrary. It’s worth to mention that their kernel mode driver was importing KeBugCheck. How do you like a IHV driver bringing down your system at will?

Comments are closed.