C++26静态反射不能用于插件系统,因为它是纯编译期机制,生成的元信息不进入二进制符号表,动态库加载后完全不可见;插件系统依赖运行时类型查找与实例化,而reflect(Person)仅在编译该插件时有效,主程序无法获取其类型名、字段等信息,GetProcAddress也查不到consteval生成的反射数据。

纯虚接口 + extern "C" 导出函数是唯一能跨编译器、跨 STL 版本稳定工作的方案;C++/CLI 反射在 VS 2017+ 已不可用,reflect 关键字属于 C++26 尚未落地,别在生产环境依赖它。
为什么不能直接用 C++26 静态反射做插件系统
C++26 的 reflect 是编译期机制,生成的元信息不进入二进制符号表,动态库加载后完全不可见。插件系统本质是运行时行为——主程序必须在 dlopen/LoadLibrary 后拿到可用对象,而静态反射连 create_plugin 这种入口函数都导不出。
-
reflect(Person)只在编译该插件时有效,主程序根本看不到插件里定义了什么类 - 没有运行时类型名字符串,无法按名称查找并实例化类型(比如
Activator.CreateInstance("MyPlugin::AIHandler")) - 所有反射结果是
consteval常量,无法存入 DLL 的导出符号段,GetProcAddress查不到
Windows 下 LoadLibrary 返回 NULL 的真实原因和排查步骤
90% 的失败不是代码写错,而是依赖链断裂或路径污染。别急着改 create_plugin 实现,先确认 DLL 能否独立加载。
- 用
Dependencies.exe(非旧版 Dependency Walker)打开插件 DLL,看红色标记的缺失模块——尤其是MSVCP140.dll、VCRUNTIME140_1.dll这类运行时 - 主程序用
/MD编译,插件却用/MDd?调试版运行时不会随发布包分发,必然失败 - 调用
LoadLibrary前加一句SetDllDirectory(L"."),强制只从插件同目录搜索依赖,避免系统目录里的旧版 DLL 干扰 - 传给
LoadLibrary的路径必须是绝对路径:GetFullPathName(L"plugins\ai.dll", ...)再传入,相对路径在工作目录变更后必崩
插件热重载时全局状态残留的典型表现和修复方式
卸载再加载插件后,新插件初始化失败、日志重复打印、第三方 SDK 报 “already initialized” 错误——基本可以断定是静态变量没清理。
立即学习“C++免费学习笔记(深入)”;
- 插件 DLL 内禁止使用
static std::map<int std::string> g_cache</int>;改用PluginInterface实例的成员变量 - 所有第三方库(如
spdlog::default_logger()、FMOD::System::create())必须在shutdown()中显式释放,不能靠 DLL 卸载自动析构 - 主程序在调用
FreeLibrary前,必须先调用插件的shutdown()虚函数——这个调用不能漏,也不能放在FreeLibrary之后 - 若插件内部用了单例模式(如
MyConfig::instance()),必须提供reset_for_reload()并在shutdown()中调用
如何让插件支持“按名创建”而不暴露具体类名
主程序不该知道插件里有 RenderPipelinePlugin 或 PhysicsModPlugin,只应通过字符串标识符获取实例。这靠工厂函数 + 插件内注册表实现,不是靠反射。
- 插件 DLL 导出一个
extern "C" const char** get_supported_types(),返回{"renderer", "physics", "audio"}这样的 C 字符串数组 - 主程序调用
create_plugin(api_version)得到基类指针后,立即调用其initialize(),插件在此函数中把自身类型注册进全局工厂映射表(如g_factory_map["renderer"] = this) - 主程序后续调用
PluginFactory::create("renderer")时,实际是查表返回已存在的插件实例,而非新建——避免多次初始化冲突 - 注意:这个映射表必须由主程序持有(如
std::unordered_map<:string plugininterface></:string>),插件 DLL 内绝不维护跨 DLL 边界的容器
真正难的不是写 create_plugin,而是确保插件生命周期和主程序内存管理边界完全对齐——new 在哪边,delete 就必须在哪边;日志句柄、线程池、GPU 上下文,全得在 shutdown() 里归零。漏掉任意一项,热重载就变成玄学崩溃。


















