Tille - I can see you, read those man pages!   Tille's Site

The most basic SDL program

Before starting on this, you should read a bit in /usr/share/doc/SDL-devel-<version> and read the tutorials that can be found at libsdl.org.

The C program

Time to take out your favorite editor!

Information about the functions can be found in any SDL tutorial. You need to activate and properly shutdown the SDL system. You have to specify which SDL subsystem you are going to use in your program. If you don't do this, it won't compile (not enough arguments). So we specify the CD-ROM subsystem, on which we are going to build the next exercise.

We're not going to use that subsystem in this first test, this is just to see that you can compile an SDL program.

/* Program name: startsdl.c */
/* This is a test with SDL (see /usr/share/doc/SDL-devel/index.html. */

/* This library is always needed when writing SDl programs: */
#include <SDL.h>
 
int main() {
 
  printf("Initializing SDL.\n");
  if (SDL_Init(SDL_INIT_CDROM) < 0 ) {
        printf("Error: Could not initialize SDL: %s.\n", SDL_GetError());
        exit(-1);
  }
  printf("SDL CDROM initialized.\n");
 
  printf("Quitting SDL.\n");
 
  SDL_Quit();
  return(0);
}

The SDL_GetError function is defined in the file SDL_error.h in /usr/include/SDL/. We do not need to refer to this file, because our program will include /usr/include/SDL/SDL.h, which contains references to the SDL_error.h file and all the other files in /usr/include/SDL/.

Another thing that I found very disturbing is that in the docs you will sometimes see the expression SDL_Quit(), and in other places atexit(SDL_Quit). Turns out that atexit is sort of a C builtin command, that everybody knows about, but that was not discussed in the books I was told to read. We will use the atexit command in further examples because it allows for quitting the program more elegantly in different places, should it be needed. But for now, really the most simple example.

Note that SDL functions return a value lower than zero if fatal errors occur. We do a basic check on that, and if all goes well we print a message to the terminal, indicating success. Should the program fail, the SDL framework will try to tell you why it did so.

Before you compile

GCC basics

The GNU C compiler from the GNU Compiler Collection will create a program from this code. Roughly this happens in four phases:

  • Preprocessing: cpp resolves #define, #include and #if.
  • Compilation: gcc produces assembly language and feeds it into the assembler.
  • Assembly: gas reads input from compiler and produces object files, with file names ending in .o.
  • Linking: ld puts object files together in an executable program.

If you would now just run gcc startsdl.c, it will not work, you will see this:

[tille@octarine ~C/SDL/startsdl] gcc startsdl.c
startsdl.c:5:17: SDL.h: No such file or directory
startsdl.c: In function `main':
startsdl.c:10: `SDL_INIT_CDROM' undeclared (first use in this function)
startsdl.c:10: (Each undeclared identifier is reported only once
startsdl.c:10: for each function it appears in.)

This kind of message has driven me mad in the past, multiple times, because the only thing people think about, is include files. This only works for very simple programs that only use the default libraries and include files, like the wc exercise in the basic books, or the stuff where they let you do exercises on arithmetic operations and such.

But the problem here is that the C compiler does not know which non-standard C libraries to link with, unless you tell it exactly which ones. The compiler also needs to know which specific settings your system needs in order to run the executable. How do you find that out?

The SDL package provides a tool called sdl-config, which you can run to provide the necessary information. First for the libraries:

[tille@octarine ~/C/SDL/startsdl] sdl-config --libs
-L/usr/lib -Wl,-rpath,/usr/lib -lSDL -lpthread

This means that gcc should be called with the -L option, indicating a particular directory to search for libs. /usr/lib is where gcc normally looks, but we put it in anyway, just to be on the safe side. Furthermore, we are going to instruct the compiler to give extra information, with the -W option, about what the linker does. We use the -rpath option for safety also, I am told it is not really necessary on Linux systems, but let's try to make compatible code, so don't worry too much about it and leave it as proposed to specify runtime libraries (and not overwrite existing libraries on some systems). Then with -l we include the libraries: SDL stands for libSDL, pthread for libpthread (specific to the Linux platform, so best leave that in as well.)

We're not finished yet, there are those system specific settings as well. Find out about those like this:

[tille@octarine ~/C/SDL/startsdl] sdl-config --cflags
-I/usr/include/SDL -D_REENTRANT

This extends the default /usr/include path with /usr/include/SDL and defines a variable. These settings are very specific to the system on which you compile, so it might just as well be something different for you. Anyway, all this output from sdl-config needs to be fed as options to the gcc program - although compiling is only a part of the work, this is how you start the creation of a program. You don't need to run the individual commands manually.

However, even if you use command substitution, this results very fast in very long command lines. With make, we can make this easier and it also allows for automation and optimizing of the compilation process. I'm told that nobody is really happy with this make, but if it's the first thing a professional does when explaining supposedly simple things such as SDL, who am I to care?

Docs with this part:

  • Programming with GNU Software, from O'Reilly.
  • info gcc

The Makefile

The Makefile tells make what to do. The above book contains some very good reading about the structure of Makefiles, but you can also read info make. But even easier is to read the documented Makefile below. It describes some simple options that you can run the gcc compiler with.

# Makefile for startsdl.c

# Use the gcc compile program and produce a lot of warning messages about
# questionable coding practices:
CC  = gcc -Wall
 
# Name the binary program that will be created:
BIN = startsdl
 
# `sdl-config --cflags` gives the following options to the C compiler:
# -I/usr/include/SDL -D_REENTRANT
# This says to include the /usr/include/SDL directory, which defines
# a.o. SDL_Init and holds references to all other SDL functions you use in
# your program and which can be found in the same directory.
# -D acts like #define, it sets a variable so as the program will build in the
# correct way on your system.  It is used by functions in /usr/include.
CFLAGS=`sdl-config --cflags`
 
# Linking with libraries: `sdl-config --libs` gives the following output:
# -L/usr/lib -Wl,-rpath,/usr/lib -lSDL -lpthread
# -L looks for libs in a particular directory; -l specifies individual
# libraries.
LDFLAGS=`sdl-config --libs`

# "all" is just a name, which points to the objectfiles to be processed.
# If it is the first target, it is also the default.
all: $(BIN)
                                                                                
# Particular things to do for each part of the program.  This has only one part,
# in which we define the object file and to process it with the appropriate
# options.
$(BIN): startsdl.o
        $(CC) startsdl.o \
        -o $(BIN) $(LDFLAGS)
                                                                                
# Stuff to clean up when make is called with this argument: remove object files.
clean:
        @rm -f *.o
        @rm $(BIN)

One important rule: the indentation in the Makefile should be done with tabs, don't just use the space bar.

Additional information:

  • The command make -p | less gives an overview of possible make variables and default settings.
  • The make info files.

Compiling your SDL program

Put the Makefile in the same directory as the startsdl.c file, and run make. With no options, it will make the first target:

[tille@octarine ~/C/SDL/startsdl] ls
Makefile  startsdl.c

[tille@octarine ~/C/SDL/startsdl] make
gcc -Wall `sdl-config --cflags`   -c -o startsdl.o startsdl.c
startsdl.c: In function `main':
startsdl.c:12: warning: implicit declaration of function `exit'
gcc -Wall startsdl.o \
        -o startsdl -lm `sdl-config --libs`

You get an warning message here that we will solve later on. Remember for now that warnings are not critical, but they give hints about unclean code. Only actual errors will make the build process fail.

After make finishes, you will see the executable file it has created:

[tille@octarine ~/C/SDL/startsdl] ls
Makefile  startsdl*  startsdl.c  startsdl.o

Test the program

You should now be able to run the executable startsdl:

[tille@octarine ~/C/SDL/startsdl] ./startsdl
Initializing SDL.
SDL CDROM initialized.
Quitting SDL.

Next

To prove that you can now rely on the documentation from the SDL package and site, we will now also do something with the CD-ROM. We'll use some of the functions defined in /usr/include/SDL/SDL_cdrom.h. We also use the atexit command here, which is in the /usr/include/stdlib.h (not included by gcc by default).

From the system documentation:

[tille@octarine ~/C/SDL/CD] apropos atexit
atexit               (3)  - register a function to be called at normal program termination

And from the man page man 3 atexit:

ATEXIT(3)                  Linux Programmer's Manual                 ATEXIT(3)

 
NAME
       atexit  -  register  a function to be called at normal program termina-
       tion.
 
SYNOPSIS
       #include <stdlib.h>
 
       int atexit(void (*function)(void));
 
DESCRIPTION
       The atexit() function registers the given function to be called at nor-
       mal  program  termination,  whether  via exit(3) or via return from the
       program's main function.  Functions so registered  are  called  in  the  
       reverse order of their registration; no arguments are passed.
 
RETURN VALUE
       The  atexit()  function returns the value 0 if successful; otherwise it
       returns a nonzero value; errno is not set.

This is what it looks like:

/* Program name: sdlcd2.c */
/* This does some stuff with functions from the SDL CD-ROM library.*/

/* This include header is always needed when doing something with SDL: */
#include <SDL.h>

/* And this library contains general utilities, like the atexit function: */
#include <stdlib.h>
                                                                                
int main() {
                                                                                
  printf("Initializing SDL.\n");
  if (SDL_Init(SDL_INIT_CDROM) < 0 ) {
        printf("Error: Could not initialize SDL: %s.\n", SDL_GetError());
        exit(-1);
  }
  atexit(SDL_Quit);
                                                                                
  printf("SDL initialized.\n");
                                                                                
  printf("CD drive(s): available: %d\n", SDL_CDNumDrives());
  int i;
  for ( i=0; i<SDL_CDNumDrives(); ++i ) {
        printf("Drive %d: \"%s\"\n", i, SDL_CDName(i));
  }
  return(0);
}

Adapt the Makefile accordingly - we left out the comments now:

CC  = gcc -Wall
BIN = sdlcd2
CFLAGS=`sdl-config --cflags`
LDFLAGS=`sdl-config --libs`

all: $(BIN)

$(BIN): sdlcd2.o
        $(CC) sdlcd2.o \
        -o $(BIN) $(LDFLAGS)

clean:
        @rm -f *.o
        @rm $(BIN)

Compile:

[tille@octarine ~/C/SDL/CD] make
gcc `sdl-config --cflags`   -c -o sdlcd2.o sdlcd2.c
gcc sdlcd2.o \
        -o sdlcd2 `sdl-config --libs`

And run:

[tille@octarine ~/C/SDL/CD] ./sdlcd2
Initializing SDL.
SDL initialized.
CD drive(s): available: 1
Drive 0: "/dev/cdrom"

Now you should be able to play around a bit with the functions defined in the CD-Rom subsystem, read through the SDL_cdrom.h file and check with the docs and on-line documentation.

Here is a longer example:

/* Program name: sdlcd3.c */
/* Testing the SDL_CD subsystem */
 
/* This library is always needed: */
#include <SDL.h>
/* And this library contains general utilities: */
#include <stdlib.h>
 
int main() {
 
  printf("Initializing SDL.\n");
  if (SDL_Init(SDL_INIT_AUDIO|SDL_INIT_CDROM) < 0 ) {
        printf("Error: Could not initialize SDL: %s.\n", SDL_GetError());
        exit(-1);
  }
  atexit(SDL_Quit);
 
  printf("SDL initialized.\n");
 
  printf("CD drive(s): available: %d\n", SDL_CDNumDrives());
  int i;
  for ( i=0; i<SDL_CDNumDrives(); ++i ) {
        printf("Drive %d: \"%s\"\n", i, SDL_CDName(i));
  }
  SDL_CD *cdrom;
  CDstatus status;
  char *status_str;
 
  cdrom = SDL_CDOpen(0);
  if ( cdrom == NULL ) {
        fprintf(stderr, "Couldn't open default CD drive: %s\n", SDL_GetError());
        exit(2);
  }
 
  status = SDL_CDStatus(cdrom);
  switch (status) {
        case CD_TRAYEMPTY:
          status_str = "No CD.";
          break;
        case CD_STOPPED:
          status_str = "CD Stopped.";
          break;
        case CD_PLAYING:
          status_str = "CD Playing.";
          break;
        case CD_PAUSED:
          status_str = "CD Paused.";
          break;
        case CD_ERROR:
          status_str = "CD Error.";
          break;
        }
        printf("Drive status: %s\n", status_str);
        if ( status >= CD_PLAYING ) {
          int m, s, f;
          FRAMES_TO_MSF(cdrom->cur_frame, &m, &s, &f);
          printf("Currently playing track %d, %d:%2.2d\n",
          cdrom->track[cdrom->cur_track].id, m, s);
        }
  return(0);
}

Upon insertion of an audio CD:

[tille@octarine ~/C/SDL/CD3] ./sdlcd3
Initializing SDL.
SDL initialized.
CD drive(s): available: 1
Drive 0: "/dev/cdrom"
Drive status: CD Stopped.
[tille@octarine ~/C/SDL/CD3] cdp
[tille@octarine ~/C/SDL/CD3] ./sdlcd3
Initializing SDL.
SDL initialized.
CD drive(s): available: 1
Drive 0: "/dev/cdrom"
Drive status: CD Playing.
Currently playing track 1, 0:04

And then, the next step.

Home
© 1995-2010 Machtelt Garrels - tille - Powered by vIm - Best viewed with your eyes - Validated by W3C - Last update 20100511