Best Practices for DllMain


DllMain keeps on popping up as a pain point for developers.  Heck, just yesterday we found an old bug where a component was calling CoFreeUnusedLibraries in its DllMain function.

 

So it was really cool that this morning I came in and discovered an email (from the PM who owns the DLL loader) about a new article that was posted with best practices for authoring DLLs (in particular DllMain).

 

You can find the article here.

Comments (19)

  1. Peter Ritchie says:

    Great reference.  It’s too bad it’s hidden away deep within device driver development resources.

  2. Gianluca Varenni says:

    Very interesting document. But I think it’s not clear enough regards the use of CRT functions:

    – it says you *cannot* use the CRT functions dealing with memory (so I suppose malloc, free, but also strdup, which is in any case DEPRECATED).

    – it says that you can "open, read from, write to file". Can I use the CRT file functions like fopen/fclose? Or just the CreateFile/ReadFile/WriteFile ones? My suspect is that you cannot use fopen/… functions are they are NOT inside kernel32.dll.

    My rule of thumb is usually use *only* the functions exported by Kernel32.dll (and then follow the other restrictions).

  3. Anonymous says:

    I agree with Peter Ritchie – this should be in MSDN – possibly added to the DllMain document itself.

    As Gianluca Varenni implies, the item about not using CRT memory alloc functions should be expanded to simply say that CRT routines should be avoided – calling a CRT function before the CRT DLL has been initialized can cause the deadlock.

    I like the list of what you *can* do – but I think it basically boils down to don’t call other DLLs except kernel32.

    Also, the document says that the "loader lock must be at the bottom" of the lock acquisition hierarchy.  Unless I’m confused on the terminology, it seems that the loader lock must be at the top of the heirarchy, since it is by definition aqcuired by the DllMain thread before that thread has an opportunity to acquire any other locks.

  4. Anonymous says:

    > DllMain keeps on popping up as a pain point for developers.

    Not for some.  Consider Visual Studio 2005.  If you use the wizard to generate a skeleton project, it generates code with the first parameter having type HANDLE.  If you change the first parameter to have type HINSTANCE, Visual Studio gives compile-time errors.  So is this painful for Visual Studio developers?  Not at all.  Not only is it a "won’t fix" for the present version of Visual Studio, it’s already a "won’t fix" for the next version.  No one’s going to do any painful exercise fixing this.

    Of course it really is less painful than some other "won’t fixes", but I don’t know of others involving DllMain at the moment.

  5. PetriW says:

    Something I’ve always wondered, why are there no functions to load a dll from memory rather than from disc?

    Sure, you can set it up manually but well… that’s error prone. 😉

  6. What does it mean to load a DLL from memory?

    A DLL is a file on the disk that contains code, just like an EXE.  Executable code is loaded from some storage medium.  

    If it was in memory, you could just execute it.

  7. PetriW says:

    Say you do not wish to distribute a dll as a separate file, but you can’t compile it into the executable as it’s written in a different programming language.

    The only way to load the DLL via api functions is to store the DLL as a resource, then when your application runs save it to disc and load it from there. You can’t directly load it from memory.

    There are ways to manually load and initialize a dll located in memory, but none are provided by the API as far as I know.

    Considering how often I’ve seen different solutions to being able to include a DLL inside the executable I think more people than I wonder why you can only load DLLs from a storage medium. (a simple search gives quite a long list of results)

  8. Why NOT distribute it as a separate file?  MSI is a stable reliable technology, free for redistribution (as far as I know), and adds you to ARP for easy discovery.

    Zip also works pretty well.

    The scenario you’re describing is pretty fringe, IMHO.

  9. Peter Ritchie says:

    PetrieW, I think what Larry is saying, is; if you have code you want to run but don’t want it in a physical DLL on disk then just run it as part of your application.  I.e. don’t put it in a separate DLL.

    If you don’t have the source for the DLL but still want to use it, then that’s a different story–and I would echo Larry, that’s pretty fringe.

  10. PetriW says:

    Well, I started looking into it after people replaced customized DLLs with other DLLs (opensource project stuff and they know what I use) and caused data corruption. Some people seem willing to go to great lengths to do stuff the way they want to.

    (In this case they replaced the DLL to be able to do inserts in a encrypted flatfile database.)

    While loading from memory would in this case not be 100% remedy to that, I’ve found that encrypting the dll and simply loading it manually into memory proved to be too big a hurdle to make it worthwhile to even try. (Probably helps a bit that it doesn’t actually call winapi to find dll functions.)

    I know anything placed on someone elses pc is essentially unprotectable but that doesn’t mean I feel like making it dead easy.

    And yes, this is very fringe and probably there’s some much better way to do it, I just happened to find the code for loading it from memory and stuck with that. 😉

  11. Anonymous says:

    There’s a slightly less obscure reason for having memory-based DLLs: global hooks. Often, the type of program that uses global hooks is a relatively small utility-type program: e.g. global hotkey utilities, mouse/keyboard recording utilities, window management changes ("true X-mouse", theme programs) etc. The hook DLL isn’t needed outside the lifetime of the process that installed the hook. Many of these programs are small enough that they could be distributed as one file, and would benefit from a memory-based DLL. The image of the DLL could be backed by the swap file like an anonymous memory-mapped file.

  12. JeffCurless says:

    I found that paper pretty much useless.  It contains a very poor overview, in my estimation.  A much better resource is this blog:  http://blogs.msdn.com/mgrier/default.aspx

    He goes much further in depth than the paper.

  13. Anonymous says:

    I do not think that configuration file loading and varification should be recommended, as people may use complex configuration files including having XML files and if the XML parser loads any DLL, it may creat problem.

  14. Anonymous says:

    Being able to call a sort of "LoadLibraryInMemory()" would actually be quite useful for various things: UPX-like compression (currently, everything has to be decompressed into a temporary file instead), packaging Java applications with their own JVM (you can’t go hacking the JVM to embed more neatly – but linking against an unmodified DLL in memory should be OK)…

    ISTR someone somewhere had a (proprietary) tool which claimed to do this; I could see some promising directions in the native API, but not enough documentation to make it an easy route.

  15. Anonymous says:

    One of the things one cannot do in DllMain or in static objects (on Windows) is to perform network operations (with other threads or processes). This is allowed on other platforms. Can you shed some light on which we have this restriction on DllMain?

    ~ash

  16. Ash, the simple answer is "the loader lock".  This is the root of the problem with DllMain – the loader lock is held across the DllMain calls for all Dlls.  Essentially this puts the loader lock at the top of the lock hierarchy, which means that you can’t take any other locks while inside it.

    Mike Grier wrote an insanely detailed series on the loader lock a while ago, it’s referenced in Jeff’s comment above.

  17. ndiamond says:

    [1]  I wrote:

    > Consider Visual Studio 2005.  If you use the wizard to

    > generate a skeleton project, it generates code with the

    > first parameter having type HANDLE.  If you change the

    > first parameter to have type HINSTANCE, Visual Studio

    > gives compile-time errors.

    Today I had occasion to discover that Visual Studio 2005 doesn’t have that bug with plain old Win32 DLLs.  So two of my DLLs now have code like this:

    BOOL APIENTRY DllMain(

    #ifdef UNDER_CE

       HANDLE hModule,

    #else  // WIN32

       HMODULE hModule,

    #endif // CE vs. WIN32

       DWORD  ul_reason_for_call,

       LPVOID lpReserved)

    {

       [….]

    It’s still been resolved as a "Won’t fix" for the next release, so it’s not painful for Microsoft’s Visual Studio development team.

    [2]  Earlier today I happened to notice one of Mr. Osterman’s blog entries from year 2004, in which a comment by Mr. Grier mentioned that memory allocation calls from DllMain can cause deadlock.  This made me wonder yet again how to allocate memory for Thread Local Storage.  Is GlobalAlloc really safer than malloc?

  18. Anonymous says:

    Great, now my question is why OLE32.DLL itself is guilty of this?

    I don’t have the exact call stack in from of me, but if a thread terminates with unbalanced CoInitialize calls, OLE32 will call CoUninitialize from its DllMain(DLL_THREAD_DETACH), which, if you’ve got marshalled COM objects, will end up RPC’ing while holding the loader lock.  If the thread it’s trying to talk to is doing anything which needs the loader, welcome to deadlock city.  Now, say your app is shutting down, about the same time the above thread is terminating (not uncommon).  Well, some genius added a GetModuleHandle("mscoree.dll") to __crtExitProcess, so what would have been automatically cleaned up at process termination is now a deadlock and the process gets orphaned…

    For the record, the unbalanced CoInit was in a component not owned by us.