3

MSDN 说:

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

我试图LoadLibrary从 DllMain 打电话,但什么也没发生。

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

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

编辑:

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

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

4

6 回答 6

15

您支持继续这样做的论点似乎是:

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

您在一个很大的假设下操作:您假设 Windows 加载程序的底层实现永远不会改变。如果加载程序在“Windows 8”中以某种方式更改以使您的代码不再正常工作怎么办?现在微软为此受到指责,他们必须包含另一个兼容性黑客来解决他们告诉 不要首先编写的代码。

遵循指南。它们的存在不仅仅是为了让你的生活更加困难,它们还可以保证你的代码在未来的 Windows 上和现在一样好用。

于 2010-12-06T23:12:01.737 回答
15

从 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 之前,编写 DLL 的 MFC 程序员被告知将他们的初始化代码放在 CWinApp::InitInstance 中,但没有被告知此代码受 DllMain 规则的约束。)

此外,如果微软的任何人都说应该毫无疑问地遵循 DllMain 规则,那将是有点丰富。微软自己的程序员违反规则的例子存在,并且即使在违反规则被认为造成了严重的现实世界麻烦之后仍继续这样做。

于 2010-12-10T07:35:41.630 回答
9

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

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

干杯

于 2010-12-06T20:55:40.680 回答
3

我正在处理一个可能需要在 DllMain 中使用 LoadLibrary 的案例,因此在调查时发现了这个讨论。根据我今天的经验对此进行更新

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

现在,我在win7下用vc9试过了。是的,就是这样。根据库传递给链接器的顺序,使用 LoadLibrary 是否有效。但是,在win8下与vc11相同,无论链接顺序如何,都可以正常工作。应用程序验证者不会对此负责。

我不是现在和任何地方都要求以这种方式使用它:) 但仅供参考,如果它与 win10 及其他版本相同 - 这可能会有更多用处。无论如何,win8下的loader机制似乎发生了一些明显的变化。

谢谢。

于 2015-05-25T00:44:56.920 回答
0

已经很晚了,但仍然,

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

现在如果 T2 在其处理过程中加载一个库,加载器将无法获取锁,因为 T1 已经获取了它。由于 T2 挂在 LoaderLock 上,它永远不会发出 T1 正在等待的事件信号。

这将导致死锁。

可能还有更多这样的情况,我想这里的广泛推理是我们无法确定哪些代码将在其他库中运行,所以不这样做是个好主意(变成了最佳实践)。

于 2019-04-17T16:34:44.867 回答
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
于 2021-02-06T11:18:22.723 回答