无法直接用LoadLibrary加载导出C++类的DLL,必须通过extern "C"导出工厂函数(如CreatePlugin/DestroyPlugin),主程序仅依赖纯虚接口头文件操作对象,严禁暴露类布局、虚表或RTTI,所有数据传递需跨ABI兼容(如const char*代替std::string)。

如何用LoadLibrary和GetProcAddress安全加载DLL中的C++类
直接用LoadLibrary加载导出C++类的DLL会失败——主程序看不到类布局、虚表、RTTI,也无法调用构造/析构函数。必须绕过C++ ABI限制,只暴露纯C接口。
正确做法是:在DLL中定义工厂函数(C链接),返回抽象接口指针;主程序只通过头文件声明的纯虚基类操作对象。
- DLL导出函数必须用
extern "C" __declspec(dllexport),禁用名字修饰 - 接口类必须无数据成员、无虚析构以外的虚函数(避免vptr偏移差异)
- 构造/析构逻辑全由DLL内部工厂函数
CreatePlugin和DestroyPlugin接管 - 主程序头文件里只声明接口,不包含DLL源码中的实现细节
插件接口头文件怎么写才跨编译器兼容
接口头文件是主程序和所有插件的唯一契约,稍有不慎就因ABI不一致崩溃。核心原则:一切可序列化、无模板、无STL容器、无异常抛出。
例如,不能写std::string或std::vector<int></int>作为参数;字符串统一用const char* + 长度,数组传const int* + size_t count。
- 所有函数用
extern "C"声明,避免C++名字修饰导致GetProcAddress找不到 - 接口类继承自空基类,显式声明虚析构:
virtual ~IPlugin() = default; - 禁止内联函数、静态成员变量、非POD类型成员(如
std::mutex) - 版本号建议硬编码在接口中,比如
static constexpr int VERSION = 1;,便于运行时校验
FreeLibrary之前为什么必须先调用插件的DestroyPlugin
因为插件DLL内部可能持有堆内存、文件句柄、线程或全局状态,这些资源通常在DLL自己的new/malloc或WinAPI调用中分配。如果只调用FreeLibrary,这些资源不会被自动释放,造成泄漏甚至死锁。
-
DestroyPlugin必须在FreeLibrary前调用,且保证该函数本身不依赖DLL后续被卸载的代码路径 - 插件DLL的
DLL_PROCESS_DETACH处理中,不应执行任何清理逻辑——此时C运行时可能已部分销毁 - 主程序应检查
DestroyPlugin返回值,若失败则记录日志但仍继续FreeLibrary,避免卡住 - 多次调用
DestroyPlugin必须幂等,防止误操作引发二次析构
调试时GetProcAddress返回nullptr的常见原因
不是函数名拼错这么简单。Windows下符号查找失败往往隐藏着更底层的不匹配。
- 函数确实没加
__declspec(dllexport),或者导出时用了.def文件但漏写了函数名 - 主程序工程设置为Unicode,而DLL导出的是ANSI版函数名(反之亦然),查
dumpbin /exports your.dll确认实际导出符号 - 函数签名不一致:比如DLL里是
int func(float),主程序声明成int func(double),GetProcAddress能成功但调用时栈错位 - 目标DLL依赖其他DLL(如VCRT),但未部署对应版本,
LoadLibrary静默失败,后续GetProcAddress必然nullptr
最稳妥的验证方式:用Dependency Walker或dumpbin /imports确认依赖链完整,再用dumpbin /exports核对符号名和调用约定(@4后缀代表__stdcall)。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











