使用 DLL 覆盖/修改 C++ 类

Posted

技术标签:

【中文标题】使用 DLL 覆盖/修改 C++ 类【英文标题】:Overriding / modifying C++ classes using DLLs 【发布时间】:2009-08-17 18:06:31 【问题描述】:

我有一个项目,我维护的代码库很大(>200,000 行代码)(“核心”)。

目前,这个核心有一个脚本引擎,它由钩子和一个脚本管理器类组成,当它们发生时调用所有钩子函数(通过 DLL 注册的函数)。老实说,我不知道它究竟是如何工作的,因为核心大多没有文档记录,并且跨越数年和大量开发人员(当然,他们不存在)。当前脚本引擎的一个例子是:

void OnMapLoad(uint32 MapID)

    if (MapID == 1234)
    
        printf("Map 1234 has been loaded");
    


void SetupOnMapLoad(ScriptMgr *mgr)

    mgr->register_hook(HOOK_ON_MAP_LOAD, (void*)&OnMapLoad);

一个名为setup.cpp 的补充文件调用SetupOnMapLoad 与核心的ScriptMgr

这种方法不是我要找的。对我来说,完美的脚本引擎将允许我覆盖核心类方法。我希望能够创建从核心类继承并对其进行扩展的类,如下所示:

// In the core:    
class Map

    uint32 m_mapid;
    void Load();
    //...


// In the script:
class ExtendedMap : Map

    void Load()
    
        if (m_mapid == 1234)
            printf("Map 1234 has been loaded");

        Map::Load();
    

然后我希望核心和脚本中的每个Map 实例实际上都是ExtendedMap 的实例。

这可能吗?怎么样?

【问题讨论】:

【参考方案1】:

继承是可能的。我没有看到用 ExtendedMap 实例替换 Map 实例的解决方案。

通常,如果您有一个始终用于创建 Map 对象的工厂类或函数,则可以这样做,但这是现有(或不存在)设计的问题。

我看到的唯一解决方案是在代码中搜索实例化并尝试手动替换它们。这是一个有风险的做法,因为您可能会错过其中的一些,并且可能是某些实例不在您可用的源代码中(例如,在那个旧 DLL 中)。

稍后编辑 在以多态方式使用它时,这种方法覆盖也有副作用。

例子:

Map* pMyMap = new ExtendedMap; 

pMyMap->Load(); // This will call Map::Load, and not ExtendedMap::Load.

【讨论】:

如果脚本不提供这样的类,我不希望源有ExtendedMap。这个想法是在我扩展某些(或所有)类之后,在 DLL 本身中重新注册它们【参考方案2】:

这听起来像是“装饰者”设计模式的教科书案例。

【讨论】:

听起来接近我正在寻找的东西,但我如何使核心始终使用脚本提供的装饰器并回退到基类(或它的装饰器)?我希望我的核心始终使用MapDecorator可以在外部 DLL 中定义 我认为您必须对核心代码进行一些重构,以便 Map 对象的所有创建都通过工厂完成,这将公开一些您的脚本挂钩可以用来控制哪些接口创建了装饰地图的味道。我喜欢我在其他答案中看到的一些想法;也许其中一个更适合您的情况。【参考方案3】:

虽然有可能,但非常危险:系统应该对扩展开放(即挂钩),但对更改关闭(即覆盖/重新定义)。像这样继承时,您无法预期客户端代码将显示的行为。正如您在示例中看到的那样,客户端代码必须记住调用超类的方法,它不会:)

一种选择是创建一个非虚拟接口:一个抽象基类,它具有一些调用纯虚函数的模板方法。这些必须由子类定义。

如果您不想创建核心地图,脚本应该为核心提供一个工厂来创建地图后代。

【讨论】:

【参考方案4】:

如果我对类似系统的经验适用于您的情况,则注册了几个挂钩。因此,基于模式 abstract factory 的解决方案不会真正起作用。您的系统接近模式observer,这就是我要使用的。您创建一个基类,将所有可能的钩子作为虚拟成员(如果钩子很多,则创建几个具有相关钩子的基类)。您无需逐个注册钩子,而是注册一个对象,该对象的类型为具有所需覆盖的类的后代。对象可以具有状态,并且可以方便地替换此类回调系统通常具有的void* user data 字段。

【讨论】:

以上是关于使用 DLL 覆盖/修改 C++ 类的主要内容,如果未能解决你的问题,请参考以下文章

使用 JNA 从 Java 调用 C++ dll 方法并避免方法名称修改

修改 C++ DLL 以支持 unicode - 要避免的常见陷阱?

C++ - 替换基类方法

Unity与DLL(C++)☀️二新建一个DLL的类库,并编写C++代码

Unity与DLL(C++)☀️二新建一个DLL的类库,并编写C++代码

Visual C++ - 覆盖从 DLL 导入的函数?