c++26反射不能用于插件系统,因其仅为编译期静态机制,元信息不进入dll符号表,getprocaddress/dlsym无法获取;跨模块时类型名、字段等完全不可见,必须用extern "c"导出纯虚接口工厂函数实现稳定插件加载。

不能用 C++26 reflect 做插件系统的运行时反射,它编译期就“蒸发”了,DLL 加载后主程序根本看不到类型名、字段或构造函数。
为什么 reflect(Person) 在插件里毫无意义
静态反射生成的元信息不进符号表,GetProcAddress 查不到,dlsym 返回 nullptr;主程序连 "Person" 这个字符串都没有,更别说按名创建实例。插件系统本质是运行时行为——你得在 LoadLibrary 之后还能问“你提供哪些类?”,而 consteval 函数返回值连地址都没有,根本没法导出。
- 所有反射数据只在插件编译单元内有效,跨 DLL 边界即失效
- 没有类型名字符串,无法支持类似
create_instance("AIHandler")的通用工厂调用 - C++26 标准尚未落地,VS/Clang/GCC 当前均无完整实现,生产环境禁用
extern "C" + 纯虚接口才是唯一稳定路径
必须放弃“自动发现类”的幻想,改用显式导出 C 风格函数作为插件入口,再由该函数返回继承自统一基类的实例。这是跨编译器、跨 STL 版本、跨调试/发布配置的唯一可行方案。
- 插件 DLL 中必须定义
extern "C" PluginInterface* create_plugin(),不能是create_plugin<t>()</t>或重载函数 -
PluginInterface必须是纯虚类,析构函数带= default且声明为virtual - 主程序调用
GetProcAddress(h, "create_plugin")后,直接转型为函数指针并调用,不经过任何模板或 decltype 推导 - 禁止在接口中使用
std::string、std::vector等依赖 STL 实现细节的类型,传参/返回一律用const char*或 POD 结构体
插件热加载时 FreeLibrary 失败的典型原因
不是 FreeLibrary 调用失败,而是卸载后全局状态残留导致下次加载崩溃——比如日志器重复初始化、音频引擎报 “already running”、缓存 map 未清空。
- DLL 内禁用全局
static std::map、static spdlog::logger等单例,改用PluginInterface实例成员变量 - 所有第三方 SDK(如 FMOD、OpenSSL、libcurl)必须在
PluginInterface::cleanup()中显式释放,不能依赖 DLL 卸载时的静态析构顺序 - 主程序调用
FreeLibrary前,确保所有插件对象已 delete,且无线程仍在访问其内存 - Windows 下建议调用
SetDllDirectory(L".")并传绝对路径给LoadLibrary,避免依赖搜索路径污染
工厂注册表如何避免多 TU 冲突与初始化顺序问题
用函数作用域内的静态局部变量封装注册表,而不是全局变量——这样能保证首次访问时才构造,且线程安全(C++11 起)。
- 注册宏要加断言:
assert(registry.find(ext) == registry.end()),防止两个插件同时注册"json" - 注册逻辑必须放在每个解析器类的头文件末尾(如
JsonParser.h),而非源文件,确保链接时必被拉入 - 注册用的 lambda 必须捕获完整构造逻辑,不要写成
[] { return std::make_unique<jsonparser>(); }</jsonparser>这种依赖 ADL 或隐式转换的形式 - 若插件需支持多实例(如多个 AIHandler),工厂函数应返回新对象,而非单例指针
最易被忽略的一点:插件 DLL 的 CRT 链接方式必须和主程序完全一致——/MD 对 /MD,/MTd 对 /MTd,混用会导致堆分配错乱、new/delete 不匹配、随机崩溃。这不是设计问题,是 ABI 层面的硬约束。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











