安全插件加载需用extern "C"导出C风格工厂函数,配合纯虚接口契约;接口禁数据成员和STL类型,内存管理与ABI须一致;dlopen/LoadLibrary失败须查错误码;热重载需清理静态变量与SDK资源。

dlsym / GetProcAddress 拿到的函数指针,不能直接 static_cast 成 C++ 类指针去调用虚函数——这是运行时崩溃最常见原因。 真正安全的插件加载,不靠“反射”自动发现类型,而靠显式导出的 C 风格工厂函数 + 主程序提供的纯虚接口契约。
为什么 extern "C" 是硬性要求,不是可选项
Windows 的 GetProcAddress 和 Linux 的 dlsym 只认原始符号名。C++ 编译器会对 create_plugin() 做 name mangling,比如变成 ?create_plugin@@YAPAVIPlugin@@XZ 或 _Z14create_pluginv,字符串字面量对不上就必然返回 NULL。
- 必须写成
extern "C" __declspec(dllexport) IPlugin* create_plugin();(Windows)或extern "C" __attribute__((visibility("default"))) IPlugin* create_plugin();(Linux) - 别依赖
#pragma comment(linker, "/EXPORT:"):它只影响 .lib 生成,不影响运行时符号可见性 - 用
DumpBin /exports plugin.dll(Windows)或nm -D libplugin.so(Linux)确认导出表里确实是create_plugin,而不是一长串乱码
IPlugin 接口怎么定义才不会跨 DLL 崩溃
接口不是越“面向对象”越可靠,而是越接近 C 风格越稳。虚表布局、sizeof、RTTI 地址在不同编译单元中极易错位。
- 基类必须是纯虚类:
struct IPlugin { virtual ~IPlugin() = default; virtual void run() = 0; };,禁止任何数据成员、禁止虚继承、禁止非平凡构造/析构 - 工厂函数返回
IPlugin*,但内存由插件分配、主程序delete—— 必须确保 new/delete ABI 一致(推荐主程序和插件都静态链接 libc++ 或 MSVCRT) - 禁止在接口中使用
std::string、std::vector、异常、dynamic_cast;字符串统一用const char*,所有权规则写死(如“插件分配,主程序调用free()”)
dlopen / LoadLibrary 失败,90% 不是代码问题,而是环境链断裂
返回 NULL 只是表象,真正原因藏在系统错误码里。不查就重试,只会反复失败。
立即学习“C++免费学习笔记(深入)”;
- Linux 下立刻调
dlerror():返回"undefined symbol"很可能不是函数名错,而是插件依赖的另一个.so没找到 - Windows 下立刻调
GetLastError():ERROR_MOD_NOT_FOUND多半是相对路径 + 当前工作目录不对;ERROR_PROC_NOT_FOUND是没加extern "C";ERROR_DLL_NOT_FOUND是缺运行时(如vcruntime140.dll),用Dependencies.exe查依赖树 - 路径务必用绝对路径:
getcwd()打印当前目录,再拼接插件路径;Windows 下可先调SetDllDirectory(L".")锁定搜索范围
插件热重载后行为异常?大概率是静态变量或第三方 SDK 没清理
卸载 DLL 后,里面定义的 static 变量、全局 logger、音频引擎实例等并不会自动释放。再次加载时,初始化逻辑可能跳过,或重复注册导致冲突。
- 插件内部禁用
static std::map<int std::string> g_cache;</int>,改用IPlugin实例的成员变量 - 所有第三方 SDK(如
spdlog::default_logger()、FMOD::System::create())必须提供配套的shutdown()函数,并在主程序调用FreeLibrary/dlclose前显式调用 -
HMODULE或void*句柄不要存全局变量长期持有——DLL 卸载后该句柄立即失效,后续GetProcAddress返回NULL却不报错,容易引发静默崩溃
最易被忽略的一点:所谓“反射式架构”,从来不是让 C++ 自动发现类型,而是把类型发现逻辑从编译期挪到运行时——靠字符串比对 plugin_type_name()、靠手动注册表维护 create_v1/create_v2 映射、靠版本号字段做 ABI 兼容校验。这些都得你亲手写,没有捷径。


















