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:
class CEngine { private: // private stuff like the IDirect3DDevice9 or the OpenGL stuff go in here protected: HRESULT InitGraphics(); HRESULT InitAudio(); HRESULT InitInput(); HRESULT Destroy(); public: CEngine ( HINSTANCE ); ~CEngine (); HRESULT InitEngine(); HRESULT ShutdownEngine(); HRESULT MoveFrame( float ); HRESULT BeginRender(); HRESULT RenderFrame(); HRESULT EndRender(); HRESULT PlaySound(); HRESULT PlayMusic(); };
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
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.
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
class CGameEngineDevice { protected: HINSTANCE m_hDll; int m_iWidth; int m_iHeight; int m_iBpp; bool m_bWindowed; HWND m_hWnd; public: CGameEngineDevice(){}; virtual ~CGameEngineDevice() = 0; virtual HRESULT InitEngine() = 0; virtual HRESULT ShutdownEngine() = 0; virtual HRESULT MoveFrame( float ) = 0; virtual HRESULT BeginRender() = 0; virtual HRESULT RenderFrame() = 0; virtual HRESULT EndRender() = 0; virtual HRESULT PlaySound() = 0; virtual HRESULT PlayMusic() = 0; }; typedef class CGameEngineDevice *LPENGINEDEVICE; extern "C" { HRESULT CreateEngineDevice( HINSTANCE hDll, CGameEngineDevice **pInterface ); typedef HRESULT (*CREATEENGINEDEVICE)(HINSTANCE hDll, CGameEngineDevice **pInterface ); HRESULT RelaseEngineDevice( CGameEngineDevice **pInterface ); typedef HRESULT (*RELEASEENGINEDEVICE)(CGameEngineDevice **pInterface ); }
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:
#include "gameEngineDevice.h" class CGameEngine { private: CGameEngineDevice *m_pDevice; HINSTANCE m_hInst; HMODULE m_hDll; public: CGameEngine( HINSTANCE hInst ); ~CGameEngine( void ); void Clear(); HRESULT CreateDevice( void ); LPENGINEDEVICE GetDevice( void ) { return m_pDevice; } HINSTANCE GetModule( void ) { return m_hDll; } void Release( void ); }; 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
HRESULT CGameEngine::CreateDevice() { HRESULT hr = S_OK; char buffer[300]; m_hDll = LoadLibraryEx("gameEngine.dll", NULL, 0); if( !m_hDll ) return E_FAIL; CREATEENGINEDEVICE _CreateEngineDevice = 0; _CreateEngineDevice = (CREATEENGINEDEVICE)GetProcAddress( m_hDll, "CreateEngineDevice"); hr = _CreateEngineDevice( m_hDll, &m_pDevice ); if( FAILED( hr )) { m_pDevice = NULL; return E_FAIL; } return S_OK; }
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:
extern "C" { HRESULT DLLIMPORT CreateEngineDevice( HINSTANCE hDll, CGameEngineDevice** pDevice ) { if( !*pDevice ) { *pDevice = new CGameEngineDevice( hDll ); return S_OK; } return E_FAIL; } HRESULT DLLIMPORT ReleaseEngineDevice( CGameEngineDevice** pDevice ) { if( !*pDevice ) return E_FAIL; delete *pDevice; *pDevice = NULL; return S_OK; } }
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?
- include in your game project the file gameEngine.h of the static library project
- define two new variables using the objects in our static library project (CGameEngine and CGameEngineDevice)
- create an instance of CGameEngine and call the function CreateDevice() to create a valid game engine device
- get a valid pointer into our CGameEngineDevice object using the call CGameEngine->GetDevice()
Here is the sample code:
int WINAPI WinMain (HINSTANCE hThisInstance, HINSTANCE hPrevInstance, LPSTR lpszArgument, int nFunsterStil) { HWND hwnd; MSG messages; WNDCLASSEX wincl; // our objects from the static library project CGameEngine *m_pEngine; CGameEngineDevice *m_pEngineDevice; // window dependent stuff goes in here if (!RegisterClassEx (&wincl)) return 0; hwnd = CreateWindowEx ( 0, szClassName, "Windows App", /* window init stuff here */ ); ShowWindow (hwnd, nFunsterStil); /* we need to save the instance and the window handle because we need them for the engine */ g_hInst = hThisInstance; g_hwnd = hwnd; m_pEngine = new CGameEngine( g_hInst ); // some simple error handling if( !m_pEngine ) // error handling // now with calling the CreateDevice function the dll symbol is dynamicaly // loaded so it's a real dynamic binding if (FAILED( m_pEngine->CreateDevice())) // error handling // the created device is passed over to our engine pointer in order to work with m_pEngineDevice = m_pEngine->GetDevice(); if( m_pEngineDevice == NULL ) // error handling if( FAILED( m_pEngineDevice->InitEngine() )) // error handling /* ... message que here ... */ return 0; }
Listing 6: How to use the game engine in a game Project
Just for fun
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.
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 😉