FreeLibrary持续返回true的原因?DllImport加载原生DLL卸载失败
嘿,这个坑我之前踩过好几次!结合你用DllImport加载原生DLL的背景,以及你提到的技巧,问题大概率出在CLR对DLL引用的内部管理和你手动操作的计数不匹配上,具体原因和解决办法我给你拆解下:
核心原因分析
1. CLR的DllImport会维护独立的引用计数(且和你的手动操作共享底层计数)
当你用DllImport声明原生函数时,CLR会自动调用LoadLibraryEx加载DLL,并且在内部保留一个引用计数。这个计数和你通过LoadLibrary/FreeLibrary操作的是同一个底层系统计数,但CLR不会轻易释放它——除非它确定你再也不会用到该DLL的任何函数。
如果你用的技巧是通过GetModuleHandle获取句柄(没有额外调用LoadLibrary),那你循环调用FreeLibrary其实是在减少CLR持有的计数,但只要CLR还在缓存函数指针、或者还有未回收的委托引用,它可能会在你减完计数后自动重新加载DLL(再次增加计数),导致FreeLibrary一直返回true,陷入死循环。
2. 你可能不小心重复增加了引用计数
如果那个技巧里包含了重复调用LoadLibrary来获取句柄(而不是GetModuleHandle),那每次调用LoadLibrary都会给DLL的引用计数+1。这时候你循环调用FreeLibrary只是在抵消自己加的计数,永远碰不到CLR持有的那个计数,自然一直返回true,DLL也卸不掉。
3. 其他线程或进程组件在持续引用DLL
如果你的程序里还有其他线程在调用该DLL的函数,或者有其他组件(比如第三方库)也加载了同一个DLL,那每次你调用FreeLibrary减计数,其他地方又会通过调用函数或加载操作把计数加回去,导致循环永远停不下来。
可行的解决办法
方法1:放弃DllImport,手动控制加载卸载
这是最稳妥的方式:
- 用
LoadLibrary手动加载DLL,获取句柄 - 用
GetProcAddress获取你需要的函数指针 - 用完后循环调用
FreeLibrary直到返回false,彻底卸载 - 这种方式你完全掌控引用计数,不会和CLR的内部管理冲突
方法2:强制CLR释放对DLL的依赖(不推荐,但应急可用)
如果你必须用DllImport,可以尝试以下步骤:
- 确保所有调用该DLL的代码都已经执行完毕,没有未完成的调用
- 强制触发垃圾回收:
GC.Collect(); GC.WaitForPendingFinalizers(); - 用
GetModuleHandle获取DLL句柄,然后循环调用FreeLibrary直到返回false
注意:这种方式不稳定,因为CLR可能会在之后的某个时刻重新加载DLL,导致未知错误。
方法3:检查DLL的DllMain逻辑
有些写得不好的DLL会在DLL_PROCESS_DETACH事件里重新加载自己(比如调用LoadLibrary),导致引用计数永远降不到0。你可以用工具(比如Dependency Walker)检查DLL的入口函数逻辑,排除这种情况。
内容的提问来源于stack exchange,提问作者Jan

