You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

运行时动态加载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_

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 08:17:27