运行时动态加载B.dll时出现"unresolved external symbol"错误
解决动态加载B.dll时A.exe的未解析外部符号问题
核心原因
你遇到的unresolved external symbol错误,本质是:虽然你打算运行时动态加载B.dll,但编译A.exe时,代码里直接引用了B.dll导出的Machine类(比如调用构造函数、静态成员sing或静态方法),编译器需要这些符号的定义,而你没链接对应的导入库(比如B.lib),所以链接器抛出了错误。
下面提供两种针对性的解决方案,适配不同的项目需求:
方案1:纯运行时动态加载(完全解耦,不依赖B的导入库)
这种方式通过Windows原生API实现动态加载,编译阶段A完全不依赖B,适合需要彻底解耦的场景。
步骤1:修改B的代码,导出C风格接口
C++类直接导出会有名字 mangling(符号名被编译器修改),不好通过GetProcAddress获取,所以先给B添加C风格的导出函数:
// src/b/b.cpp #include "b.h" Machine* Machine::sing = nullptr; Machine::Machine() : u(0) {} // 导出C风格的实例创建/销毁函数 extern "C" __declspec(dllexport) Machine* CreateMachine() { if (!Machine::sing) { Machine::sing = new Machine(); } return Machine::sing; } extern "C" __declspec(dllexport) void DestroyMachine() { delete Machine::sing; Machine::sing = nullptr; } // 如果需要调用Machine的成员函数,也可以导出对应的C风格包装函数 extern "C" __declspec(dllexport) int GetMachineU(Machine* inst) { return inst->u; }
步骤2:修改A的代码,通过函数指针间接调用
不要直接使用Machine类,而是动态加载DLL后通过函数指针操作:
// src/a/a1.cpp #include <windows.h> #include <iostream> // 定义对应导出函数的指针类型 typedef Machine* (*CreateMachineFunc)(); typedef void (*DestroyMachineFunc)(); typedef int (*GetMachineUFunc)(Machine*); int main() { // 加载B.dll,注意路径要和A.exe的运行位置匹配 HMODULE hDll = LoadLibrary(L"../bin/B.dll"); if (!hDll) { std::cerr << "加载B.dll失败,错误码:" << GetLastError() << std::endl; return 1; } // 获取导出函数地址 CreateMachineFunc createMachine = (CreateMachineFunc)GetProcAddress(hDll, "CreateMachine"); DestroyMachineFunc destroyMachine = (DestroyMachineFunc)GetProcAddress(hDll, "DestroyMachine"); GetMachineUFunc getMachineU = (GetMachineUFunc)GetProcAddress(hDll, "GetMachineU"); if (!createMachine || !destroyMachine || !getMachineU) { std::cerr << "获取导出函数地址失败" << std::endl; FreeLibrary(hDll); return 1; } // 通过函数指针使用Machine类 Machine* machine = createMachine(); std::cout << "Machine的u值:" << getMachineU(machine) << std::endl; destroyMachine(); FreeLibrary(hDll); return 0; }
步骤3:调整CMake配置
- B的CMakeLists.txt:
# src/b/CMakeLists.txt add_library(B SHARED b.cpp b.h ) # 设置DLL和导入库的输出路径 set_target_properties(B PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib )
- A的CMakeLists.txt(无需链接B):
# src/a/CMakeLists.txt add_executable(A a1.cpp) # 让A能找到B的头文件(仅用于定义Machine指针类型) target_include_directories(A PRIVATE ${CMAKE_SOURCE_DIR}/src/b) set_target_properties(A PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin )
方案2:编译时链接导入库(保留直接使用类的代码)
如果你想继续直接在A中使用Machine类,可以让A链接B的导入库,这样链接器能找到符号,运行时再确保B.dll在可搜索路径即可。
步骤1:修改B的头文件,添加导出/导入宏
// src/b/b.h #ifdef B_EXPORTS #define B_API __declspec(dllexport) #else #define B_API __declspec(dllimport) #endif class B_API Machine { int u; static Machine* sing; public: Machine(); static Machine* getInstance(); // ... 其他成员函数 };
步骤2:调整B的CMakeLists.txt
# src/b/CMakeLists.txt add_library(B SHARED b.cpp b.h ) # 定义B_EXPORTS宏,让编译B时触发dllexport target_compile_definitions(B PRIVATE B_EXPORTS) set_target_properties(B PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib )
步骤3:调整A的CMakeLists.txt,链接B的导入库
# src/a/CMakeLists.txt add_executable(A a1.cpp) # 链接B的导入库 target_link_libraries(A PRIVATE B) # 让A能找到B的头文件 target_include_directories(A PRIVATE ${CMAKE_SOURCE_DIR}/src/b) set_target_properties(A PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin )
这种方式下,A编译时会链接B的.lib导入库,运行时只要B.dll和A.exe在同一目录(或系统PATH中),就能正常加载运行。
额外注意事项
- 如果使用
export.def文件导出符号,要确保符号名称和编译后的实际符号一致(C++类需要注意名字 mangling,更推荐用宏+__declspec的方式)。 - 运行时务必保证B.dll在A.exe的可搜索路径中,否则会提示"找不到B.dll"。
内容的提问来源于stack exchange,提问作者yO_
相关产品推荐
相关产品推荐

