必须确保工厂函数同时满足extern "c"和__declspec(dllexport)(windows)或visibility("default")加-fvisibility=hidden(linux),否则getprocaddress/dlsym返回空指针;需用dumpbin/nm验证导出符号真实存在,且抽象基类须纯虚、无非pod成员、禁用rtti/异常、crt链接方式一致。

要在 Windows 或 Linux 上让主程序成功调用插件 DLL/SO 中的工厂函数创建对象,必须确保符号能被 GetProcAddress 或 dlsym 真实查到——不是加载失败,而是函数名被修饰或根本未导出,导致句柄拿到后调用返回空指针,且 GetLastError() 为 0,排查无从下手。
Windows 下导出工厂函数:extern "C" 和 __declspec(dllexport) 缺一不可
第一步:在插件工程的头文件(如 plugin_interface.h)中,用 extern "C" 块包裹导出声明:
【必须用 extern "C" 块整体包裹,不能只加在单个函数前】
第二步:每个导出函数都必须同时带 __declspec(dllexport),例如:
extern "C" {<br> __declspec(dllexport) IPlugin* create_plugin();<br> __declspec(dllexport) void destroy_plugin(IPlugin* p);<br>}
第三步:确保该头文件被插件源文件(如 plugin_impl.cpp)包含,并在其中实现这两个函数。不要在头文件里写实现体,也不要漏掉任何一处 __declspec(dllexport)。
第四步:编译插件时,确认项目属性中“配置类型”设为“动态库 (.dll)”,而非应用程序。若误设为 Application,CRT 初始化逻辑异常,new 可能直接崩溃。
Linux 下导出工厂函数:visibility("default") + -fvisibility=hidden 编译选项
方法一:使用宏统一控制可见性(推荐)
在共享头文件中定义:
#define PLUGIN_API __attribute__((visibility("default")))
然后声明:
extern "C" {<br> PLUGIN_API IPlugin* create_plugin();<br> PLUGIN_API void destroy_plugin(IPlugin* p);<br>}
方法二:编译时显式加 -fvisibility=hidden 参数
g++ -fPIC -shared -fvisibility=hidden plugin_impl.cpp -o libplugin.so
【不加 -fvisibility=hidden 就算写了 visibility("default"),也可能因其他符号干扰导致行为不可靠】
这一步操作起来很简单,直接把编译命令改掉就行;但漏掉它,nm -D libplugin.so 就完全看不到 create_plugin,dlsym 必定返回 nullptr。
验证导出符号是否真实存在
Windows:
用 Visual Studio 自带的 dumpbin 工具检查导出表:
dumpbin /exports processor_plugin.dll | findstr create_plugin
输出中应看到干净的 create_plugin,而不是带问号和 @ 符号的乱码(如 ?create_plugin@@YAPAVIPlugin@@XZ)。
Linux:
用 nm 命令查看动态符号:
nm -D libplugin.so | grep create_plugin
如果无输出,说明符号未导出;若有输出但含 _Z 开头(如 _Z14create_pluginv),说明 extern "C" 缺失或 visibility 未生效。
注意:此验证必须在插件编译完成后立即执行,不能跳过——很多团队直到运行时报 nullptr 才回头查,浪费数小时。
接口基类 ABI 稳定性强制约束
第一步:抽象基类必须是纯虚类,所有函数以 = 0 结尾,禁止默认参数、内联实现、构造函数体。
第二步:删除所有非 POD 成员。把 std::string m_name; 改成 char m_name[64]; 或 const char* name() const;。
第三步:禁用虚继承、模板成员、RTTI、异常处理。这些特性会改变 vtable 布局,一旦主程序与插件用不同 MSVC 版本编译,调用 run() 就可能跳到非法地址。
第四步:确保主程序与插件使用相同 CRT 链接方式。若主程序用 /MT(静态链接),插件也必须用 /MT;否则 new/delete 跨 DLL 释放内存会触发访问冲突。
主程序安全调用流程
方法一:Windows 显式加载标准路径
调用 LoadLibrary 加载 DLL 后,立刻检查返回值和 GetLastError:
HMODULE h = LoadLibrary(L"processor_plugin.dll");<br>if (!h) { DWORD err = GetLastError(); /* err == 126 表示路径错或缺依赖 */ }
方法二:获取函数指针后做双重空值检查
先取 create_plugin 地址,再调用它:
auto create_func = (IPlugin*(*)())GetProcAddress(h, "create_plugin");<br>if (create_func) { IPlugin* p = create_func(); if (p) p->run(); }
方法三:RAII 封装裸指针生命周期
定义 PluginPtr 类,在构造时调 create_plugin,在析构时调 destroy_plugin;绝不使用 std::shared_ptr
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











