C++无运行时反射,所谓“反射工厂”实为extern "C"工厂函数+手动接口映射;std::reflexpr仅编译期有效,无法跨模块发现DLL中未声明类型,插件加载必须依赖显式导出符号与ABI一致的纯虚接口。

没有“反射工厂”这回事——C++ 标准不支持运行时自动发现 DLL 中的类,所谓“反射”只是用 extern "C" 工厂函数 + 手动接口映射撑起来的架子。
为什么 std::reflexpr 或任何 C++26 反射机制无法加载插件
std::reflexpr 是编译期特性,只对当前翻译单元中已声明、已定义的类型起作用。插件 DLL 里的 MyPlugin 类,主程序头文件里没写过、编译器根本没见过,std::reflexpr(MyPlugin) 编译都过不了,更别说运行时从 .so 里“扫描”出来。
常见错误现象:
- 写了宏自动注册插件类,编译通过,但
dlsym(handle, "create_plugin")返回nullptr - 强行拿到指针后调
plugin->run(),虚函数跳转到非法地址,段错误或静默崩溃 - 用
dynamic_cast或typeid做类型识别,结果总是失败
根本原因:反射不跨模块,类型信息不出 .so 边界。
立即学习“C++免费学习笔记(深入)”;
必须用 extern "C" 导出工厂函数,且签名要严丝合缝
C++ 编译器会对函数名做 name mangling,比如 create_plugin() 在 MSVC 下可能变成 ?create_plugin@@YAPAVIPlugin@@XZ,而 GetProcAddress 和 dlsym 只认原始符号名 "create_plugin"。
正确写法(Windows):
extern "C" __declspec(dllexport) IPlugin* create_plugin_0();
正确写法(Linux):
extern "C" __attribute__((visibility("default"))) IPlugin* create_plugin_0();
关键点:
- 导出函数必须是
extern "C",否则符号找不到 - 返回类型只能是
void*或抽象基类指针(如IPlugin*),不能是具体类 - 函数签名(参数个数、类型、调用约定)在主程序 typedef 中必须完全一致,否则栈被破坏
- 不要依赖
#pragma comment(linker, "/EXPORT:")—— 它不影响运行时符号可见性
接口基类必须是纯虚、无数据成员、ABI 稳定
跨 DLL 边界的虚函数调用,本质是通过 vtable 跳转。一旦插件和主程序的 IPlugin 布局不一致(比如虚表偏移、RTTI 地址、析构函数位置不同),调用立即失效。
安全接口定义示例:
struct IPlugin {
virtual int initialize() = 0;
virtual int process(const char* input) = 0;
virtual void shutdown() = 0;
virtual ~IPlugin() = default;
};
禁止以下操作:
- 在接口中加数据成员(
int m_state;) - 继承带虚基类或多重虚继承
- 使用
std::string、std::vector等 STL 类型作为参数或返回值 - 抛出异常(异常跨越 DLL 边界是未定义行为)
- 让插件 new、主程序 delete —— 必须统一 new/delete ABI(推荐静态链接 libc++/MSVCRT)
dlopen / LoadLibrary 后不查错误码,等于没写错误处理
加载失败不是“文件不存在”这么简单。Linux 下 dlopen 返回 nullptr,真实原因藏在 dlerror();Windows 下 LoadLibrary 返回 NULL,得立刻调 GetLastError()。
典型错误与应对:
-
ERROR_MOD_NOT_FOUND:路径是相对路径,但当前工作目录不对 → 改用绝对路径,或调SetDllDirectory() -
ERROR_PROC_NOT_FOUND:函数名拼错,或没加extern "C"→ 用DumpBin /exports plugin.dll(Windows)或objdump -T libplugin.so(Linux)确认导出符号 -
"undefined symbol: xxx"(Linux):插件依赖另一个.so没找到 → 用ldd libplugin.so查依赖链
别把 HMODULE 或 void* 句柄存全局变量长期持有——DLL 卸载后指针立刻失效,再次 GetProcAddress 不报错,但返回 nullptr,后续调用直接崩溃。
最常被忽略的一点:插件和主程序必须用**完全一致的编译器版本、STL 实现、构建选项(包括 _GLIBCXX_DEBUG、/MD vs /MT)**,否则哪怕接口一字不差,sizeof(IPlugin) 都可能不同,vtable 偏移全错。


















