Intelligent DLL design in game programming

by Dimi 20. February 2008 23:10

So what is that title about? What is intelligent DLL design? When designing a game engine you have to take care of so many things. You have to think of inertial delay times, transmission delays, overheads, buffers and so much more....

Usually you design and develop your game engine, you test it and as soon as it's validated you compile it as a dll. This is in general a good idea because of so much advantages of a dll. Think of the faster cycle times, encapsulation, better design (we will change it to great design in this post) and so much more.

To use the game engine the game programmer would include the main header file of the game engine somewhere in his code and link to the static library when compiling the code. In Visual Basic you call this early binding. In C++ you call it binding on compile time.

I will show you here a more elegant and more efficient way to design a DLL. Instead of providing a "simple" source code for download, you can download the blueTec engine. This is a small 2d mini game engine which I've published on my project site G-Productions. This package contains some examples which will show you the implementation of this engine design. You can download the blueTec Engine directly at the end of this article. You need Dev-Cpp to compile this correctly. 

What is a game engine? In this article when we talk about a game engine we don't only mean the render stuff. A game engine does not only exist of those fun-making render calls and renderstates but also of those less-fun-making stuff like the sound and music api, like the script engine and the network module.... So if we talk about a game engine in this article we talk about all the stuff that makes the visual, the audio, the network game and much more. For simplicity we will use here pseudo code but you can download a complete working project with this article.

 

Imagine you have finished the development of your game engine and you want to provide it to a team of developers who will use it as base of game. Usually you would now create a DLL (dynamic link library) project, copy your header and source files into this and compile with a pre-processor directive like #define GAME_API __declspec(dllexport). What's wrong with this operation? Nothing! In fact this is ok for many projects but there are more intelligent ways.

Assuming your main game class of your game engine looks like this:

   1: class CEngine 
   2: {
   3: private:
   4:     // private stuff like the IDirect3DDevice9 or the OpenGL stuff go in here
   5: protected:
   6:     HRESULT    InitGraphics();
   7:     HRESULT    InitAudio();
   8:     HRESULT    InitInput();
   9:  
  10:     HRESULT    Destroy();
  11: public:
  12:     CEngine ( HINSTANCE );
  13:     ~CEngine ();
  14:  
  15:     HRESULT    InitEngine();
  16:     HRESULT    ShutdownEngine();
  17:  
  18:     HRESULT    MoveFrame( float );
  19:     HRESULT    BeginRender();
  20:     HRESULT    RenderFrame();
  21:     HRESULT    EndRender();
  22:  
  23:     HRESULT    PlaySound();
  24:     HRESULT    PlayMusic();
  25: };
Listing 1: the main engine class from the dll 

 

This class is the main exported class of your game engine and would usually look in line 1 like this:

class GAME_API CEngine

what tells the compiler that this class can be used from outside. You would now have to compile the dll (a static library is generated automatically which the developer team will use it to link to it) and provide the complete code/project to the developer team. Now imagine you would detect a bug or optimize some functions within your game engine or the developer team would detect a bug? You would have to fix the bug and the developer team would not be able to continue the work on that point where the bug occured. They would have to find a way around. Then you would have to send again the whole package and the team would have to replace the complete game engine again, all the header and source files, recompile with the new static library provided with the dll etc.

And here comes the cool thing. With our method these steps aren't necessary any more. So let's get to work guys.

1) Create a static library project

step1

First of all create a new static library project like shown in the figure on the right. This static library is the base of our DLL design.

In Dev-Cpp the static libraries have the *.a extension. In Visual Studio or other compilers the static libraries have a *.lib extension. This is nearly the same, except of one point. You will be able to use Visual Studio generated static libraries with Dev-Cpp (in fact the MinGW compiler will use them since Dev-Cpp is only the IDE) but only if they are not so called short-symbol static libs.

 

pic2

Now after creating the project add two new header files like in the picture on the left and one source file. We will now take a closer look what the files contain. Let's start with the header file gameEngineDevice.h

 

 

   1: class CGameEngineDevice
   2: {
   3: protected:
   4:     HINSTANCE   m_hDll;
   5:     int         m_iWidth;
   6:     int         m_iHeight;
   7:     int         m_iBpp;
   8:     bool        m_bWindowed;
   9:     HWND        m_hWnd;
  10:  
  11: public:
  12:     CGameEngineDevice(){};
  13:     virtual ~CGameEngineDevice() = 0;
  14:     virtual HRESULT         InitEngine() = 0;
  15:     virtual HRESULT         ShutdownEngine() = 0;
  16:  
  17:     virtual HRESULT         MoveFrame( float ) = 0;
  18:     virtual HRESULT         BeginRender() = 0;
  19:     virtual HRESULT         RenderFrame() = 0;
  20:     virtual HRESULT         EndRender() = 0;
  21:  
  22:     virtual HRESULT         PlaySound() = 0;
  23:     virtual HRESULT         PlayMusic() = 0;
  24: };
  25:  
  26: typedef class CGameEngineDevice *LPENGINEDEVICE;
  27:  
  28: extern "C"
  29: {
  30:     HRESULT CreateEngineDevice( HINSTANCE hDll, CGameEngineDevice **pInterface );
  31:     typedef HRESULT (*CREATEENGINEDEVICE)(HINSTANCE hDll, CGameEngineDevice **pInterface );
  32:  
  33:     HRESULT RelaseEngineDevice( CGameEngineDevice **pInterface );
  34:     typedef HRESULT (*RELEASEENGINEDEVICE)(CGameEngineDevice **pInterface );
  35: }

Listing 2: class CGameEngineDevice from the file gameEngineDevice.h

 

What did we do here? Compare the class CGameEngineDevice with the original game engine class CGameEngine. What do you see? Yes you are right. All the public functions in our class CGameEngine are defined virtual. Below in line 28 we see an implementation of helper functions which we will need later.

Let's go to the file gameEngine.h. Here is the content:

   1: #include "gameEngineDevice.h"
   2:  
   3: class CGameEngine
   4: {
   5: private:
   6:     CGameEngineDevice    *m_pDevice;
   7:     HINSTANCE            m_hInst;
   8:     HMODULE              m_hDll;
   9: public:
  10:     CGameEngine( HINSTANCE hInst );
  11:     ~CGameEngine( void );
  12:     
  13:     void                Clear();
  14:  
  15:     HRESULT             CreateDevice( void );
  16:     LPENGINEDEVICE      GetDevice( void ) { return m_pDevice; }
  17:     HINSTANCE           GetModule( void ) { return m_hDll; }
  18:     void                Release( void );
  19: };
  20:  
  21: typedef class CGameEngine *LPGAME;

Listing 3: class CGameEngine from the file gameEngine.h

 

and here is a snapshot only of the most important function of the file gameEngine.cpp

   1: HRESULT CGameEngine::CreateDevice()
   2: {
   3:     HRESULT hr = S_OK;
   4:     char buffer[300];
   5:  
   6:     m_hDll = LoadLibraryEx("gameEngine.dll", NULL, 0);
   7:     if( !m_hDll )
   8:         return E_FAIL;
   9:  
  10:     CREATEENGINEDEVICE _CreateEngineDevice = 0;
  11:  
  12:     _CreateEngineDevice = (CREATEENGINEDEVICE)GetProcAddress( m_hDll, "CreateEngineDevice");
  13:     hr = _CreateEngineDevice( m_hDll, &m_pDevice );
  14:  
  15:     if( FAILED( hr ))
  16:     {
  17:         m_pDevice = NULL;
  18:         return E_FAIL;
  19:     }
  20:     return S_OK;
  21: }

Listing 4: code snippet from the file gameEngine.cpp

 

Wow, pretty much isn't it? Ok in short, the CreateDevice function is the bread and the butter of this project. LoadLibraryEx in line 6 loads the dynamic link library gameEngine.dll which will contain the game engine. In line 10 you see the the use of _CreateEngineDevice which was defined in the gameEngineDevice.h. The call of GetProcAddress retrieves the address of the exported function CreateEngineDevice from the specified dynamic-link library gameEngine.dll which was loaded before with the call LoadLibraryEx.

The static library project is finished. Save and compile to generate the static library file.

2. The DLL project

Now back to our dll project. The only thing you have to do here is to extend the main engine class CEngine. Take a look at listing 1 and replace the first line from class class CEngine to class CEngine : public CGameEngineDevice and to include the header file gameEngineDevice.h from our previously created static library project.

What did we do? The class CEngine inherits public from CGameEngineDevice. What does this mean for us? This means that all the functions which are declared virtual in the class CGameEngineDevice have to be implemented in the class CEngine.

One last thing is also to do. Remember the line 28 in listing 2. The extern "C" declaration? Where are those functions? Those have to be integrated within the dll so here we go with the code:

   1: extern "C"
   2: {
   3: HRESULT DLLIMPORT CreateEngineDevice( HINSTANCE hDll, CGameEngineDevice** pDevice )
   4: {
   5:     if( !*pDevice )
   6:     {
   7:         *pDevice = new CGameEngineDevice( hDll );
   8:         return S_OK;
   9:     }
  10:     return E_FAIL;
  11: }
  12:  
  13: HRESULT DLLIMPORT ReleaseEngineDevice( CGameEngineDevice** pDevice )
  14: {
  15:     if( !*pDevice )
  16:         return E_FAIL;
  17:  
  18:     delete *pDevice;
  19:     *pDevice = NULL;
  20:     return S_OK;
  21: }
  22: }

Listing 5: implementation of the wrapper functions for allowing the dynamic binding of the dll.

3. Using the dll in a game

How would you use the game engine in your game project?

  1. include in your game project the file gameEngine.h of the static library project
  2. define two new variables using the objects in our static library project (CGameEngine and CGameEngineDevice)
  3. create an instance of CGameEngine and call the function CreateDevice() to create a valid game engine device
  4. get a valid pointer into our CGameEngineDevice object using the call CGameEngine->GetDevice()

 

Here is the sample code:

   1: int WINAPI WinMain (HINSTANCE hThisInstance,
   2:                     HINSTANCE hPrevInstance,
   3:                     LPSTR lpszArgument,
   4:                     int nFunsterStil)
   5:  
   6: {
   7:     HWND hwnd;
   8:     MSG messages;
   9:     WNDCLASSEX wincl;
  10:  
  11:     // our objects from the static library project
  12:     CGameEngine           *m_pEngine;
  13:     CGameEngineDevice     *m_pEngineDevice;
  14:  
  15:     // window dependent stuff goes in here
  16:  
  17:     if (!RegisterClassEx (&wincl))
  18:         return 0;
  19:  
  20:     hwnd = CreateWindowEx ( 0, szClassName, "Windows App", /* window init stuff here */ );
  21:  
  22:     ShowWindow (hwnd, nFunsterStil);
  23:  
  24:     /* we need to save the instance and the window handle because we need them for the engine */
  25:     g_hInst = hThisInstance;
  26:     g_hwnd = hwnd;
  27:  
  28:     m_pEngine = new CGameEngine( g_hInst );
  29:  
  30:     // some simple error handling
  31:     if( !m_pEngine )
  32:         // error handling
  33:  
  34:     // now with calling the CreateDevice function the dll symbol is dynamicaly
  35:     // loaded so it's a real dynamic binding
  36:     if (FAILED( m_pEngine->CreateDevice()))
  37:         // error handling
  38:  
  39:     // the created device is passed over to our engine pointer in order to work with
  40:     m_pEngineDevice = m_pEngine->GetDevice();
  41:  
  42:     if( m_pEngineDevice == NULL )
  43:         // error handling
  44:  
  45:     if( FAILED( m_pEngineDevice->InitEngine() ))
  46:         // error handling
  47:  
  48:     /* ... message que here ... */
  49:     return 0;
  50: }

Listing 6: How to use the game engine in a game project

 

Just for fun

pic3 Open the dll using the tool Dependency Walker which you can find here. This tool scans Windows modules like dll's, exe files and ocx files and builds a hierarchical tree of the dependency. The tool shows also the exportable functions and entry points of the dll.

If you open our blueTec.dll with this tool you will notice only two available/exportable functions: CreateEngineDevice and ReleaseEngineDevice (look at the picture on the right). These are the functions which we have implemented within our dll and declared in our static library. Nothing more nothing less and that's all we need.

 

pic4 Other dll's like shown on the second picture which are compiled with the usual dll design are just exporting the complete class and provide entry points to every single method and property. Do you see the difference?

 

 

Resume

Why did we do all that above and why did the author confuse us more than necessary. Where are the benefits and advantages and why should someone go the way above?

Quiet easy. Imagine the problems mentioned at start of this article with bugs and stuff like that.

  • This way is a real dynamic implementation. Looking at the code snippets we see that the real game engine class is referenced from the dll itself. The application just receives a pointer at runtime. You could develop and test your complete application without even owning the dll because it is referenced at runtime. Something which wouldn't be possible with the usual method.
  • The greatest advantage is that in case of a change, an optimization and/or a bug fix you would recompile the dll and send only the compiled dll to the developer team. The team would replace only one file instead of having to replace many single header and source files and recompile the current milestone with the new created static library which ships with the dll. You safe so much time.
  • Another great benefit is that you don't have to provide your very secret code because you only deliver the static library project and the compiled dll. With the traditional way you would have to provide the complete source of the dll project.
  • Another very important advantage is that you could develop a game engine using completely the DirectX API and name it gameEngineDX.dll and provide it to a game development team. While the development team is full in progress to create a game using your game engine you could develop a second game engine using the OpenGL API (gameEngineOGL.dll). This game engine should also inherit from the class CGameEngineDevice. Now it would be for the development team very easy to switch between the two render API's and the best is that the team could develop the complete game using only one dll without owning the second and won't have to rewrite any code when receiving the second dll, since the definition of the main game engine class is the same and ignores which API is used. The development team doesn't even need to have the DirectX-SDK installed since the complete game engine code is already compiled and delivered within the dll.

These points are the most important I think and so this article is at the end now. I hope I could provide a "different" look at game engine design or just call it dll programming. There are more ways to design a dll but you should always have in mind that the result should not only be a fast, scalable and efficient dll at runtime but also while development since the most time you and your team will sit in front of the code and if you are able to safe some time for the game developer and make his work more efficient you should spend the time for a good design of your game engine ;)

Downloads:

blueTec_sdk.rar (515,53 kb)

Currently rated 4.2 by 5 people

  • Currently 4.2/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: , , ,

game development

Comments

Add comment


(Will show your Gravatar icon)  

  Country flag

biuquote
  • Comment
  • Preview
Loading



Powered by BlogEngine.NET 1.4.5.0
Theme by Mads Kristensen