插件接口必须用 extern "C" 导出函数,因 C++ name mangling 会导致 dlsym/GetProcAddress 找不到符号;IPlugin 需为纯虚类且无异常说明符;dlopen/dlsym 后须检查返回值与 dlerror;create_plugin/destroy_plugin 必须成对使用同一堆;Windows 下需 __declspec(dllexport) 且禁用延迟加载。

插件接口必须用 extern "C" 导出函数
动态加载的插件本质是共享库(.so 或 .dll),而 C++ 编译器会对函数名做 name mangling,导致主程序用 dlsym(Linux)或 GetProcAddress(Windows)找不到符号。不加 extern "C" 是最常见、最隐蔽的注册失败原因。
正确做法是:在插件源码中显式导出两个 C 风格函数:
extern "C" {
// 插件入口,返回实现 IPlugin 接口的对象指针
void* create_plugin() {
return new MyPluginImpl();
}
<pre class="brush:php;toolbar:false;">// 插件析构,由主程序调用释放
void destroy_plugin(void* p) {
delete static_cast<iplugin>(p);
}</iplugin>}
注意:IPlugin 必须是纯虚基类,且所有虚函数声明中不含异常说明符(如 noexcept)、不依赖模板实例化,否则跨模块 ABI 不稳定。
主程序用 dlopen/dlsym 加载时要检查 RTLD_NOW 和错误码
Linux 下用 dlopen 加载失败往往静默返回 nullptr,但真实原因藏在 dlerror() 里;Windows 下 LoadLibrary 失败后需用 GetLastError() 辅助定位。忽略这些会导致“插件没加载”却查不出原因。
关键操作要点:
-
dlopen(path, RTLD_NOW | RTLD_LOCAL)比RTLD_LAZY更安全——它在加载时就解析所有符号,避免运行时dlsym找不到函数的诡异崩溃 - 每次调用
dlopen、dlsym后必须立即检查返回值,不能假设成功 - 路径传入前确保是绝对路径,相对路径依赖
LD_LIBRARY_PATH或当前工作目录,极易出错 - 若插件依赖其他共享库,需提前用
ldd plugin.so检查缺失项
插件对象生命周期必须由主程序统一管理
插件对象在共享库中构造、在主程序中析构,如果两边使用不同堆(例如插件用静态链接的 libc,主程序用动态链接),delete 就会触发段错误。这不是内存泄漏,而是堆不匹配的 UB。
解决方案只有一条铁律:
- 插件只提供
create_plugin()和destroy_plugin(void*)两个函数,且二者必须使用同一套内存分配器 - 推荐让插件内部用
new/delete,主程序调用destroy_plugin释放——这样分配和释放都在插件自己的上下文中完成 - 禁止在插件中返回栈对象指针、智能指针(如
std::shared_ptr),它们会隐式绑定主程序的类型信息和删除器
Windows 下 DLL 要显式导出符号并禁用延迟加载
MSVC 默认不导出任何符号,即使写了 extern "C",也得加 __declspec(dllexport),否则 GetProcAddress 一定返回 NULL。而且 Windows 的延迟加载(/DELAYLOAD)会干扰插件热加载逻辑,必须关掉。
实操步骤:
- 插件工程属性中设置「配置属性 → 常规 → 配置类型」为「动态库(.dll)」
- 头文件中定义宏:
#define PLUGIN_API __declspec(dllexport),然后写extern "C" PLUGIN_API void* create_plugin(); - 主程序链接时不要加插件的
.lib,完全走LoadLibrary+GetProcAddress运行时绑定 - 项目属性「配置属性 → 链接器 → 延迟加载 → 延迟加载的 DLL」留空,禁用该特性
跨平台抽象层可以封装 dlopen/LoadLibrary 差异,但底层逻辑逃不开这两套规则。最容易被忽略的是插件与主程序的 C 运行时(CRT)版本一致性——Debug/Release、MT/MD 必须严格匹配,否则连 malloc 都可能崩。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











