andersch.dev

<2024-07-03 Wed>

Platform API

A platform API in software development serves as a boundary between platform-specific code (e.g. calls to the Windows API) and code that can run anywhere.

For simple programs and/or isolated cases the below example of using the preprocessor might suffice. However, this quickly becomes too restrictive and introduces visual noise in the code.

#ifdef PLATFORM_WIN32
  Win32SpecificCall();
#elif PLATFORM_LINUX
  linux_call();
#else
  #error Platform not implemented
#endif

There are two major styles of approaching this problem that basically boil down to abstracting away the platform or abstracting away the application. Both approaches can also be combined.

Architecture 1: Virtualize the OS

In a simple case, the platform API can be a single struct that contains function pointers to all functions that need to be implemented. The application uses these functions for any platform-specific tasks, e.g. opening a file.

/* platform.h */
struct platform_window_t;
struct platform_api_t
{
    b32     (*init)(platform_window_t**);
    file_t  (*file_load)(const char* file_name);
    void    (*file_close)(file_t file);
    void    (*event_loop)(input_t*, platform_window_t*);
    void    (*quit)(platform_window_t*);
};

For encapsulation, the platform API can make use of opaque pointers, such as in the case of above platform_window_t. This way, each platform can decide for itself what data makes up a window.

This gives less flexibility to the platform-specific code, as it has to adhere to a specific API.

Architecture 2: Application as a Service to the OS

A different way of looking at the problem of enabling code to be cross-platform is to consider the application to be the one that is supplying services to the operating system.

In the case of a game for example, these services would be:

  • Return to the OS what needs to be rendered
  • Return to the OS what sounds need to be played
  • Respond to the users input

With this architectural style, the game provides functions for these cases, and the platform layer can decide when to call them.

This method sacrifices flexibility of the application at the benefit of enabling the platform code to be as convoluted as it needs to be (as long as the game's services are called).

Resources