采取标准的Windows应用程序。 它使用LoadLibrary加载一个DLL来调用一个函数(我们称之为DLL_A)。 该函数加载另一个DLL(我们称之为DLL_B)。 应用程序现在使用FreeLibrary卸载DLL_A DLL,因为它不再需要它。
问题是: DLL_B仍然在内存中并加载?
这是我可以依赖的东西,还是没有logging?
不会, DLL_B
不会被卸载。 由DLL_A
进行的LoadLibrary()
调用将增加DLL_A
的负载计数。 由于DLL_B
没有对应的FreeLibrary()
调用,因此DLL_B
不会为零。
从LoadLibrary()文档:
系统在所有加载的模块上维护每个进程的引用计数。 调用LoadLibrary会增加引用计数。 调用FreeLibrary或FreeLibraryAndExitThread函数会减少引用计数。 系统在其引用计数达到零时或过程终止时(不管引用计数如何),卸载模块。
在这种情况下,你将有一个手柄泄漏:
Program -Load> Dll A -Load> Dll B -Unload> Dll A
没有代码被卸载的模块隐式地执行以卸载它加载的模块。
由于没有执行代码来减少引用计数,所以模块B将永远不会被卸载。
这里是加载/卸载DLL的规则:
仍在内存vs仍然加载:
不能保证当参考值达到0时,模块将在某个特定的时间从内存中释放。但是当引用计数达到0时,您应该考虑模块是否被卸载。
阻止DLL被卸载:
强制DLL被卸载,你可以尝试
编辑:
你提到你的目标是将代码注入正在运行的程序中,并且你想故意泄漏这个句柄。
这很好,但是如果你经常运行这个操作,会导致源程序崩溃,因为会使用太多的句柄,或者最终会使用太多的内存。
你可以从你的DllMain返回FALSE来阻止它被加载,这样你就不会浪费内存。 当fdwReason是DLL_PROCESS_ATTACH时,你可以这样做。 你可以在这里阅读更多 。
如果您试图模拟DLL并添加自己的额外功能,则需要实现源DLL所实现的所有功能,并将每个调用委托给源DLL。
阅读备注部分以获得详细的解释。
关键要注意的是:
系统为每个加载的模块维护每个进程的引用计数
并进一步下降
当模块的引用计数达到零或进程终止时,系统将从进程的地址空间卸载模块
来自MSDN :
释放加载的动态链接库(DLL)模块,并在必要时减少其引用计数。 当引用计数达到零时,模块将从调用进程的地址空间中卸载,并且句柄不再有效。
Windows中的DLL是引用计数。 当A被卸载时,你将递减A上的引用计数,如果它达到零,它将卸载,并且(假设没有代码中的错误)递减B上的引用计数。如果B上的引用计数变为零,则它将被卸载。 有可能DLL C在B上有一个refcount,卸载A不会卸载B.