当前位置: 首页 > 科技观察

为什么动态链接库以“错误”方式卸载?

时间:2023-03-13 06:36:00 科技观察

当程序启动或加载DLL时,加载程序将生成该程序/DLL引用的所有DLL的依赖关系树,该DLL的依赖关系等。然后它确定初始化这些DLL的正确顺序,以便在初始化它所依赖的所有DLL之前,没有初始化任何DLL。(当然,如果你有循环依赖,那么它会崩溃。从DLL的DLL_PROCESS_ATTACH通知调用LoadLibrary函数或LoadLibraryEx函数也会打乱这些依赖的计算。)同样,当卸载DLL或程序时终止时,会发生去初始化,以便DLL在其所有依赖项之后被去初始化。但是,当您手动加载DLL时,关键信息会丢失:调用LoadLibrary的DLL取决于正在加载的DLL。因此,如果A.DLL手动加载B.DLL,并不能保证A.DLL会先于B.DLL被卸载。这意味着,例如,像下面这样的代码是不可靠的:在标记为“oops”的行中,不能保证B.DLL仍在内存中,因为B.DLL不会作为A.DLL列表的依赖项出现,即使存在因调用LoadLibrary而产生的运行时生成的动态依赖项。为什么加载程序不能跟踪这种动态依赖性?换句话说,当A.DLL调用LoadLibrary("B.DLL")时,为什么加载程序不能自动说“好吧,现在A.DLL依赖于B.DLL”?首先,因为正如我在之前的帖子中指出的那样,您不能相信返回地址。第二,即使你可以信任回邮地址,你仍然不能相信回邮地址。让我们看下面的代码:在这种情况下,B.DLL的加载不是直接从A.DLL发生的,而是通过一个中间体(在这种情况下是一个中间函数)进行的。即使您可以信任返回地址,相关性也会分配给MID.DLL而不是A.DLL。你可能会问,“什么样的人会写一个像中间函数这样的函数?”。这样的中间函数在辅助函数/包装函数中很常见,或者提供额外的生命周期管理功能(虽然它不再这样做了,但它曾经这样做过)。三是调用GetModuleHandle函数的情况。让我们看看下面的代码:我们对GetModuleHandle的调用是否应该创建一个依赖项?另请注意,与调用LoadLibrary相比,DLL之间的依赖关系更多。例如,如果将回调函数指针传递给另一个DLL,则会创建反向依赖关系。最后要注意的是,如上所述,这种隐式依赖很难看到,而且一旦你放入全局析构函数,情况就更糟了。例如下面的代码:DLL依赖项现在隐藏在SomethingHolder类中,当A.DLL卸载时,g_SomethingHolder的析构函数将运行并尝试与B.DLL通信。接下来是程序不可预测的行为。综上所述,尽量不要对系统的运行行为做太多假设,严格按照MSDN说的,老老实实写自己的代码,基本不会出现大错误。