3

MSDN says:

It must not call the LoadLibrary or LoadLibraryEx function (or a function that calls these functions), because this may create dependency loops in the DLL load order. This can result in a DLL being used before the system has executed its initialization code.

I tried to call LoadLibrary from DllMain and nothing happened.

The only issue that I see is that loaded DLL will use functions in my DLL before rest of my DllMain executes.

Why I must not call LoadLibrary in DllMain?

EDIT:

OK, I realized that I must not call LoadLibrary in DllMain just because I must believe MSDN as other believers do (I saw some wrong things there, but I should forget them too).
And because something may happen in newer versions of Windows (although there nothing was changed for last ten years).

But can anyone show a code which will reproduce something bad what happens when LoadLibrary is called in DllMain? In any existing Windows OS?
Not just a call of one singleton initialization function inside another, but LoadLibrary in DllMain?

manuell
  • 7,528
  • 5
  • 31
  • 58
Abyx
  • 12,345
  • 5
  • 44
  • 76
  • 6
    A good explanation here: http://blogs.msdn.com/b/oldnewthing/archive/2004/01/27/63401.aspx – Simon Mourier Dec 06 '10 at 20:56
  • 1
    @Simon Mourier : there is nothing related to this question. – Abyx Dec 06 '10 at 21:07
  • 6
    Is the subtext to this question, "I plan to call LoadLibrary from DllMain in my code and don't want to take the advice not to do so"? – David Heffernan Dec 06 '10 at 23:04
  • This has nothing to do with whether anybody "believes" MSDN or not; the API specification in MSDN is the interface that Microsoft guarantees will always work. You must consider any other behaviors to be subject to change. Sure nothing has changed in ten years, but that doesn't prevent Microsoft from changing things in the future. Don't forget that Windows 9x had a completely different loader than Windows NT, even though they both supported Win32. Do you REALLY want your code to be the stuff that doesn't work on a different implementation? – Aaron Klotz Dec 20 '10 at 07:07

6 Answers6

16

There are simple, and even not so simple, circumstances in which calling LoadLibrary from DllMain is perfectly safe. But the design is that DllMain is trusted not to change the list of loaded modules.

Though possession of the loader lock does indeed constrain what can be done in DllMain, it is only indirectly relevant to the LoadLibrary rule. The relevant purpose of the loader lock is serialise access to the list of loaded modules. While NTDLL works on this list in one thread, possession of the loader lock ensures that the list won't be changed by NTDLL code that's executing in another thread. However, the loader lock is a critical section. It does nothing to stop the same thread from re-acquiring the loader lock and changing the list.

This would not matter if NTDLL kept entirely to itself while working on the list. However, NTDLL provides for involving other code in this work, as when initialising a newly loaded DLL. Each time NTDLL calls outside itself while working on the list, there is a choice to make for the design. Broadly, there are two options. One is to stabilise the list and release the loader lock, call outside, then acquire the loader lock and resume work on the list as if from scratch because the outside call may have changed it. The other is to keep the loader lock and trust the called code not to do anything that changes the list. And thus does LoadLibrary become off-limits in DllMain.

It's not that the loader lock does anything to stop DllMain from calling LoadLibrary or even that the loader lock itself makes such a call unsafe. It is instead that by retaining the loader lock, NTDLL trusts DllMain not to call LoadLibrary.

For contrast, consider the DllMain rule about not waiting on synchronisation objects. Here, the loader lock has a direct role in making this unsafe. Waiting on a synchronisation object in DllMain sets up the possibility of deadlock. All that's needed is that another thread already holds the object you're waiting on, and then this other thread calls any function that would wait on the loader lock (e.g., LoadLibrary but also such functions as the seemingly inocuous GetModuleHandle).

Wanting to stretch or break the DllMain rules may be mischievous or even outright stupid. However, I must point out that Microsoft is at least partly to blame for people asking how strong or meaningful are these rules. After all, some have not always been documented clearly and forcefully, and when last I looked they were still not documented in all the situations where they're surely needed. (The exception I have in mind is that at least until Visual Studio 2005, MFC programmers writing DLLs were told to put their initialisation code in CWinApp::InitInstance but were not told that this code is subject to the DllMain rules.)

Moreover, it would be a bit rich for anyone from Microsoft to speak as if the DllMain rules ought be followed without question. Examples exist where Microsoft's own programmers break the rules, and continue to even after breaking the rules is seen to have caused serious real-world trouble.

Geoff Chappell
  • 161
  • 1
  • 2
15

Your argument in favor of going ahead with this seems to be, to paraphrase:

Microsoft says don't do this, but my single test case seems to work, therefore I fail to see why nobody should be doing this.

You're operating under a big assumption: you're assuming that the underlying implementation of the Windows loader will never change. What if the loader is changed in "Windows 8" in a way such that your code no longer works properly? Now Microsoft gets blamed for it and they have to include yet another compatibility hack to work around code that they told you not to write in the first place.

Follow the guidelines. They're not there just to make your life more difficult, they're there to guarantee that your code will work just as well on the Windows of the future as it does now.

Aaron Klotz
  • 11,287
  • 1
  • 28
  • 22
9

As stated in http://msdn.microsoft.com/en-us/library/ms682583%28VS.85%29.aspx:

Threads in DllMain hold the loader lock so no additional DLLs can be dynamically loaded or initialized.

Cheers

Marcus Borkenhagen
  • 6,536
  • 1
  • 30
  • 33
  • 4
    @Abyx - what would be the point? The same page specifically forbids calling `LoadLibrary(Ex)` from `DllMain`. it should be clear that if you do this, you are on your own. – Steve Townsend Dec 06 '10 at 21:07
  • 1
    @Fritschy: That doesn't necessarily mean it is problem. If you call LoadLibrary within DllMain it is being called from the same thread that the doc says currrently holds the loader lock, so there shouldn't be a deadlock. I'm not advocating using LoadLibrary though, the doc saying you shouldn't do it is reason enough not to do it. – Praetorian Dec 06 '10 at 21:30
  • 7
    @Praetorian: It won't deadlock. It might just crash. And the crash might happen much later on. If you call LoadLibrary from a DllMain, there are no guarantees that DllInitialization will happen in the right order. You might get lucky and it will work. But you might fail miserably. You have no way of knowing what will happen. – Larry Osterman Dec 06 '10 at 21:52
  • 1
    "Did you test that?", er what?!! – David Heffernan Dec 06 '10 at 23:07
  • @Praetorian: It is a problem if the intention was to LoadLibrary in DllMain. Other problems may arise too: http://www.mail-archive.com/osg-users@lists.openscenegraph.org/msg30386.html – Marcus Borkenhagen Dec 06 '10 at 23:30
  • 1
    @Abyx: I did not test it, but have some experience with bugs related to it. I am however inclined to do as the docs tell me to ;) – Marcus Borkenhagen Dec 06 '10 at 23:32
3

I was working on a case that could require using LoadLibrary in DllMain, so while investigating found this discussion. An update on this from my todays experience

Reading this one can get really scary http://blogs.msdn.com/b/oleglv/archive/2003/10/28/56142.aspx . Not only various locks matter, but also the order in which the libs was passed to the linker. The case is say one bi

Now, I've tried this with vc9 under win7. Yes, so is it. Depending on the order of how the libs are passed to the linker, using LoadLibrary works or not. However, the same with vc11 under win8 works properly disregarding the link order. Application Verifier doesn't blame about that.

I'm not calling to use it this way right now and everywhere :) But just FYI, if it's the same with win10 and further - this might have more usefulness. Anyway seems that the loader mechanism under win8 undergone some noticeable changes.

Thanks.

Anatol Belski
  • 660
  • 6
  • 8
0

It is extremely late but still,

If on thread 1 (T1) you DllMain loads other libraries, those other lib's DllMain will be called; which in itself is okay but say their DLLMain creates a thread (T2) and waits on an event for T2 to finish.

Now if T2 loads a library in its processing, loader will not be able to acquire the lock as T1 has already acquired it. As T2 is hung on LoaderLock it will never signal the event T1 is waiting on.

Which will result in a deadlock.

There could be more such scenario, I guess the broad reasoning here is that we can not be sure of what code will run in other libraries, so it is a good idea (turned best practice), to not do it.

0

Here is how to reproduce a loader lock hang in Windows 8 / Server 2012 and later. Note this code is not directly calling load library but uses Windows APIs that trigger Load Library calls.

Create a Visual Studio C++ DLL project and use this code in DLL main:

#define WIN32_LEAN_AND_MEAN

#include "framework.h"

#include <windows.h>
#include <winsock2.h>
#include <iphlpapi.h>
#include <ws2tcpip.h>
#include <stdio.h>
#pragma comment(lib, "IPHLPAPI.lib")

#define MALLOC(x) HeapAlloc(GetProcessHeap(), 0, (x))
#define FREE(x) HeapFree(GetProcessHeap(), 0, (x))
// Need to link with Ws2_32.lib
#pragma comment(lib, "ws2_32.lib")

BOOL APIENTRY DllMain(HMODULE hModule,
    DWORD  ul_reason_for_call,
    LPVOID lpReserved
)
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        WORD wVersionRequested;
        WSADATA wsaData;
        int err;

        /* Use the MAKEWORD(lowbyte, highbyte) macro declared in Windef.h */
        wVersionRequested = MAKEWORD(2, 2);

        err = WSAStartup(wVersionRequested, &wsaData);
        if (err != 0) {
            printf("WSAStartup failed with error: %d\n", err);
            return 1;
        }
    
        if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
            printf("Could not find a usable version of Winsock.dll\n");
            WSACleanup();
            return 1;
        }
        else
            printf("The Winsock 2.2 dll was found okay\n");
    
        
        FIXED_INFO* pFixedInfo;
        ULONG ulOutBufLen;
        DWORD dwRetVal;
        IP_ADDR_STRING* pIPAddr;

        pFixedInfo = (FIXED_INFO*)MALLOC(sizeof(FIXED_INFO));
        if (pFixedInfo == NULL) {
            printf("Error allocating memory needed to call GetNetworkParams\n");
            return 1;
        }
        ulOutBufLen = sizeof(FIXED_INFO);

        // Make an initial call to GetAdaptersInfo to get
        // the necessary size into the ulOutBufLen variable
        if (GetNetworkParams(pFixedInfo, &ulOutBufLen) == ERROR_BUFFER_OVERFLOW) {
            FREE(pFixedInfo);
            pFixedInfo = (FIXED_INFO*)MALLOC(ulOutBufLen);
            if (pFixedInfo == NULL) {
                printf("Error allocating memory needed to call GetNetworkParams\n");
                return 1;
            }
        }

        if (dwRetVal = GetNetworkParams(pFixedInfo, &ulOutBufLen) == NO_ERROR) {

            printf("Host Name: %s\n", pFixedInfo->HostName);
            printf("Domain Name: %s\n", pFixedInfo->DomainName);

            printf("DNS Servers:\n");
            printf("\t%s\n", pFixedInfo->DnsServerList.IpAddress.String);

            pIPAddr = pFixedInfo->DnsServerList.Next;
            while (pIPAddr) {
                printf("\t%s\n", pIPAddr->IpAddress.String);
                pIPAddr = pIPAddr->Next;
            }

            printf("Node Type: ");
            switch (pFixedInfo->NodeType) {
            case BROADCAST_NODETYPE:
                printf("Broadcast node\n");
                break;
            case PEER_TO_PEER_NODETYPE:
                printf("Peer to Peer node\n");
                break;
            case MIXED_NODETYPE:
                printf("Mixed node\n");
                break;
            case HYBRID_NODETYPE:
                printf("Hybrid node\n");
                break;
            default:
                printf("Unknown node type %0lx\n", pFixedInfo->NodeType);
                break;
            }

            printf("DHCP scope name: %s\n", pFixedInfo->ScopeId);

            if (pFixedInfo->EnableRouting)
                printf("Routing: enabled\n");
            else
                printf("Routing: disabled\n");

            if (pFixedInfo->EnableProxy)
                printf("ARP proxy: enabled\n");
            else
                printf("ARP Proxy: disabled\n");

            if (pFixedInfo->EnableDns)
                printf("DNS: enabled\n");
            else
                printf("DNS: disabled\n");

        }
        else {
            printf("GetNetworkParams failed with error: %d\n", dwRetVal);
            return 1;
        }

        if (pFixedInfo)
            FREE(pFixedInfo);
        //WSACleanup();
        break;
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

From a 2nd application (That does not import any network APIs or called any network functions yet) create a console desktop C++ application containing the following code:

HMODULE hModule;
hModule = LoadLibrary(L"<specify DLL created in previous example>"); // application will hang here
Malcolm McCaffery
  • 2,468
  • 1
  • 22
  • 43