How To Write Cross-Platform Code With, And The Pitfalls Of, SDL
From codeTank
With the upcoming of Linux, Mac OS X and a variety of other Operating Systems as an alternative to Windows(especially on the desktop), one might be interested in having a program that is capable of running on multiple Operating Systems and maybe even multiple platforms.
Contents |
Understanding How It Works
First off, different Operating Systems(Abbreviated to OS from now on) have different internal workings, so how can one kind of code be runnable on multiple? This answer is simple to answer: having another layer(often called libraries) between the inner workings of the OS and the actual code, or stick to standard library C/C++, thus meaning having no Graphical User Interface. Having a setup with extra libraries makes it possible to have general functions in the added layer which translate your intention to the right specifics of the OS. This 'library' allows on to, for instance, put up a screen with a function exposed by the library, while the library itself figures out how to do it precisely.
Multiple Libraries
Of course, there are multiple libraries available which may or may not do the same as SDL, the library used in this tutorial. Some libraries have more functionality than others, but that usually comes with a higher learning curve. This tutorial'll be using SDL because it is simple to setup and start with.
What Is SDL
SDL is a library which offers functions like getting a window up, support for OpenGL, threading and many other things which are very handy for making Cross-Platform multimedia applications. With emphasis on the multimedia bit, as creating game engines is very popular with SDL too. SDL also offers support for extra sub-libraries to SDL, like SDL_Font, which supplies neat looking fonts into your application.
Getting A Simple SDL Program Up
I won't be explaining how to compile SDL programs, as it requires explaining for a lot of different system setups. See other sites[1] for that instead.
A Rather Useless Window
#include <SDL/SDL.h> #include <stdlib.h> int main(int argc, char *argv[]) { short int quit = 0; /* to be used later on */ /* Initialise the engine; SDL_Init() returns a negative number if it failed. */ if (SDL_Init(SDL_INIT_VIDEO) < 0) { printf("Unable to init SDL: %s\n", SDL_GetError()); return 1; /* exit the program. 1 means that the program exits saying that something went wrong */ } /* Define the screen variable to be a new window of 640x480 pixels with a depth of 16 bit * and render everything on a hardware surface with a double buffer. (It is ok if you do not * understand yet, I will explain them later on) */ SDL_Surface* screen = SDL_SetVideoMode(640, 480, 16, SDL_HWSURFACE|SDL_DOUBLEBUF); /* again, some error checking. If screen is not initialised it is NULL, thus returns false. */ if(!screen) /* ! means not */ { printf("Unable to set 640x480 video: %s\n", SDL_GetError()); return 1; } /* The main loop of the program. It will stay stuck in this until SDL gets an event to quit */ while(!quit) { SDL_Event event; /* returns 0 if there are no events, effectively making the while loop skip to the next command * which is the same while loop in this case */ while(SDL_PollEvent(&event)) { switch(event.type) { case SDL_QUIT: /* if the event is SDL_QUIT, we basically tell the main loop to stop */ quit = 1; break; } } } SDL_FreeSurface(screen); /* cleaning up this variable */ printf("Exited cleanly\n"); return 0; /* exit program, 0 means that everything went OK */ }
This source gets a black window up and running under at least the following OS's: Windows, Linux and Mac OS X. SDL supports many many more platforms and OS's, for a list of which, visit http://www.libsdl.org
Explanation
The standard procedure in SDL is to initialise parts(or the entire) engine, create a display(screen/window) and lock your program in an infinite loop to catch and handle incoming events. Of course, the example posted isn't quite done neatly, as it sucks up 100% CPU with the main loop of the program. There are ways to lower it(like a frame limiter, or a delay), but that's outside the scope of the article.
You might've noticed the "SDL_HWSURFACE|SDL_DOUBLEBUF" when defining the screen. This part of the function is for a certain initialisation of the video engine, namely how to draw things onto the screen. People who want to use OpenGL with SDL have to change this parameter to intialise the OpenGL surface(SDL_OPENGL).
Another interesting tidbit is the main loop. In this loop, you can handle the standard events, but also handle custom events. You also have to 'finalise' drawing to the screen by calling SDL_Flip(). Because we initialised our video engine with a double buffer, every drawing operation automatically goes into the back buffer. To get this buffer onto the screen, we have to flip it around.
Addendum
The last program didn't have much functionality, so I'll be listing various useful functions and the liked
SDL_Flip
Flips the back buffer onto the screen, making everything that is drawn visible.
SDL_BlitSurface
Has the following parameters: (source, clip, destination, offset) Draws the source image in destination(usually screen) with offset and clipped to clip.
SDL_FreeSurface
Required to free SDL_Surfaces, as it contains some stuff that doesn't go away automatically. Leaving an SDL_Surface unfreed makes your program leak memory.
SDL_LoadBMP
Loads a BMP file into an SDL_Surface, allowing you to blit it onto the screen. The SDL_Image library contains functions for loading different file formats.
Pitfalls
Although I haven't discussed many advanced features of SDL, I must warn you for some highly annoying(if not told) oddities.
Threads And Graphics
If you're using threading in SDL(through the use of SDL_Thread), you cannot do any graphical changes inside that thread(SDL_Blit, SDL_Flip, changing the program title etc). To workaround this, you have to create your own even and send it onto the event stack.
SDL_Event user_event; user_event.type = SDL_USEREVENT; user_event.user.code = 0; user_event.user.data1 = 4; SDL_PushEvent(&user_event);
And catch it in your main thread this way:
SDL_Event event; while(SDL_PollEvent(&event)) { if(event.type == SDL_USEREVENT) { if(event.user.code == 0) { /* The cast might not work. Does not really matter however, use it as you see fit */ printf("Received user event with user code 4, data1: %i", (int)event.user.data1); } } }
The Screen
The screen's coordinates are different from your normal math-graph. Whereas a math graph has the y-axis going up upwards and down downwards, SDL has it reverse. y decreases if you go up, and increases if you go down. the x-axis is the same as the math-graph though.
User Events
If you're sending data through an user event, like this:
int important_data = 5; user_event.user.data1 = important_data;
You'll notice that in your main loop, the user.data1 variable is not the same as the value you have given it. To prevent corruption of happening, do this instead:
int important_data = 5; int* tmp_pointer = malloc(sizeof(important_data)) user_event.user.data1 = tmp_pointer;
Don't forget to free() it in your main loop! P.S. it is more safe to do this in C++ with the 'new' and 'delete' functions.

