【问题标题】:Calling LoadLibrary from DllMain从 DllMain 调用 LoadLibrary
【发布时间】:2010-12-06 20:50:26
【问题描述】:

MSDN says:

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

我试图从 DllMain 调用 LoadLibrary,但什么也没发生。

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

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

编辑:

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

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

【问题讨论】:

  • @Simon Mourier :与此问题无关。
  • 这个问题的潜台词是“我打算在我的代码中从 DllMain 调用 LoadLibrary 并且不想接受不这样做的建议”吗?
  • 这与是否有人“相信” MSDN 无关; MSDN 中的 API 规范是 Microsoft 保证始终有效的接口。您必须考虑任何其他行为可能会发生变化。当然,十年来什么都没有改变,但这并不妨碍微软在未来做出改变。不要忘记,Windows 9x 的加载器与 Windows NT 完全不同,尽管它们都支持 Win32。你真的希望你的代码是在不同的实现上不起作用的东西吗?

标签: windows winapi language-agnostic dll loadlibrary


【解决方案1】:

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

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

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

遵循指南。它们的存在不仅仅是为了让您的生活更加困难,而是为了保证您的代码在未来的 Windows 上也能像现在一样正常运行。

【讨论】:

    【解决方案2】:

    在一些简单的甚至不是那么简单的情况下,从 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 规则的约束。)

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

    【讨论】:

      【解决方案3】:

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

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

      干杯

      【讨论】:

      • @Abyx - 有什么意义?同一页面明确禁止从DllMain 调用LoadLibrary(Ex)。应该清楚的是,如果你这样做,你就得靠自己了。
      • @Fritschy:这并不一定意味着这是个问题。如果您在 DllMain 中调用 LoadLibrary,则它是从文档所说的当前持有加载程序锁的同一线程调用的,因此不应该出现死锁。虽然我不提倡使用 LoadLibrary,但文档说你不应该这样做是足够的理由不这样做。
      • @Praetorian:它不会死锁。它可能会崩溃。崩溃可能会在很久以后发生。如果您从 DllMain 调用 LoadLibrary,则无法保证 DllInitialization 将以正确的顺序发生。你可能会很幸运,它会起作用。但你可能会惨败。你无法知道会发生什么。
      • “你测试了吗?”,呃什么?!!
      • @Abyx:我没有测试它,但是有一些与它相关的错误的经验。然而,我倾向于按照文档告诉我的去做;)
      【解决方案4】:

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

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

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

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

      谢谢。

      【讨论】:

        【解决方案5】:

        已经很晚了,但还是,

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

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

        这会导致死锁。

        可能还有更多这样的场景,我想这里的广泛推理是我们无法确定哪些代码会在其他库中运行,所以最好不要这样做(转为最佳实践)。

        【讨论】:

          【解决方案6】:

          这是在 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
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2020-07-16
            • 2017-03-30
            • 2013-06-20
            • 2016-03-28
            • 2011-04-28
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多