从DllMain调用LoadLibrary

时间:2010-12-06 20:50:27

标签: windows winapi language-agnostic dll loadlibrary

MSDN says:

  

它不能调用LoadLibrary或LoadLibraryEx函数   (或调用这些函数的函数),   因为这可能会在DLL加载顺序中创建依赖循环。   这可能导致在系统执行其初始化代码之前使用DLL。

我试图从DllMain打电话给LoadLibrary但没有任何反应。

我看到的唯一问题是加载的DLL将在我的DllMain执行之前使用我的DLL中的函数。

为什么我不能在DllMain中调用LoadLibrary?

编辑:

好的,我意识到我不能仅仅因为我必须像其他信徒那样相信 MSDN而在DllMain中调用LoadLibrary(我在那里看到了一些错误的东西,但我也应该忘记它们)。
并且因为在较新版本的Windows中可能会发生某些事情(尽管过去十年没有任何变化)。

但是,任何人都可以显示一个代码,它会重现在DllMain中调用LoadLibrary时发生的事情吗?在任何现有的Windows操作系统中? 不只是在另一个内部调用一个单例初始化函数,而在DllMain中调用LoadLibrary

6 个答案:

答案 0 :(得分:12)

你赞成继续这样做的论据似乎是:

  

微软说不要这样做,但我的   单个测试用例似乎有效,因此我不明白为什么没有人应该这样做。

您在一个很大的假设下运行:您假设Windows加载程序的底层实现永远不会改变。如果在“Windows 8”中更改加载程序以使您的代码无法正常工作,该怎么办?现在 Microsoft 被指责并且他们必须包含另一个兼容性黑客来处理代码,他们告诉 来写第一个的地方。

遵循指南。他们并不只是为了让你的生活变得更加艰难,他们在那里保证你的代码在未来的Windows上能像现在一样好用。

答案 1 :(得分:12)

从DllMain调用LoadLibrary是非常安全的简单甚至不那么简单的情况。但设计是DllMain可信赖不更改已加载模块的列表。

尽管拥有加载程序锁确实限制了DllMain中可以执行的操作,但它只与LoadLibrary规则间接相关。加载程序锁定的相关目的是序列化访问已加载模块的列表。虽然NTDLL在一个线程中在此列表上工作,但拥有加载程序锁可确保列表不会被在另一个线程中执行的NTDLL代码更改。但是,装载机锁是一个关键部分。它不会阻止同一线程重新获取加载程序锁并更改列表。

如果NTDLL在列表上工作时完全保留了自己,这无关紧要。但是,NTDLL规定在此工作中涉及其他代码,如初始化新加载的DLL时。每次NTDLL在列表上工作时都会调用外部,可以选择进行设计。从广义上讲,有两种选择。一种是稳定列表并释放加载程序锁,调用外部,然后获取加载程序锁并恢复列表上的工作,就像从头开始一样,因为外部调用可能已经改变了它。另一种方法是保持加载器锁定并信任被调用的代码,不要做任何改变列表的事情。因此,LoadLibrary在DllMain中成为禁区。

加载程序锁不会对停止 DllMain调用LoadLibrary做任何事情,甚至加载程序锁本身使得这样的调用不安全。相反,通过保留加载程序锁定,NTDLL 信任 DllMain不会调用LoadLibrary。

相比之下,请考虑关于不等待同步对象的DllMain规则。在这里,装载机锁具有直接作用,使其不安全。等待DllMain中的同步对象设置了死锁的可能性。所需要的只是另一个线程已经拥有你正在等待的对象,然后这个另一个线程调用任何等待加载器锁定的函数(例如,LoadLibrary,还有像看似无害的GetModuleHandle这样的函数)。

想要延长或打破DllMain规则可能是恶作剧甚至是彻头彻尾的愚蠢。但是,我必须指出,对于那些质疑这些规则有多么强大或有意义的人来说,至少部分归咎于微软。毕竟,有些并不总是被清楚而有力地记录下来,而且当我最后看时,他们仍然没有记录在他们肯定需要的所有情况下。 (我想到的例外是,至少在Visual Studio 2005之前,MFC程序员编写DLL被告知将他们的初始化代码放在CWinApp :: InitInstance中,但没有被告知此代码受DllMain规则约束。)

此外,对于来自微软的任何人来说,如果DllMain规则应该毫无疑问地被跟踪,那将会有点富裕。微软自己的程序员违反规则,并且在违反规则后继续存在导致严重的现实问题的例子。

答案 2 :(得分:8)

http://msdn.microsoft.com/en-us/library/ms682583%28VS.85%29.aspx中所述:

  

DllMain中的线程持有加载程序锁定,因此无法动态加载或初始化其他DLL。

干杯

答案 3 :(得分:3)

我正在研究一个可能需要在DllMain中使用LoadLibrary的案例,所以在调查时发现了这个讨论。从我今天的经历中得到的更新

阅读这篇文章可能会让人感到非常可怕http://blogs.msdn.com/b/oleglv/archive/2003/10/28/56142.aspx。不仅各种锁都很重要,而且libs传递给链接器的顺序也很重要。案件就是说一个双

现在,我已经在win7下用vc9尝试了这个。是的,它也是。根据libs如何传递给链接器的顺序,使用LoadLibrary工作与否。但是,与win8下的vc11相同,无论链接顺序如何都能正常工作。应用程序验证程序并不能归咎于此。

我现在并没有打算以这种方式使用它:)但仅仅是因为它与win10相同而且更进一步 - 这可能会更有用。无论如何,似乎win8下的加载器机制经历了一些明显的变化。

感谢。

答案 4 :(得分:0)

已经很晚了,但是仍然

如果在线程1(T1)上DllMain加载了其他库,则将调用这些其他库的DllMain;本身还可以,但是说他们的DLLMain创建一个线程(T2)并等待事件等待T2完成。

现在,如果T2在其处理过程中加载库,则加载器将无法获取锁,因为T1已经获取了该锁。由于T2挂在LoaderLock上,因此它永远不会表示T1正在等待事件。

这将导致死锁。

可能会有更多这样的情况,我想这里的广泛理由是我们不确定在其他库中将运行什么代码,因此不这样做是一个好主意(转为最佳实践)。 / p>

答案 5 :(得分:0)

以下是在 Windows 8/Server 2012 及更高版本中重现加载程序锁定挂起的方法。请注意,此代码不是直接调用加载库,而是使用触发加载库调用的 Windows API。

创建一个 Visual Studio C++ DLL 项目并在 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;
}

从第二个应用程序(尚未导入任何网络 API 或调用任何网络函数)创建一个包含以下代码的控制台桌面 C++ 应用程序:

HMODULE hModule;
hModule = LoadLibrary(L"<specify DLL created in previous example>"); // application will hang here