EXE 和 DLL 加载共享 DLL 导致无效指针操作并在终止时挂起
EXE and DLL Loading shared DLL leads to Invalid Pointer Operation and hang on termination
我有一个可执行项目,它使用另一个 DLL 项目并使用 LoadLibrary
和 GetProcAddress
动态加载它。另一方面,我有另一个 DLL,它被 EXE 和第一个 DLL 使用,也使用 LoadLibrary
和 GetProcAddress
。我有一个包装共享 DLL 的通用单元,用于 EXE 和第一个 DLL。
问题是EXE加载了第二个DLL及其过程地址后,EXE加载了第一个DLL,然后第一个DLL又加载了第二个DLL,所以加载了两次。最终结果是,当第一个 DLL 从 EXE 中卸载时,它也会卸载第二个 DLL - 实际上共享同一个实例 - 因此也会卸载原始 EXE 已知的实例。这会导致关闭时出现 Invalid Pointer Operation
错误,因为第二个 DLL 已经卸载(并且它正在尝试再次卸载它)以及挂在后台的 EXE 不可见(使用一个完整的处理器核心)。
如何确保在仅 EXE 终止之前不卸载第二个(共享)DLL?
我认为有两种方法可以解决这个问题。
解决方案 1
加载时将 HMODULE
实例和每个 proc 地址传递到第一个 DLL 中,并使用该实例而不是尝试再次加载它。但是这个 DLL 有大量的导出函数需要传递给每个实例。
解决方案 2
向第二个 DLL 添加条件,例如 IS_DLL
,如果定义了此条件,则不要编译共享库的释放。 应该 调用 LoadLibrary
两次是安全的 - 第二次应该重新使用第一次。
{$IFNDEF IS_DLL}
FreeLibrary(FModule);
{$ENDIF}
我不确定第二种解决方案是否符合我的假设,但它是我的首选解决方案,因为第一种解决方案的工作量要大得多(考虑到 DLL 的大小和复杂性)。我已经尝试过了并且有效,我只是不确定可能存在什么问题。
How do I make sure the second (shared) DLL isn't unloaded until only the EXE is terminated?
DLL 由内部引用计数管理。每当您调用 LoadLibrary
时,引用计数都会增加。第一次调用 LoadLibrary
加载库并将引用计数设置为 1。随后的调用增加引用计数和 return 相同的模块句柄。调用 FreeLibrary
会减少引用计数。当它变为零时,库将被卸载。对 LoadLibrary
的每个调用都应与另一个对 FreeLibrary
的调用相匹配。
文档对所有这些都非常清楚。来自 LoadLibrary
:
The system maintains a per-process reference count on all loaded modules. Calling LoadLibrary increments the reference count. Calling the FreeLibrary or FreeLibraryAndExitThread function decrements the reference count. The system unloads a module when its reference count reaches zero or when the process terminates (regardless of the reference count).
来自 FreeLibrary
:
Frees the loaded dynamic-link library (DLL) module and, if necessary, decrements its reference count. When the reference count reaches zero, the module is unloaded from the address space of the calling process and the handle is no longer valid.
你可以从所有这些中得出结论,你误诊了你的问题。您提出的两个 "solutions" 都不需要。那是因为无论你的问题是什么,它都不是你想的那样。只要引用计数为正,您只需调用 LoadLibrary
并依靠系统保持模块加载。
- 如果 DLL 正在卸载,那是因为您调用了
FreeLibrary
并且它的引用计数变为零。如果您希望它保持加载状态,请在完成之前不要调用 FreeLibrary
。
- 如果 DLL 没有卸载,那么您的问题显然不是您所说的那样。
我有一个可执行项目,它使用另一个 DLL 项目并使用 LoadLibrary
和 GetProcAddress
动态加载它。另一方面,我有另一个 DLL,它被 EXE 和第一个 DLL 使用,也使用 LoadLibrary
和 GetProcAddress
。我有一个包装共享 DLL 的通用单元,用于 EXE 和第一个 DLL。
问题是EXE加载了第二个DLL及其过程地址后,EXE加载了第一个DLL,然后第一个DLL又加载了第二个DLL,所以加载了两次。最终结果是,当第一个 DLL 从 EXE 中卸载时,它也会卸载第二个 DLL - 实际上共享同一个实例 - 因此也会卸载原始 EXE 已知的实例。这会导致关闭时出现 Invalid Pointer Operation
错误,因为第二个 DLL 已经卸载(并且它正在尝试再次卸载它)以及挂在后台的 EXE 不可见(使用一个完整的处理器核心)。
如何确保在仅 EXE 终止之前不卸载第二个(共享)DLL?
我认为有两种方法可以解决这个问题。
解决方案 1
加载时将 HMODULE
实例和每个 proc 地址传递到第一个 DLL 中,并使用该实例而不是尝试再次加载它。但是这个 DLL 有大量的导出函数需要传递给每个实例。
解决方案 2
向第二个 DLL 添加条件,例如 IS_DLL
,如果定义了此条件,则不要编译共享库的释放。 应该 调用 LoadLibrary
两次是安全的 - 第二次应该重新使用第一次。
{$IFNDEF IS_DLL}
FreeLibrary(FModule);
{$ENDIF}
我不确定第二种解决方案是否符合我的假设,但它是我的首选解决方案,因为第一种解决方案的工作量要大得多(考虑到 DLL 的大小和复杂性)。我已经尝试过了并且有效,我只是不确定可能存在什么问题。
How do I make sure the second (shared) DLL isn't unloaded until only the EXE is terminated?
DLL 由内部引用计数管理。每当您调用 LoadLibrary
时,引用计数都会增加。第一次调用 LoadLibrary
加载库并将引用计数设置为 1。随后的调用增加引用计数和 return 相同的模块句柄。调用 FreeLibrary
会减少引用计数。当它变为零时,库将被卸载。对 LoadLibrary
的每个调用都应与另一个对 FreeLibrary
的调用相匹配。
文档对所有这些都非常清楚。来自 LoadLibrary
:
The system maintains a per-process reference count on all loaded modules. Calling LoadLibrary increments the reference count. Calling the FreeLibrary or FreeLibraryAndExitThread function decrements the reference count. The system unloads a module when its reference count reaches zero or when the process terminates (regardless of the reference count).
来自 FreeLibrary
:
Frees the loaded dynamic-link library (DLL) module and, if necessary, decrements its reference count. When the reference count reaches zero, the module is unloaded from the address space of the calling process and the handle is no longer valid.
你可以从所有这些中得出结论,你误诊了你的问题。您提出的两个 "solutions" 都不需要。那是因为无论你的问题是什么,它都不是你想的那样。只要引用计数为正,您只需调用 LoadLibrary
并依靠系统保持模块加载。
- 如果 DLL 正在卸载,那是因为您调用了
FreeLibrary
并且它的引用计数变为零。如果您希望它保持加载状态,请在完成之前不要调用FreeLibrary
。 - 如果 DLL 没有卸载,那么您的问题显然不是您所说的那样。