模块化游戏引擎:DLL循环依赖

时间:2015-11-10 13:04:57

标签: c++ dll module directx game-engine

我想创建一个游戏引擎作为培训&投资组合项目和模块化方法听起来很有希望,但我在模块设计方面遇到了一些问题。

首先,我想创建像渲染,应用程序,实用程序等低级模块,并在像Terrain这样的高级模块中使用它们。 所以依赖关系有点像这个游戏< -Engine< -Terrain< -Rendering。

我想创建多个渲染"子模块"比如Rendering.Direct3D11和Rendering.OpenGL。那就是我有循环依赖的地方。子模块将使用渲染和渲染的接口需要管理子模块,对吧? 游戏和LT; - 发动机< -Terrain< -Rendering< - > Rendering.Direct3D11

我可能会创建一个像RenderingInterfaces这样的模块并打破循环依赖,但这似乎是一个hacky解决方法。我打算使用"子模块设计"多次喜欢: 游戏和LT; - 发动机< -Application< - > Application.Windows

子模块设计难看吗?有没有办法在没有循环依赖的情况下使用子模块设计?

2 个答案:

答案 0 :(得分:1)

在您的设计中不应该需要反向依赖。

这完全是关于接口的。您的渲染模块需要一个本机渲染API(在您的术语中为子模块),但它不应该关心它是OpenGL还是Direct3D11。 API子模块只需暴露一个通用的API;像CreatePrimitiveFromResource()RenderPrimitive()这样的子模块......这些子模块不应该知道上层,它们只是公开它们的公共API。

换句话说,唯一的"依赖"需要的是渲染模块依赖于渲染子模块(使用公共接口),渲染子模块不依赖于任何东西(在引擎中),它们只是暴露了一个公共接口。

简单示例:

我们有一个渲染模块" IntRenderer"这会产生整数。它的工作是将整数转换为字符并打印它们。现在我们想要子模块" IntRenderer.Console"和" IntRenderer.Window",在控制台或窗口中打印。

有了这个,我们定义了我们的接口:子模块必须是一个导出函数void print( const char * );的DLL。 整个描述是我们的界面;它描述了我们所有的int渲染器子模块必须具有的共同公共面。以编程方式,您可以说界面只是函数定义,但这仅仅是术语问题。

现在每个子模块都可以实现接口:

// IntRenderer.Console
DLLEXPORT void print( const char *str ) {
    printf(str);
}

// IntRenderer.Window
DLLEXPORT void print( const char *str ) {
    AddTextToMyWindow(str);
}

使用它,int渲染器可以只使用导入子模块,并使用printf(myFormattedInt);,无论子模块如何。

您可以根据需要明确定义您的界面,如果需要,可以使用C ++多态 示例:子模块X必须是导出函数CreateRenderer()的DLL,该函数返回继承类Renderer的类,并实现其所有虚函数。

答案 1 :(得分:1)

你可以抽象地解决这个问题。假设您有三个dylib:Game.dllRenderer.dllSubRenderer.dll

渲染器界面可能如下所示(简化):

// Renderer.h
class SubRenderer
{
public:
     virtual ~SubRenderer() {}
     virtual void render() = 0;
};

class API Renderer
{
public:
     explicit Renderer(SubRenderer* sub_renderer);
     void render();

private:
     SubRenderer* sub_renderer;
};

您可以将其添加到Renderer.h或类似的内容中,并且可以在render中实现呈现器构造函数和Renderer.cpp方法,其中包含输出Renderer.dll的项目}。

现在在SubRenderer.dll,你可能有这样的功能:

// SubRenderer.h
class SubRenderer;
API SubRenderer* create_opengl_renderer();

这可以在SubRenderer.cpp中实现,它被编译/链接到输出`SubRenderer.dll。它可能看起来像这样:

// SubRenderer.cpp
#include "SubRenderer.h"
#include <Renderer.h>

class OpenGlRenderer: public SubRenderer
{
public:
    virtual void render() override {...}
};

SubRenderer* create_opengl_renderer()
{
    return new OpenGlRenderer;
}

最后但并非最不重要的是,在Game.dll的某些源文件中,您可以在某些Game.cpp内执行此类操作:

// Game.cpp
#include <Renderer.h>
#include <SubRenderer.h>

int main()
{
    SubRenderer* opengl_renderer = create_opengl_renderer();
    Renderer renderer(opengl_renderer);
    renderer.render(); // render a frame
    ...
    delete opengl_renderer;
}

...当然希望有一个符合RAII的更安全的设计。

使用这种系统,你有这些头依赖:

`Game.cpp->Renderer.h`
`Game.cpp->SubRenderer.h`
`SubRenderer.cpp->Renderer.h`

就模块依赖性而言:

`Game.dll->Renderer.dll`
`Game.dll->SubRenderer.dll`

就是这样 - 任何地方都没有循环依赖。 Game.dll取决于Renderer.dllSubRenderer.dll,但Renderer.dllSubRenderer.dll完全相互独立。

这是有效的,因为这个Renderer可以使用SubRenderer给定其虚拟接口而不确切知道它是什么(因此不需要依赖于具体类型的'子渲染器')。

您可以将Renderer.h置于可通过公共包含路径(例如:SDK目录内)的所有三个项目集中访问的位置。没有必要复制它。