gpt4 book ai didi

delphi - Delphi DLL 是注定要用于加载程序锁的吗?

转载 作者:行者123 更新时间:2023-12-03 14:47:33 24 4
gpt4 key购买 nike

有一个 DLL,由主(桌面)应用程序通过 Windows.LoadLibrary 动态加载。这是因为有很多类似的 DLL,并且只有很少到一个需要在运行时加载。所以静态链接不是一个选项。

问题是,每隔一段时间,主应用程序就会在加载这些 DLL 之一时挂起。请注意,每个问题都可能发生。可能是因为它们有很多共同的代码库。

问题似乎是加载程序锁 ( see this SO answer on what it is )。我发现了一段公共(public)代码,启动时所有 DLL 在 library 单元的 begin...end 部分(即 project.dpr),其中使用 GetModuleHandleGetProcAddress

我发现,这完全不适合 DLL,因为 DLL 项目文件的 begin...end 部分实际上是库的 DllMain函数并调用此类函数可能会导致死锁(称为加载器锁)。我在这个Microsoft Best Practice Guide中读到了这一点.

因此,我重建了代码,以便稍后在 Windows.LoadLibrary 调用完成时调用这些调用。

不幸的是,挂起问题仍然存在。 :-(

然后我运行了调试器,单步调试了在执行一行代码之前调用的每一个初始化部分。我确定,许多第三方代码违反了 DLL 初始化代码中该做什么和不该做什么的指南:

  • TMS 组件包
  • JEDI 组件库
  • OmniThreadLibrary
  • Indy 组件

以上所有内容都在初始化代码中动态加载其他DLL,或通过GetProcAddress请求过程指针。我认为这些调用会导致加载 DLL 时挂起。

难道只有少数 Delphi 开发人员知道初始化的危险吗?对此我能做什么?

最佳答案

这是一个常见问题,我认为并不是 Delphi 程序员特有的问题。如果您的代码在初始化部分中调用 LoadLibrary ,或者在终结部分中调用 FreeLibrary ,那么该代码在库中使用是不安全的。

请注意,我并不熟悉您提到的所有库,并且根本无法确认它们都具有在库中使用不安全的初始化部分代码。我认为这是需要您确认的事情——我想坚持这个答案中的概念,而不是对特定的 Delphi 库进行评论。

我想说的是,从 DllMain 调用 GetModuleHandleGetProcAddress 是没问题的。我这么说是因为您特别提到了GetProcAddress。绝对可以通过调用GetModuleHandle来获取模块句柄,然后通过调用GetProcAddress来获取函数地址。因此,如果某些可疑库这样做,并且不调用 LoadLibrary,那么它们可能没问题。

无论如何,根据上述规定,您需要安排从 DllMain 调用的任何违反 Microsoft 规定的规则的代码都在安全时间调用,而不是从DllMain。不幸的是,这些规则充其量是模糊的。微软say the following in the DllMain documentation :

The entry-point function should perform only simple initialization or termination tasks. It must not call the LoadLibrary or LoadLibraryEx function (or a function that calls these functions), because this may create dependency loops in the DLL load order. This can result in a DLL being used before the system has executed its initialization code. Similarly, the entry-point function must not call the FreeLibrary function (or a function that calls FreeLibrary) during process termination, because this can result in a DLL being used after the system has executed its termination code.

Because Kernel32.dll is guaranteed to be loaded in the process address space when the entry-point function is called, calling functions in Kernel32.dll does not result in the DLL being used before its initialization code has been executed. Therefore, the entry-point function can call functions in Kernel32.dll that do not load other DLLs. For example, DllMain can create synchronization objects such as critical sections and mutexes, and use TLS. Unfortunately, there is not a comprehensive list of safe functions in Kernel32.dll.

最后一段给你的指导很少。为了确保您的库强大,您需要执行以下操作:

  1. 安排您控制其源代码的任何单元的每个初始化部分向中央注册表注册初始化和完成过程。
  2. 在可执行项目中,您可以在注册时调用初始化过程,并在程序终止时以相反的顺序调用终止过程。
  3. 在库项目中,您可以推迟调用这些初始化和完成过程。从 DLL 中导出一对函数,DLL 的使用者可以调用这些函数来请求调用这些初始化和终止过程。

这是我在图书馆中采用的方法,多年来一直对我很有帮助。

这种方法涉及大量工作,并且缺点是您需要修改第三方库。但是,如果这些库在交付使用时无法正常工作,您有什么替代方案?

也许在较慢的时间内,您可以联系您认为与库中的使用不兼容的任何库的开发人员。尝试说服他们更改代码,使其与库中的使用兼容。从 Remy 对您的问题的评论中可以看出,库开发人员完全有可能没有意识到这个问题,并且非常愿意做出更改。

关于delphi - Delphi DLL 是注定要用于加载程序锁的吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34882124/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com