C++26反射无法自动发现DLL/so中的插件类,因其仅支持编译期本翻译单元已知类型,不能跨模块探查未声明的类结构;插件加载必须依赖显式C函数入口点和严格ABI一致。

直接用 std::reflexpr 或任何 C++26 反射机制自动发现 DLL/.so 中的插件类,不可行——它根本看不到那些类型,编译器在主程序里压根没见过它们。
为什么不能靠虚函数多态直接跨动态库调用
虚函数调用依赖 vtable,而 vtable 地址、偏移、RTTI 布局受编译器版本、STL 实现、构建选项(如 _GLIBCXX_DEBUG)影响,跨模块传递会导致崩溃或静默错误。即使插件导出了 MyPlugin 类,主程序也无法安全地 new 它或 downcast。
- 主程序和插件必须用完全一致的编译器、标准库、ABI 选项构建,否则
dynamic_cast失败、delete崩溃、析构函数不执行 - 插件中虚函数表的内存布局与主程序不匹配,调用时跳转到非法地址
- 异常跨越 DLL 边界传播(如从插件抛出、主程序捕获)是未定义行为,Windows 下尤其危险
dlopen/LoadLibrary 后必须检查错误码,否则等于没做错误处理
加载失败时返回空句柄,但真正原因藏在系统错误码里。不查就打印 “load failed”,90% 的问题无法定位。
- Linux:
dlopen失败后立刻调dlerror();常见返回 “undefined symbol” 其实是插件依赖的另一个.so没找到,不是符号名错 - Windows:
LoadLibrary返回NULL后必须调GetLastError();ERROR_MOD_NOT_FOUND往往是相对路径 + 当前工作目录不对,不是文件不存在 - 用
DumpBin /exports plugin.dll(Windows)或objdump -T libplugin.so(Linux)确认导出符号名是否带extern "C"修饰——C++ name mangling 会让create_plugin变成?create_plugin@@YAPAVIPlugin@@XZ,dlsym肯定找不到
插件接口必须用纯 C 风格工厂函数,不能依赖 C++ 类型导出
唯一安全的跨模块入口点是 extern "C" 函数,返回 void* 或基类指针,且基类头文件必须由主程序提供、插件仅包含不实现。
- 接口头文件(如
PluginInterface.h)只含纯虚函数、无模板、无 STL 容器成员、析构函数必须为virtual且有定义(哪怕空实现) - 插件中导出函数签名必须严格匹配:
extern "C" PluginInterface* create_plugin();,不能是std::unique_ptr或引用返回 - 主程序拿到指针后,只能通过基类虚函数调用;插件内部的
new必须配对delete,且必须用插件自己的operator delete—— 所以推荐插件同时导出destroy_plugin(PluginInterface*)
插件句柄和实例生命周期必须手动配对管理
HMODULE 或 void* 句柄不是智能指针,不会自动卸载;插件对象析构也不等于库卸载。混用会引发静默崩溃或资源泄漏。
- 不要把
HMODULE存全局变量长期持有:插件卸载后句柄失效,再次GetProcAddress返回NULL,但不报错 - 每次
dlopen后应记录句柄,并在插件销毁后显式调dlclose(Linux)或FreeLibrary(Windows);注意dlclose是引用计数,多次dlopen同一路径需对应次数dlclose - 插件实例的
delete必须由插件自己的代码执行,主程序只能调用插件导出的destroy_plugin—— 因为分配和释放必须在同一个堆上
最易被忽略的一点:插件的构造函数/析构函数里不能调用主程序的函数指针,也不能持有主程序分配的内存地址(比如回调函数指针),除非双方 ABI 和调用约定(__cdecl vs __stdcall)完全一致。这种隐式耦合比头文件依赖更难排查。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











