Lazy Foo' Productions

Using Multiple Source Files

Last Updated 7/16/12
As you saw with the last article, program source codes can get pretty big. Here you'll learn how to break your program's source into smaller, easier to manage pieces.
The state machine demo had over 1000 lines of code. Any of you that have tried know it's a pain having to search through one big source file. By having your program's source split into multiple source files, you no longer have to sift through one big chunk of code.

When dealing with multiple source files there's basically two types of files you're going to be dealing with: source files and header files. Standard *.cpp source files you already know as you've been using them since you started programming. Headers, however, are a bit tricky.

To try to understand what headers do, look in the SDL_image.h file. Inside, you'll see a bunch of declarations of the functions in the SDL_image API like IMG_Load(), IMG_GetError(), IMG_isPNG(), etc, but you don't actually see the definitions of the functions that load the images, get the errors, and such. Is all that headers do is declare constants/classes/functions/variables?

The answer is yes. All SDL_image.h does is tell the compiler about the functions in SDL_image so it can compile your source file. Is that all it needs? Well, when you try to build your executable, the compiler will compile your source file, then the linker will try to link everything together into one binary. After it compiles your .cpp file, it will try to look up the actual definitions for the SDL_image functions. When the linker doesn't find the function definitions in one of the *.cpp files in your project, it will complain that it can't find the definitions and abort.

Well where is the actual code for SDL_image functions? The functions are compiled inside of the SDL_image binary (*.dll on windows and *.so on *nix). To get the linker to stop complaining, we give it a lib file which tells the linker where function definitions are in the SDL_image binary so it can link dynamically at the program's run time.

This might not make much sense now, but it will after we get our hands dirty by splitting up the motion tutorial source code into mulitple files.
#ifndef CONSTANTS_H #define CONSTANTS_H //The screen attributes const int SCREEN_WIDTH = 640; const int SCREEN_HEIGHT = 480; const int SCREEN_BPP = 32; //The frame rate const int FRAMES_PER_SECOND = 20; //The dimensions of the dot const int DOT_WIDTH = 20; const int DOT_HEIGHT = 20; #endif
Here's the code inside of the constants.h file. This header file declares the constants we're going to use in the program.

The preprocessors at the top and the bottom might be new to you. For those of you who don't know what a preprocessor is, it's basically a way to talk to the compiler. For example the #include preprocessor tells the compiler to include a file into the code.

What "#ifndef CONSTANTS_H" does is asks if CONSTANTS_H is not defined. If CONSTANTS_H is not defined, the next line defines CONSTANTS_H. Then we continue on with the code that defines the constants. Then #endif serves as the end of the "#ifndef CONSTANTS_H" condition block.

Now why did we do that?
#include "constants.h" #include "constants.h"
Let's say we had a situation where we included the same file twice.

In the first line where we include constants.h, the compiler will check if CONSTANTS_H is defined. Since it's not, it will define CONSTANTS_H and use the constants code inside the constants.h normally.

In the second line when we try to include constants.h, the compiler will try check if CONSTANTS_H is defined. Because CONSTANTS_H was defined already, it will skip past the code that defines the constants. This prevents the constants from being defined twice and causing a conflict.

So now you see how this simple but effective safeguard works.
#ifndef BACON #define BACON //The screen attributes const int SCREEN_WIDTH = 640; const int SCREEN_HEIGHT = 480; const int SCREEN_BPP = 32; //The frame rate const int FRAMES_PER_SECOND = 20; //The dimensions of the dot const int DOT_WIDTH = 20; const int DOT_HEIGHT = 20; #endif
Just for the sake of information, what definition you check for doesn't matter. The above code will work perfectly. The thing is, using FILENAME_H is the common naming standard and as I have mentioned before it's important to use a naming standard.
#ifndef CLASSES_H #define CLASSES_H //The dot that will move around on the screen class Dot { private: //The X and Y offsets of the dot int x, y; //The velocity of the dot int xVel, yVel; public: //Initializes the variables Dot(); //Takes key presses and adjusts the dot's velocity void handle_input(); //Moves the dot void move(); //Shows the dot on the screen void show(); }; //The timer class Timer { private: //The clock time when the timer started int startTicks; //The ticks stored when the timer was paused int pausedTicks; //The timer status bool paused; bool started; public: //Initializes variables Timer(); //The various clock actions void start(); void stop(); void pause(); void unpause(); //Gets the timer's time int get_ticks(); //Checks the status of the timer bool is_started(); bool is_paused(); }; #endif
Here's the classes.h file which declares the Dot and Timer classes.

It would be better if I had separate header files for the Dot and Timer classes. The general rule is you should have a header file associated with each source file, but for the sake of not having to fumble with more files than we need to, I lumped them together here.
#ifndef FUNCTIONS_H #define FUNCTIONS_H //The headers #include "SDL/SDL.h" #include "SDL/SDL_image.h" #include <string> //File Loader SDL_Surface *load_image( std::string filename ); //Surface blitter void apply_surface( int x, int y, SDL_Surface* source, SDL_Surface* destination, SDL_Rect* clip = NULL ); //Initialization bool init(); //File loading bool load_files(); //Clean up void clean_up(); #endif
Here you see functions.h which has the declarations for the various utility functions. At the top we include the SDL and string headers because the functions need to know what a string and SDL_Surface are.
#ifndef GLOBALS_H #define GLOBALS_H //The header #include "SDL/SDL.h" //The surfaces extern SDL_Surface *dot; extern SDL_Surface *screen; //The event structure extern SDL_Event event; #endif
Here's the globals.h which contains the declarations for the global variables in the program. At the top you see we included SDL.h because the header needs to know what an SDL_Surface is.

Now the extern keyword you see infront of the global variables might be new to you. Well remember that header files are just supposed to inform the compiler that something exists. If we didn't have extern infront of the globals, when the header is included in a source file the compiler will create a copy of the variable for that source file. Then when the linker tries to link everything together, it will find multiple copies of the same variable and it will complain and abort.

The extern keyword just tells the compiler the variable exists somewhere. Now you won't have multiple definitions of the same variable, but where are the actual globals located?
//The headers #include "SDL/SDL.h" #include "globals.h" //The surfaces SDL_Surface *dot = NULL; SDL_Surface *screen = NULL; //The event structure SDL_Event event;
When the linker looks for the definitions for the of the globals, it'll find them in the global.cpp source file. Also notice this is where we initialize the globals which makes sense since these are the actual variables and not just a declaration.

Just a tip: in case you mess up with the globals and get a multiple definiton error and then you fix the source code but the linker still complains, try rebuilding the whole project. To save time, the compiler will only recompile the source codes that have been changed, and since the source files haven't been changed, the linker stll has them compiled with multiple definitons. Rebuilding them will get rid of the old compiled code.
//The headers #include "SDL/SDL.h" #include "SDL/SDL_image.h" #include <string> #include "constants.h" #include "classes.h" #include "functions.h" #include "globals.h" Dot::Dot() { //Initialize the offsets x = 0; y = 0; //Initialize the velocity xVel = 0; yVel = 0; }
Here's the top of the dot.cpp file which defines the Dot class.

We include the SDL headers because we use SDL_Event and SDL_Surface types. We use the constants header for the dot dimensions. The classes header has the dot declaration so that's a given. We need the functions header for applying surfaces. Lastly we need the globals header to apply stuff to the screen.
#include "SDL/SDL.h" #include "classes.h" Timer::Timer() { //Initialize the variables startTicks = 0; pausedTicks = 0; paused = false; started = false; }
Here's the top of the timer.cpp source file. All we include is the SDL header for the SDL timing functions and the classes header for the timer declaration.
#include "SDL/SDL.h" #include "SDL/SDL_image.h" #include <string> #include "constants.h" #include "functions.h" #include "globals.h" SDL_Surface *load_image( std::string filename ) { //The image that's loaded SDL_Surface* loadedImage = NULL; //The optimized surface that will be used SDL_Surface* optimizedImage = NULL; //Load the image loadedImage = IMG_Load( filename.c_str() ); //If the image loaded if( loadedImage != NULL ) { //Create an optimized surface optimizedImage = SDL_DisplayFormat( loadedImage ); //Free the old surface SDL_FreeSurface( loadedImage ); //If the surface was optimized if( optimizedImage != NULL ) { //Color key surface SDL_SetColorKey( optimizedImage, SDL_RLEACCEL | SDL_SRCCOLORKEY, SDL_MapRGB( optimizedImage->format, 0, 0xFF, 0xFF ) ); } } //Return the optimized surface return optimizedImage; }
Here's the top of functions.cpp file. We use the SDL headers, the constants header, functions header and globals header.
#include "SDL/SDL.h" #include "SDL/SDL_image.h" #include <string> #include "constants.h" #include "classes.h" #include "functions.h" #include "globals.h" int main( int argc, char* args[] ) { //Quit flag bool quit = false; //The dot that will be used Dot myDot; //The frame rate regulator Timer fps; //Initialize if( init() == false ) { return 1; } //Load the files if( load_files() == false ) { return 1; }
Here's the top of main.cpp which hosts the main() function. At the top we include all the headers we need and then some. The main function itself is exactly the same as its single source file counterpart.
Download the media and source code for this article here.