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

引擎DLL如何跨动态库分配未知的CBase继承类实例

引擎DLL如何跨动态库分配未知的CBase继承类实例

我完全懂你的需求——引擎要牢牢掌控实体的内存分配权,但又没法提前知道Game DLL里会定义哪些派生自CBase的实体类,毕竟引擎是固定发布的,游戏开发者要自定义自己的实体类型。之前你用Game DLL里的SERVER_DLL_GETNEWOBJECT来分配的方案,确实没法让引擎主导分配,那咱们来聊聊几个可行的解决思路:

方案一:注册构造回调+引擎负责内存分配

核心思路是让Game DLL把每个实体类的构造逻辑和类大小注册给引擎,引擎负责分配内存,再调用Game侧的构造回调完成对象初始化(用placement new)。这样既让引擎掌控了内存分配,又能构造Game侧的自定义实体。

引擎DLL实现示例

首先完善CBase类,同时引擎维护一个实体类注册表,提供注册接口和分配逻辑:

// 引擎侧的CBase类
class DLLEXPORT CBase {
public:
    std::string className;
    virtual void Init() = 0;
    virtual void OtherMethod() = 0;
    // 虚析构很重要!跨DLL多态析构必须要有
    virtual ~CBase() = default;
};

// 引擎侧的注册表:类名 -> (类大小, 构造回调)
#include <unordered_map>
#include <functional>
std::unordered_map<std::string, std::pair<size_t, std::function<CBase*(void*)>>> s_entityRegistry;

// 引擎提供的注册接口,让Game DLL注册实体类
DLLEXPORT void RegisterEntityClass(const char* className, size_t classSize, std::function<CBase*(void*)> constructor) {
    s_entityRegistry[className] = {classSize, constructor};
}

// 引擎负责分配并创建实体的核心函数
DLLEXPORT CBase* CreateNewEntity(const char* className) {
    auto it = s_entityRegistry.find(className);
    if (it == s_entityRegistry.end()) {
        return nullptr;
    }

    // 引擎分配内存(这里用new,也可以换成自定义内存池)
    void* mem = new char[it->second.first];
    if (!mem) {
        return nullptr;
    }

    // 调用Game侧的构造回调,用placement new构造对象
    CBase* newEntity = it->second.second(mem);
    if (!newEntity) {
        delete[] static_cast<char*>(mem);
        return nullptr;
    }

    newEntity->className = className;
    newEntity->Init();
    entitylist.addentity(newEntity);
    return newEntity;
}

// 引擎提供释放接口,统一管理内存
DLLEXPORT void DestroyEntity(CBase* entity) {
    if (!entity) return;
    // 先调用析构函数
    entity->~CBase();
    // 释放引擎分配的内存
    delete[] static_cast<char*>(reinterpret_cast<void*>(entity));
    entitylist.removeentity(entity);
}

Game DLL实现示例

Game侧只需要在初始化时,把自定义实体类注册到引擎即可:

// Game侧的基类,继承自引擎的CBase
class CBaseEntity : public DLLIMPORT CBase {
public:
    virtual void Init() override {
        // 自定义初始化逻辑
    }
    virtual void OtherMethod() override {
        // 自定义实现
    }
    // 游戏侧自己的函数,引擎无法调用
    void AnotherFunction() {
        // 仅游戏侧可见的逻辑
    }
};

// 自定义武器实体
class CTestWeapon : public CBaseEntity {
public:
    CTestWeapon() = default;
    virtual void Init() override {
        CBaseEntity::Init();
        // 武器专属初始化
    }
};

// Game DLL初始化时注册实体类
void GameDLL_Init() {
    // 注册CTestWeapon:传入类名、类大小、构造回调(用placement new)
    RegisterEntityClass("Entity_Weapon_Test", sizeof(CTestWeapon), [](void* mem) {
        // 在引擎分配的内存上构造对象
        return new(mem) CTestWeapon();
    });

    // 其他实体类同理注册...
}

这个方案的优势是:

  • 引擎完全掌控内存分配/释放,避免跨DLL内存管理的问题(前提是引擎和Game DLL使用同一个CRT版本,或者引擎用自定义内存池)
  • Game侧可以灵活添加任意实体类,无需修改引擎代码
  • 引擎只需要调用CBase的虚函数,不会访问到Game侧的专属函数

关键注意事项

  1. 跨DLL的CRT一致性:如果引擎和Game DLL使用不同的CRT版本,直接用new/delete跨DLL会导致内存泄漏或崩溃。最好的解决办法是引擎提供自定义的内存分配/释放函数(比如EngineAlloc/EngineFree),两边都用这套函数来管理内存。
  2. 虚析构函数:CBase必须要有虚析构函数,这样引擎调用entity->~CBase()时,才能正确调用到Game侧派生类的析构函数。
  3. 注册时机:Game DLL必须在引擎调用CreateNewEntity之前完成所有实体类的注册,比如在Game DLL的初始化函数中执行注册逻辑。

为什么你之前的方案不符合需求?

你之前的方案是让Game DLL负责new对象,这意味着内存归Game DLL的CRT管理,引擎如果要释放这个对象就会有风险(比如引擎用的CRT和Game DLL不同)。而上面的方案把内存分配权拿回给引擎,Game侧只负责对象的构造逻辑,完美解决了这个问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.20 12:45:30