.NET 7 NativeLibrary:如何彻底卸载并重加载不同版本SO库
核心问题根源
Linux动态链接器(ld.so)会根据SO库的文件路径/SONAME识别库实例:同一路径的SO即使更新内容,也可能被缓存复用;且全局初始化代码(如__attribute__((constructor)))仅在库首次加载时执行一次,后续重新加载同一实例不会重复执行。此外,AssemblyLoadContext仅管理.NET托管程序集,无法干预原生SO的系统级加载生命周期,因此之前的尝试无效是正常的。
可行解决方案
1. 为每个版本的SO库分配唯一路径
不要覆盖旧版本SO,而是将不同版本的库放在独立目录中,例如:
./libs/v1.0.0/libmylib.so ./libs/v2.0.0/libmylib.so
加载时直接指定对应版本的绝对路径,这样动态链接器会将它们视为完全独立的库,每次加载新版本都会重新执行全局初始化逻辑。
2. 精准控制NativeLibrary.Load的加载行为
使用NativeLibrary.Load的重载参数明确指定加载路径,避免系统加载缓存的旧版本:
var libPath = "/app/libs/v2.0.0/libmylib.so"; var handle = NativeLibrary.Load( libPath, Assembly.GetExecutingAssembly(), DllImportSearchPath.SafeDirectories );
加载完成后,确保调用NativeLibrary.Free(handle)释放句柄,且没有其他代码持有该句柄。
3. 检查并释放所有库句柄
Linux下,只有当所有指向该SO的句柄都被dlclose(对应.NET的NativeLibrary.Free)调用后,动态链接器才会真正卸载库。可以通过以下命令检查是否有残留句柄:
lsof | grep libmylib.so
如果存在其他进程或线程持有句柄,需先释放这些资源,才能让库被彻底卸载。
4. 重构SO库的初始化逻辑(推荐)
将全局初始化宏改为显式初始化函数,例如在SO中定义:
void mylib_init() { // 原全局构造函数中的初始化逻辑 }
在.NET代码中,每次加载新版本SO后,主动通过P/Invoke调用这个初始化函数:
[DllImport("libmylib.so")] private static extern void mylib_init(); // 加载库后调用 mylib_init();
这种方式不依赖动态链接器的全局初始化机制,无论库是否重新加载,都能确保初始化逻辑执行。
5. 极端情况:重启服务
如果以上方法都无效,且无法修改SO库代码,只能通过重启.NET服务来彻底清除动态链接器的缓存,确保加载新版本SO时执行初始化逻辑。
内容的提问来源于stack exchange,提问作者BBO

