C++运行时加载实现纯虚接口的DLL时GetProcAddress返回空如何解决?
问题排查与解决方案
核心错误根因
你当前的实现存在3个关键错误导致GetProcAddress返回空:
- C++成员函数名字修饰问题:类成员函数会被C++编译器添加名字修饰(Name Mangling),你指定的函数名
CommandGeneratorEngine::Interface::test根本不是DLL实际导出的符号名,所以无法匹配。 - 导出对象错误:你不需要导出虚成员函数,虚函数的调用靠虚表寻址,只要拿到接口指针就能直接调用,不需要通过
GetProcAddress找成员函数地址。另外你把__declspec(dllexport)加在成员函数上的写法也不符合插件开发的规范。 - 函数指针类型不匹配:你定义的
CreatorFunction是返回void*的无参函数,但你试图查找的test是返回void的成员函数,二者类型完全不兼容,就算找到符号也会调用崩溃。
排查步骤
- 用Windows自带的
dumpbin工具查看DLL实际导出的符号,确认你要找的函数名是否存在:
执行后你会发现导出列表里根本没有dumpbin /EXPORTS 你的DLL文件路径CommandGeneratorEngine::Interface::test这个符号。
修复方案
1. DLL侧修改实现,添加C风格的工厂导出函数
插件开发的标准实现是导出工厂函数创建实例,不要导出类成员:
// 删掉成员函数的dllexport、__stdcall修饰,虚函数不需要导出 class LHFImplementationInterface : public TGComm::CommandGeneratorEngine::Interface { public: void test() override{ // 原有实现逻辑 } }; // 新增extern "C"修饰的工厂导出函数,避免名字修饰 extern "C" __declspec(dllexport) TGComm::CommandGeneratorEngine::Interface* STDCALL CreateInterfaceInstance() { return new LHFImplementationInterface(); } // 可选:新增销毁函数,避免跨模块内存释放错误 extern "C" __declspec(dllexport) void STDCALL DestroyInterfaceInstance(TGComm::CommandGeneratorEngine::Interface* ptr) { delete ptr; }
2. 主程序侧修改函数定义和加载逻辑
extern "C" { // 修正函数指针类型,和DLL导出的工厂函数匹配 typedef TGComm::CommandGeneratorEngine::Interface* (STDCALL* CreatorFunction)(); typedef void (STDCALL* DestroyFunction)(TGComm::CommandGeneratorEngine::Interface*); // 函数名和DLL导出的符号完全一致 constexpr auto CreatorFuncName{ "CreateInterfaceInstance" }; constexpr auto DestroyFuncName{ "DestroyInterfaceInstance" }; } // 加载逻辑修改 auto library = LoadLibraryExW(pluginPath.native().c_str(), nullptr, LOAD_WITH_ALTERED_SEARCH_PATH); if (library != nullptr) { auto creator = (CreatorFunction)GetProcAddress(library, CreatorFuncName); auto destroy = (DestroyFunction)GetProcAddress(library, DestroyFuncName); if (creator != nullptr && destroy != nullptr) { // 获取接口实例 TGComm::CommandGeneratorEngine::Interface* pInterface = creator(); // 直接调用虚方法,不需要找test的导出符号 pInterface->test(); // 使用完后调用销毁函数释放内存 destroy(pInterface); } else { throw std::exception("Error when loading comm plugin"); } }
额外注意事项
- 接口头文件要作为公共文件,主程序和DLL侧必须引用完全相同的头文件,否则虚表偏移不一致会导致调用崩溃。
- 不要跨模块分配/释放内存,所有DLL侧创建的对象都要由DLL侧负责销毁,避免不同运行时库的内存管理逻辑冲突。
内容的提问来源于stack exchange,提问作者Max
相关产品推荐
相关产品推荐

