Introduction
During the last month, I’ve read many discussions about writing platform-independent applications. Such discussions make many beginners in programming unsure which library to use or which language would be the right choice. I even read arguments about disappearing importance of C++ because of the rising need of platform-independent development. In this article, I want to show in a little C++ game-example, that you do not have to possess great knowledge in specific libraries to port your application. It is more important to develop a good abstraction of the platform depending tasks.
Background
The first step you should do is wrapping, wrapping, wrapping. Therefore you should first identify the functions of your code where you have to use platform dependent functionality. In my game-application, I was very surprised that there was not too much of such functionality at all. First let’s have a look at the main parts of the application:
- User-Interaction
- Drawing
- Game-Logic
The only point that really is platform-dependent is number 2. Number 1 and 3 can be done easily using C++ and the STL. Is that really everything? No, you also have to find a way to run the game loop in some kind of message loop, which lies within your application window. So the principle task was to create a wrapper that is able to do the drawing and which performs the main loop for the app. I decided to write an abstract class as an interface for all required functions. Let’s have a look at its definition:
class GdiWrapper
{
public:
GdiWrapper(){};
void SetGameNotifier( GameNotifier* pTheGame ){ m_pTheGame = pTheGame; }
virtual ~GdiWrapper(){}; virtual bool GetUserInput( char* pstrStringOne, char* pstrStringTwo ) = 0;
virtual void MessageOutput( char* pstrMessage ) = 0;
virtual void OutputGameInfo( char* pstrText ) = 0; virtual int DrawLine( int x1, int y1, int x2, int y2 ) = 0; virtual int DrawScale( int x1, int y1, int x2, int y2 ) = 0;
virtual void DrawCircle( int x, int y, int Radiant,
bool bFilled, unsigned long dwColor ) = 0;
virtual void DrawEndLineText( int x, int y, int iCoordinate ) = 0;
virtual void RunMainLoop(void) = 0;
virtual bool LoopRunning(void) = 0;
protected:
GameNotifier* m_pTheGame;
};
These functions are everything you need to visualize the game. That way you don’t need to wrap whole GUI-functionality, and you only need some functions that handle some tasks you need in your application. For porting the application, all you have to do is to inherit from GdiWrapper
and implement the required functionality. If you take a look at the DrawEndLineText(...)
function and its use in different porting of the GdiWrapper
s, you will notice, that only the console implementation really performs functionality to draw the coordinates at the end of the line.
Using the code
All versions of the code are initialized in a very similar way:
#include "Game.h"
int main(int argc, char* argv[])
{
GdiWrapperKonsole theGdi; Game theGame( PlaySize( 5,5 ), &theGdi ); theGame.Run(); return 0;
}
In the MFC - Version, the initialisation is made in the InitInstance()
of the application. You see, that the only work to do for porting, is to write a new GdiWrapper
and initialising the game-instance with it.
Conclusion
Writing platform independent code does not always have to be a question of the used libraries. The development of a portable application may take a little more time, but afterwards porting the application shall be quite easy. You don’t always need the complete functionality of a large toolkit like QT, so it is often sufficient to just use some functionality you really need in your application. It’s never wrong to test your code during development on different compilers (you will be surprised to see how often I used BOOL
, DWORD
and other MS-specific defines). These are some major aspects you need to be careful about:
- Try to avoid platform specific definitions (e.g. MS specific
DWORD
, BOOL
...).
- Keep your application data separated from your visualization (MVC-pattern, DOC/View...).
- Work with different compilers and test on different systems.
- When you write a wrapper, always divide the wrapped tasks into simple and understandable functions.
However, my code surely is not optimal - I think the OpenGL-Implementation with the SDL shows that, but only because during developing the game classes, I never planned to write such an implementation. I'm sorry for possible problems compiling the QT, SDL and wxWindows example, but I only tested it on my Linux with KDevelop 3.0. The MFC and console implementation was done with the VS6.