引擎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侧的专属函数
关键注意事项
- 跨DLL的CRT一致性:如果引擎和Game DLL使用不同的CRT版本,直接用
new/delete跨DLL会导致内存泄漏或崩溃。最好的解决办法是引擎提供自定义的内存分配/释放函数(比如EngineAlloc/EngineFree),两边都用这套函数来管理内存。 - 虚析构函数:CBase必须要有虚析构函数,这样引擎调用
entity->~CBase()时,才能正确调用到Game侧派生类的析构函数。 - 注册时机:Game DLL必须在引擎调用
CreateNewEntity之前完成所有实体类的注册,比如在Game DLL的初始化函数中执行注册逻辑。
为什么你之前的方案不符合需求?
你之前的方案是让Game DLL负责new对象,这意味着内存归Game DLL的CRT管理,引擎如果要释放这个对象就会有风险(比如引擎用的CRT和Game DLL不同)。而上面的方案把内存分配权拿回给引擎,Game侧只负责对象的构造逻辑,完美解决了这个问题。
备注:内容来源于stack exchange,提问作者FranticDreamer
相关产品推荐
相关产品推荐

