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

如何通过LoadLibrary动态加载C++类DLL并查找成员函数,同时支持DLL热更新?

如何通过LoadLibrary动态加载C++类DLL并查找成员函数,同时支持DLL热更新?

嘿,我正好折腾过类似的场景,给你梳理下具体的实现思路和实操步骤,应该能帮你搞定问题~

一、先补补普通函数的细节(你可能已经懂,但做个铺垫)

普通非成员函数的动态加载其实很直接,但要注意避免C++名字修饰——你得用extern "C"来导出函数,这样GetProcAddress才能精准找到函数名。比如DLL端这么写:

// DLL代码
extern "C" __declspec(dllexport) void MyPlainFunc() {
    // 你的函数逻辑
}

主程序里就用LoadLibrary拿到句柄,再用GetProcAddress把函数地址转成对应指针就行,没啥坑。

二、类成员函数的核心问题与解决办法

C++的非静态成员函数没法直接用GetProcAddress捞到,原因有俩:一是编译器会给成员函数做“名字修饰”(不同编译器规则还不一样),二是非静态成员函数必须绑定类实例(隐含this指针)。业界最常用的解决方案是抽象接口+工厂函数,相当于用C风格的函数做中间层,绕开成员函数的导出问题:

步骤1:定义公共抽象接口(主程序和DLL共享这个头文件)

先写一个纯虚基类,把你要调用的成员函数都声明成纯虚函数,一定要加虚析构,不然会内存泄漏:

// IMyClass.h(主程序和DLL都要包含)
class IMyClass {
public:
    virtual ~IMyClass() = default;
    virtual void DoSomething(int param) = 0; // 你要调用的成员函数
    virtual int GetResult() = 0;
};

步骤2:DLL端实现接口并导出工厂函数

在DLL里写一个继承自抽象接口的具体类,然后导出两个C风格的函数:一个用来创建类实例,一个用来销毁实例(同样用extern "C"避免名字修饰):

// DLL的实现文件
#include "IMyClass.h"

// 具体的类实现
class MyClassImpl : public IMyClass {
public:
    void DoSomething(int param) override {
        // 这里写你的业务逻辑
    }
    int GetResult() override {
        return 666;
    }
};

// 导出创建实例的工厂函数
extern "C" __declspec(dllexport) IMyClass* CreateMyClass() {
    return new MyClassImpl();
}

// 导出销毁实例的函数(也可以靠虚析构,但显式销毁更安全)
extern "C" __declspec(dllexport) void DestroyMyClass(IMyClass* obj) {
    delete obj;
}

步骤3:主程序动态加载并调用成员函数

主程序里通过加载DLL拿到工厂函数的地址,创建实例后就可以像调用普通类对象一样调用成员函数了:

// 主程序代码
#include "IMyClass.h"
#include <windows.h>
#include <iostream>

int main() {
    HMODULE hDll = LoadLibrary(L"MyClassDll.dll");
    if (!hDll) {
        std::cerr << "DLL加载失败!" << std::endl;
        return 1;
    }

    // 定义函数指针类型
    using CreateFunc = IMyClass*(*)();
    using DestroyFunc = void(*)(IMyClass*);

    // 获取工厂函数地址
    CreateFunc create = reinterpret_cast<CreateFunc>(GetProcAddress(hDll, "CreateMyClass"));
    DestroyFunc destroy = reinterpret_cast<DestroyFunc>(GetProcAddress(hDll, "DestroyMyClass"));

    if (!create || !destroy) {
        std::cerr << "获取函数地址失败!" << std::endl;
        FreeLibrary(hDll);
        return 1;
    }

    // 创建实例并调用成员函数
    IMyClass* obj = create();
    obj->DoSomething(123);
    std::cout << "结果:" << obj->GetResult() << std::endl;

    // 销毁实例+释放DLL
    destroy(obj);
    FreeLibrary(hDll);
    return 0;
}

三、实现DLL热更新的关键要点

热更新的核心是不重启主程序,无缝切换旧DLL到新DLL,这里要注意几个关键细节:

1. 确保旧DLL能被彻底卸载

  • 必须先销毁所有从旧DLL创建的实例(调用DestroyMyClass),不能留任何引用
  • 调用FreeLibrary释放旧DLL句柄,如果之前多次LoadLibrary,要对应多次FreeLibrary,确保引用计数归0

2. 热更新的完整流程示例

可以在主程序里维护全局的DLL句柄、函数指针和实例,写一个专门的热更新函数:

// 主程序全局变量(也可以封装成类)
HMODULE g_hCurrentDll = nullptr;
CreateFunc g_CreateFunc = nullptr;
DestroyFunc g_DestroyFunc = nullptr;
IMyClass* g_CurrentObj = nullptr;

// 热更新函数
void HotUpdate(const wchar_t* newDllPath) {
    // 1. 销毁旧实例
    if (g_CurrentObj && g_DestroyFunc) {
        g_DestroyFunc(g_CurrentObj);
        g_CurrentObj = nullptr;
    }

    // 2. 卸载旧DLL
    if (g_hCurrentDll) {
        FreeLibrary(g_hCurrentDll);
        g_hCurrentDll = nullptr;
    }

    // 3. 加载新DLL
    g_hCurrentDll = LoadLibrary(newDllPath);
    if (!g_hCurrentDll) {
        std::cerr << "加载新DLL失败!" << std::endl;
        return;
    }

    // 4. 获取新的工厂函数地址
    g_CreateFunc = reinterpret_cast<CreateFunc>(GetProcAddress(g_hCurrentDll, "CreateMyClass"));
    g_DestroyFunc = reinterpret_cast<DestroyFunc>(GetProcAddress(g_hCurrentDll, "DestroyMyClass"));

    if (!g_CreateFunc || !g_DestroyFunc) {
        std::cerr << "获取新DLL函数地址失败!" << std::endl;
        FreeLibrary(g_hCurrentDll);
        g_hCurrentDll = nullptr;
        return;
    }

    // 5. 创建新实例
    g_CurrentObj = g_CreateFunc();
    std::cout << "DLL热更新成功!" << std::endl;
}

3. 额外注意事项

  • 线程安全:如果主程序是多线程的,热更新时一定要加锁,避免其他线程在更新过程中访问旧实例或DLL资源
  • 接口兼容性:新DLL的抽象接口必须和旧DLL完全一致,不能改函数签名,否则主程序调用会崩溃
  • 触发时机:可以把DLL的版本号写在配置文件里,主程序定时检查配置,发现版本变化就自动触发热更新

备注:内容来源于stack exchange,提问作者chacebai

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.13 16:58:19