Error Handling

Last updated on March 5th, 2023

Error handling in Pascal means raising exceptions and catching them. SDL2 has also a way to treat errors. Both worlds can be combined and this chapter shows you how.

Let’s look at the whole program code:

program SDL_ErrorHandling;

uses SDL2, SysUtils;

type
  ESDLNilPointer = class(Exception);

var
  sdlWindow1: PSDL_Window;
  sdlRenderer: PSDL_Renderer;
  sdlSurface1: PSDL_Surface;

const
  filename = 'nofile.bmp'; // this file does not exist

begin
  // Initialize SDL2
  if SDL_Init(SDL_INIT_VIDEO) < 0 then Exit;

  // Create a window, renderer and a surface
  try
    SDL_CreateWindowAndRenderer(500, 500, SDL_WINDOW_SHOWN,
      @sdlWindow1, @sdlRenderer);

    if not Assigned(sdlWindow1) then
      raise ESDLNilPointer.CreateFmt(
        'sdlWindow1 is nil. SDL_GetError: %s', [SDL_GetError]);

    if not Assigned(sdlRenderer) then
      raise ESDLNilPointer.CreateFmt(
        'sdlRenderer is nil. SDL_GetError: %s', [SDL_GetError]);

    sdlSurface1 := SDL_LoadBMP(filename); // sdlSurface1 will be nil!
    if not Assigned(sdlSurface1) then
      raise ESDLNilPointer.CreateFmt(
        'sdlSurface1 is nil on loading %s. ' + sLineBreak + 'SDL_GetError: %s',
        [filename, SDL_GetError]);
  except
    on E: ESDLNilPointer do
    begin
      WriteLn('Exception ESDLNilPointer: ' + sLineBreak + E.Message);
      Readln;
    end;
  end;

  // Clean-up and quit SDL2
  if Assigned(sdlSurface1) then SDL_FreeSurface(sdlSurface1);
  if Assigned(sdlRenderer) then SDL_DestroyRenderer(sdlRenderer);
  if Assigned(sdlWindow1) then SDL_DestroyWindow(sdlWindow1);
  SDL_Quit;
end.

If you run this program, it will stop execution because it catches an exception. It will return several helpful information to make identification of the cause of the exception easier. It allows you to proceed running after the exception has been caught to free resources and prevent memory leaks. It will report the following exception messages:

sdlSurface1 is nil on loading nofile.bmp.

SDL_GetError: Parameter ‘src’ is invalid

Let’s start with the first part of the code:

program SDL_ErrorHandling;

uses SDL2, SysUtils;

type
  ESDLNilPointer = class(Exception);

var
  sdlWindow1: PSDL_Window;
  sdlRenderer: PSDL_Renderer;
  sdlSurface1: PSDL_Surface;

const
  filename = 'nofile.bmp'; // this file does not exist

The program “SDL_ErrorHandling” has three variables representing a SDL2 window, a SDL2 renderer and a SDL2 surface. It also has a constant “filename” which defines a file name of an imaginary file called “nofile.bmp”. As this file doesn’t exists, it will cause an exception on trying to load it (later in the code).

Right above the var block is an exception class declared whose names start by convention with a capital E instead of a capital T. It is called “ESDLNilPointer” and will be raised whenever a SDL2 function returns a nil pointer as error code. Nearly all function in SDL2 return an error code where 0 usually means sucess and -1 usually means failure. SDL2 function which create objects usually simply indicate failure by nil-pointers.

The main program’s begin .. end. code block starts with the initilization of the SDL2 system and ends with the clean up part and the quitting of the SDL2 system. I will not cover this in detail here again. Let’s instead have a detailed look into the core part of the begin .. end. block:

  // Create a window, renderer and a surface
  try
    SDL_CreateWindowAndRenderer(500, 500, SDL_WINDOW_SHOWN,
      @sdlWindow1, @sdlRenderer);

    if not Assigned(sdlWindow1) then
      raise ESDLNilPointer.CreateFmt(
        'sdlWindow1 is nil. SDL_GetError: %s', [SDL_GetError]);

    if not Assigned(sdlRenderer) then
      raise ESDLNilPointer.CreateFmt(
        'sdlRenderer is nil. SDL_GetError: %s', [SDL_GetError]);

    sdlSurface1 := SDL_LoadBMP(filename); // sdlSurface1 will be nil!
    if not Assigned(sdlSurface1) then
      raise ESDLNilPointer.CreateFmt(
        'sdlSurface1 is nil on loading %s. ' + sLineBreak + 'SDL_GetError: %s',
        [filename, SDL_GetError]);
 

Exception handling in Pascal

In short: Pascal’s exceptions are raised by the raise keyword and stop the program flow at this point. As this would terminate the program immediately, usually raised exception are caught and handled by try .. except or try .. finally blocks. After proper exception handling the program can run without termination although an exception occured.

As you can see, here the whole whole code is encapsuled by a try .. except statement.

Error handling in SDL2

SDL2 knows three error related functions: SDL_GetError, SDL_SetError(Format string, Arguments) and SDL_ClearError. They are completely independent of Pascal’s error handling but are very useful to gather the necessary information to fix issues.

Most SDL2 functions return an error code which indicated success or failure. SDL_GetError returns a string (as PAnsiChar) which gives you information about the error of the last function that returned an error code which indicated failure. Attention here, only fails will result in a message in SDL_GetError and they won’t be cleared autmatically if other functions run successully afterwards, hence you should use SDL_ClearError to clear the last error message from older functon calls. Hint: Sometimes SDL_GetError can be populated with a message even though functions were run successfully.

In the try-block of our code there are actually just two things tried:

  1. Create a renderer and a window
  2. Create a SDL2 surface from a bitmap file

After creating the renderer and window as known to you, we check for the error code (in these cases we check if one or both of them are nil). If the error code indicated a failure, an exception should be raised. Actually this code should not raise an exception and just run smoothly to the second step.

Creating a SDL2 surface will fail intentionally because there is no bitmap file to load. SDL_LoadBMP will return a nil pointer, hence “sdlSurface1” is nil. This will raise the previously declared “ESDLNilPointer” exception with a message as format string (see below if you don’t know about format strings).

The error message string is:


        'sdlSurface1 is nil on loading %s. ' + sLineBreak + 'SDL_GetError: %s'
 

The arguments are represented by an array of constants:


        [filename, SDL_GetError]
 

The error message has three parts. The first part states the failing variable and the associated file name. The second part (sLineBreak) is a cross-platform line break. The third part returns the message of the SDL_GetError function.

What is a Format string?

Coming from C, format strings are strings which contain data placeholders of defined type (string, integer, float, …) which are then combined to the final string. The data placeholders are preceded by a percent symbol (%) and followed by single symbol indicating its type (s for string, d for decimal integer, …)

Ex.: ‘This %s has more than %d words.’ [chapter, 20] will result in: “This chapter has more than 20 words.”

Back to the code:

  except
    on E: ESDLNilPointer do
    begin
      WriteLn('Exception ESDLNilPointer: ' + sLineBreak + E.Message);
      Readln;
    end;
  end;

The except-block handles the raised exception. In the example code it just prints the error message we composed on creation of the exception.

  // Clean-up and quit SDL2
  if Assigned(sdlSurface1) then SDL_FreeSurface(sdlSurface1);
  if Assigned(sdlRenderer) then SDL_DestroyRenderer(sdlRenderer);
  if Assigned(sdlWindow1) then SDL_DestroyWindow(sdlWindow1);
  SDL_Quit;
end.

Finally, the resources are free’d.

Combining Pascal exception and SDL2 error handling

The SDL2 error functions are a convenient way to have a good insight in errors related to the SDL2 functions. It builds a powerful symbiosis with Pascal’s exception handling capabilities.

By the way, SDL_SetError(Format string, Arguments) allows to create your own error messages. In Pascal this is usually not necessary though because the message is set in the exception class.

← previous Chapter | next Chapter →

Leave a Reply

Your email address will not be published. Required fields are marked *