【问题标题】:Changing Windows DLL load order? (load order, not search order)更改 Windows DLL 加载顺序? (加载顺序,不是搜索顺序)
【发布时间】:2011-09-16 08:08:01
【问题描述】:

假设我有一个可执行文件:app.exe

我在这个可执行文件中使用了 2 个不同的第 3 方 DLL:foo.dllbar.dll,并且应用程序必须隐式链接到这些 DLL,也就是说我不能使用 ::LoadLibrary 来加载它们。

(注意:不是我不能调用LoadLibrary,而是这些DLL需要静态链接(带有__declspec(dllexport)的C++ DLL),所以我调用LoadLibrary没有任何意义,因为可执行加载程序已经调用它。)

这两个 DLL 确实 彼此之间没有任何依赖关系,也就是说,据我所知,它们的加载顺序是未定义的(并且 应该 是不相关的)。 (两者的依赖基本上都只在标准的windows dll(kernel32、msvcrt等)上

我现在有一个问题,我希望控制这些 DLL 的加载顺序,即我希望 foo.dll 在 bar.dll 之前始终加载 (DLL_PROCESS_ATTACH)。

是否有可能告诉 Windows DLL 加载程序先加载一个 DLL?

编辑:要检查可执行文件的 DLL 加载顺序,可以使用 DUMPBIN.exe 实用程序:(只需启动 Visual Studio 命令提示符)

编辑:根据this answer / this blog entry,NT 加载器确实按顺序遍历导入部分。 (这将导致 独立 DLL 按它们在导入部分中出现的顺序加载。)

C:\path\to\program> dumpbin /IMPORTS app.exe | grep -i \.dll
  MSVCR80D.dll
  KERNEL32.dll
  OLEAUT32.dll
  MSVCP80D.dll
  foo.dll
  bar.DLL

此输出意味着 MSVCR80D.dll(及其依赖项[a])将首先加载,而 bar.DLL 将最后加载。卸载将以相反的顺序进行。

还没有发现如何影响这个加载顺序 ...


(注释)

[a] :这当然意味着例如kernel32.dll 会先加载,因为 msvcr80d.dll 会依赖 kernel32.dll。


根据一些要求,我为此添加了一个理由:(但是,我仍然对此一般感兴趣。我知道如何解决 MFC 问题。)

它的调试版本中的 Microsoft MFC DLL 内置了内存泄漏检测。(据我所知,它与 _CrtSetDbgFlag 和相关工具使用的机制相同。)

MFC 调试 DLL 将在卸载时转储所有未释放的内存。现在,如果您的进程中有第二个 DLL,它独立于 MFC,并且这个第二个 DLL 在 DLL_PROCESS_DETACH 上释放内存,如果 MFC DLL 在另一个 dll 之前卸载,则 MFC 报告机制将报告错误的内存泄漏。

如果可以确保调试 MFC DLL 在所有独立 DLL 中最先加载/最后卸载,那么所有其他 DLL 本身就已经清理完毕,并且 MFC 不会报告错误泄漏。

【问题讨论】:

  • 您的编辑不正确。当然kernel32.dll 不会在低级 CRT DLL (msvcr*.dll) 之后加载:P 你可以使用ProcMon 或 WinDbg 来查找。
  • @Martin Dumpbin 未按加载顺序列出其导入。我不知道你是从哪里得到这个想法的。
  • @David:根据其他问题答案中的MS blog linked to,Loader 实际上确实按顺序遍历导入表。
  • @David:我需要多久重复一次independent这个词?
  • @Martin 现在问题好多了,因为您已经给出了一些上下文并解释了为什么要影响加载顺序。我认为你最大的问题是你不明白我们喜欢被这种理由所激励。即使在您的编辑中,您也非常不情愿地给出问题背后的原因。不要那样。告诉我们你问这个问题的原因。你提供的信息越多,你的潜在回答者就越有动力,你可能会得到更好的答案。既然你的问题很清楚,例如,我删除了我的答案。

标签: c++ windows dll loader


【解决方案1】:

这是一个想法:在app.exe的链接器选项中将它们标记为“延迟加载的dll”怎么样?

延迟加载将允许您“静态”链接(即没有 LoadLibrary() 等),但不会加载 DLL 并在实际需要之前进行链接。

如果这是一个选项,那么(假设您可以等待这么久,即在 main() 之前不要访问 foo/bar dll 函数),您可以在 main() 中访问一个函数(只需获取一个函数 ptr或其他东西)首先在foo.dll 中加载它并绑定所有“静态”链接的函数?

(也许 LoadLibrary() 会在需要时触发相同的链接过程。不确定。不过在您的代码中看起来会更干净。)

【讨论】:

  • Ehrm,猜猜延迟加载助手在内部做了什么?是的,使用LoadLibrary 不好的建议。当然,OP 没有解释为什么不能使用 LoadLibrary。此外,延迟加载稍微贵一点,因为它使用 SEH 来捕获调用指针尚未解析的函数的尝试。但是,这可以通过在程序开始时调用库函数来解决。旁注:可以覆盖默认的延迟加载帮助器并编写自定义的帮助器。
  • 使用延迟加载会导致从 DLLMain 调用 LoadLibrary 并且会死锁加载器锁
  • 好主意。这确实有助于加载顺序。我必须检查它是否适用于我的特定设置。 -- 虽然我宁愿认为延迟加载方法是一种解决方法而不是真正的解决方案:-)
  • @STATUS_ACCESS_DENIED:我认为 OP 希望避免使用LoadLibrary(),因为使用GetProcAddress() 检索每个函数对于大多数应用程序来说都非常麻烦,因此人们更喜欢导入库。使用 LoadLibrary 来加载延迟导入的 dll(而不是调用返回 42 的虚拟函数)应该没问题。
  • @David:没有人说过从DLLMain() 启动库加载。我说的是正确的main()WinMain()
【解决方案2】:

我还没有发现如何影响这个加载顺序......

我不知道为什么我没有尝试过,但似乎生成的模块的导入部分顺序确实取决于提供给链接器的 lib 文件的顺序。

Configuration Properties -> Linker -> Additional Dependencies ...

这里首先列出的 lib 文件也是导入部分的第一个,meaning 加载器将按顺序导入这些文件(模依赖)。

所以,回答这部分:只需以正确的顺序将 lib 文件提供给链接器即可。

注意:我已经在 VS2005 上尝试过,它似乎可以工作。我不知道这是否记录在某处,或者它是否在较新版本的 VC++ 中发生了变化。


更新:虽然它当时有效,但今天我遇到了加载顺序不受受到链接器命令行顺序影响的情况 lib 文件中的 em>。 (仍然)不知道为什么。 (还是VS2005)

不过,我设法通过将有问题的 DLL 添加到延迟加载的 DLL 列表(如在 Macke's answer 中)来使其工作。


【讨论】:

    【解决方案3】:

    只需将foo.dll 添加到bar.dll 的导入表中,其余的将由操作系统加载程序处理。

    您应该可以在没有bar.dll 的源代码的情况下执行此操作,不确定editbin 工具是否有这样的选项,但这是对 PE 文件的相当简单的编辑。

    您也许可以使用预加载 DLL 的注册表设置,但我不会这样做,您不希望 foo.dll 被加载到不需要它的其他进程中。

    【讨论】:

    • @David:我们被告知foo.dll 不依赖于bar.dll,因此没有循环引用。
    • @Ben 是的,我知道。 OP的问题对我来说不是那么有趣。我感兴趣的是您是否知道 DLL 加载程序如何解析循环引用。在这种情况下,即使 OP 可以在另一个 DLL 之前加载一个 DLL,在 DLLMain 中要求这样的依赖也是完全错误的。
    • @David:不,我不知道加载器如何处理循环引用。一种明智的方法是对两个模块进行内存映射,然后以未指定的顺序调用入口点,但我认为这不能保证。如果你有循环依赖,最好不要在循环中的任何DLL的DllMain中做任何事情。
    • @David:实际上很有趣的问题,虽然很理论。今晚将窥视 NTDLL,看看我能挖掘出什么。我的猜测是,定义因素是从 EXE 开始的模块顺序,然后是每个 DLL 自己的导入等等。正如本所说,或多或少是任意的。
    • editbin 似乎没有该选项。我想知道如何将更改 PE 文件表视为相当微不足道(如果没有现有的工具可以做到这一点):P 这不是大多数人每天处理的事情。
    【解决方案4】:

    如果您不链接导入库(foo.lib 和 bar.lib),则加载程序不会在启动时自动加载 DLL,您可以随时调用 LoadLibrary()。

    顺便说一句,我编写了一个方便的小库,用于封装动态加载的 DLL,而无需直接处理 LoadLibrary/GetProcAddress。你可以阅读它here

    【讨论】:

    • 延迟加载同样方便 ;)
    • 其实,当它适用的时候,我会说延迟加载要方便得多。但有时,直到运行时您才知道需要加载哪些 DLL 或它们将位于何处。
    • 公平点,但即使是这些情况也可以在延迟加载帮助程序中动态处理。只需覆盖默认值,您就可以根据需要进行自定义 :) ... 包括用于定位正确 DLL 的代码 ...
    • 我没有意识到你可以做到这一点。不错!
    • @0xC0000022L: 我公司只是简单的把想要的DLL重命名为特定的名字,而具体的名字是延迟加载的:/
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-06
    • 1970-01-01
    • 2011-11-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多