DLL卸载后使用std::weak_ptr触发AV错误的最优解决方案咨询
跨DLL std::weak_ptr生命周期与DLL卸载冲突的最优解决方案
当前异常触发的本质是两个生命周期绑定错误:1. std::shared_ptr控制块的删除逻辑绑定了已卸载DLL的代码段地址;2. 控制块或对象内存分配在DLL私有堆上,DLL卸载后堆内存被回收导致访问异常。以下是按实施成本、稳定性排序的可行方案:
方案1:应用侧统一托管shared_ptr生命周期(兼容性最优)
- 实现逻辑:
- 公共接口基类定义纯虚函数、虚析构函数,虚表的析构入口会指向派生类所在DLL的实现,无需额外绑定删除器逻辑
- DLL对外暴露对象创建接口时仅返回裸指针,禁止在DLL内部创建
std::shared_ptr/std::weak_ptr实例 - 应用侧拿到裸指针后,用应用侧的分配器构造
std::shared_ptr<公共基类>,此时控制块完全分配在应用进程的默认堆上,删除器仅调用基类的虚析构函数,逻辑不依赖DLL的代码段
enable_shared_from_this兼容方案:
将std::enable_shared_from_this<公共基类>作为公共基类的父类继承,应用侧构造std::shared_ptr时会自动初始化内部的弱引用计数,只要所有跨模块传递的都是基类的std::shared_ptr/std::weak_ptr,调用shared_from_this()不会触发bad_weak_ptr异常。- 优势:改动量最小,完全规避控制块访问已卸载DLL的问题。
方案2:DLL生命周期锚定(无侵入兼容现有代码)
- 实现逻辑:
在DLL内部创建std::shared_ptr时,为其绑定带模块引用计数的删除器:
该逻辑会在创建对象时给当前DLL增加一个引用计数,主动调用卸载DLL接口时不会立即释放DLL的代码段和堆内存,直到所有// DLL内部对象创建接口实现 HMODULE hCurrentMod = nullptr; GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (LPCTSTR)__FUNCTION__, &hCurrentMod); std::shared_ptr<公共基类> ptr(new 派生类(), [hCurrentMod](公共基类* p) { delete p; FreeLibrary(hCurrentMod); });std::shared_ptr/std::weak_ptr实例析构、控制块完全释放后,才会真正卸载DLL。 - 优势:无需修改应用侧现有逻辑,DLL侧修改即可解决问题。
方案3:代理对象隔离(稳定性最优)
- 实现逻辑:
应用侧实现一层继承自公共接口的代理类,内部持有DLL对象的裸指针和所属DLL的标识:- 应用侧缓存的所有
std::shared_ptr均指向代理对象,控制块完全归属应用侧 - DLL卸载时,遍历缓存将对应DLL的所有代理对象内部裸指针置为空,后续所有接口调用先判空再转发
- 代理类的析构函数仅在应用侧执行,即使DLL已卸载,
std::weak_ptr析构时仅访问应用侧的控制块,不会触发访问异常
- 应用侧缓存的所有
- 优势:彻底隔离DLL生命周期与智能指针生命周期,完全规避跨模块内存访问风险,适合稳定性要求高的生产环境。
内容的提问来源于stack exchange,提问作者Yog.Muskrat
相关产品推荐
相关产品推荐

