首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >如何处理即使引用计数不是零,动态加载的DLL也可以被Windows卸载的事实?

如何处理即使引用计数不是零,动态加载的DLL也可以被Windows卸载的事实?
EN

Stack Overflow用户
提问于 2021-06-16 05:46:38
回答 1查看 530关注 0票数 2

文档中说:“系统卸载一个模块时,它的引用计数达到零,或者当进程终止时(不管引用计数)。”

这导致了一个重大的问题,目前还不清楚如何解决它。关于这个问题。假设我们有一个可执行文件E,它显式地依赖于一个DLL库L1,该库通过LoadLibrary动态加载DLL库L2。当E完成并且进程开始终止时,可以在L2之前卸载L1。如果L1有一个具有静态存储时间的对象(该对象包含从L2获得的一些资源),这可能会造成问题,因为这些资源将在L1卸载时开始破坏,这意味着对已经卸载的L2的调用将导致崩溃。

可能的解决方案之一(甚至是唯一的解决方案)是不销毁具有静态存储持续时间的对象。该解决方案的缺点是L1可能会被动态加载多次,从而增加内存泄漏。这可以通过使用DllMain来缓解,在这里我们可以检查进程是否终止,或者库是否通过FreeLibrary卸载。

到目前为止,一切似乎都很顺利。但还有更多。假设我们希望L1是一个与名为L_SHIM的DLL库链接的静态库。因此,现在可执行文件E与L_SHIM链接,整个DllMain技巧不再工作。实际上,如果不允许我们修改L_SHIM库,那么似乎没有什么是可行的。

有人要解决这样的问题吗?对于可能的解决方案,我将不胜感激。

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2021-06-16 08:54:08

当处理DLL时,只有在动态卸载DLL ( lpReserved参数为NULL)的情况下,DLL才应该释放堆内存等资源。如果进程正在终止( lpvReserved参数为非空),除当前线程外,进程中的所有线程要么已经退出,要么通过调用ExitProcess函数显式结束,这可能会使堆等进程资源处于不一致的状态。在这种情况下,DLL清理资源是不安全的。相反,DLL应该允许操作系统恢复内存。

因此,您需要检查lpvReserved内部的DllMain

让您拥有全局变量:

代码语言:javascript
运行
复制
bool g_is_terminating = false;

DllMain

代码语言:javascript
运行
复制
case DLL_PROCESS_DETACH: g_is_terminating = (reserved != nullptr);

DllMain之后调用全局/静态对象的析构函数。这样您就可以签入析构函数g_is_terminating

代码语言:javascript
运行
复制
if (!g_is_terminating) { do_something(); }

另一种总是很有用而且非常简单的方法,使用无文档的api。

代码语言:javascript
运行
复制
//This routine returns the status of process shutdown.

EXTERN_C
DECLSPEC_IMPORT
BOOLEAN
NTAPI
RtlDllShutdownInProgress();

此api从ntdll.dll导出-因此您需要使用或ntdll.lib或ntdllp.lib (以防您不使用crt)

只需从destructror调用RtlDllShutdownInProgress();,如果返回true --而不是访问另一个dll --这意味着进程正在终止。

代码语言:javascript
运行
复制
if (!RtlDllShutdownInProgress()) { do_something(); }

内部,ntdll中存在全局变量。

代码语言:javascript
运行
复制
BOOLEAN LdrpShutdownInProgress = FALSE;

ExitProcess调用- than LdrShutdownProcessntdll.dll调用并开始设置LdrpShutdownInProgress = TRUE;时,RtlDllShutdownInProgress()只是返回LdrShutdownProcess的值。

以及“当进程终止时,系统卸载一个模块(不管引用计数如何)”。

这是假的。进程开始终止后,系统不会卸载任何dll。即使此时直接调用FreeLibrary/LdrUnloadDll (在LdrpShutdownInProgress设置为TRUE之后)- dll也不会被卸载。真的--没什么大不了的--因为很快所有的过程都会被摧毁。

..which意味着对已经卸载的L2的调用将导致崩溃。实际上,L2永远不会在调用ExitProcess之后卸载eb。但是 DLL_PROCESS_DETACH将在L2中被调用。这将始终在L1 DLL_PROCESS_DETACH通知之前(此通知按LIFO顺序发送。因此,如果L1加载L2 - DLL_PROCESS_ATTACH之前调用L1,则在L2 - L1之前,L2在InInitializationOrderLinks列表中调用.和进程退出-这个列表(InInitializationOrderLinks)处理的顺序相反。

因此,尽管L2仍在内存中,但它已经处于"uninit“状态(在DLL_PROCESS_DETACH之后),在L2中调用某些api可能会导致未定义的结果。它可以工作,没有任何问题,或崩溃或随机结果。更糟糕的是,比较稳定的崩溃

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/67996998

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档