它不能调用LoadLibrary或LoadLibraryEx函数 (或调用这些函数的函数), 因为这可能会在DLL加载顺序中创建依赖循环。 这可能导致在系统执行其初始化代码之前使用DLL。
我试图从DllMain打电话给LoadLibrary
但没有任何反应。
我看到的唯一问题是加载的DLL将在我的DllMain执行之前使用我的DLL中的函数。
为什么我不能在DllMain中调用LoadLibrary?
好的,我意识到我不能仅仅因为我必须像其他信徒那样相信 MSDN而在DllMain中调用LoadLibrary(我在那里看到了一些错误的东西,但我也应该忘记它们)。
并且因为在较新版本的Windows中可能会发生某些事情(尽管过去十年没有任何变化)。
但是,任何人都可以显示一个代码,它会重现在DllMain中调用LoadLibrary
时发生的事情吗?在任何现有的Windows操作系统中?
不只是在另一个内部调用一个单例初始化函数,而在DllMain中调用LoadLibrary
答案 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