On Windows, I use a mutex as a kernel object (it’s the best solution for this platform) and a custom message type to activate the existing game window. For other platforms, I’ll use a similar solution, also based on system objects and their APIs (I hate dependencies).
Below is some code from my game project, modified for the purposes of this example. Snippets are in low level Free Pascal, so you can directly translate them to C and other system languages.
Few constants
In order to correctly create and detect the existence of a mutex and the game window of another instance, we need the mutex name (a GUID, to ensure uniqueness), as well as the window class name (also a GUID, for the same reason) and the window title. We also need to reserve a message code so we can send a message to another instance’s window and activate it:
const
BAS_INSTANCE_MUTEX_NAME = '{829376B4-0B76-427C-AD57-CEF8CDD912CB}'; // Example mutex name (generate your own).
const
BAS_WINDOW_CLASS = '{A5E20472-398E-4329-93EA-F1850290C4E7}'; // Example window class (generate your own).
BAS_WINDOW_TITLE = 'Bastards: When the Nightmare Comes'; // Game window's title.
const
WM_BAS_WINDOW_ACTIVATE = WM_APP + 1; // Request to activate the window from another instance of the game.
Few variables
We need two variables that live for the entire session (in my case, these are local module variables), one holding a mutex handle and the other holding a flag indicating whether the mutex already exists in the system:
var
Mutex: TBas_UInt64; // Mutex handle.
MutexExists: TBas_Bool8; // Specifies if the mutex was created by another game instance.
Opening the instance subsystem
The first step is to initialize the variables, which creates a mutex object and determines whether it is the first one or if another instance has already created it:
function Bas_InstanceOpen (): TBas_Bool8; inline;
begin
Mutex := CreateMutexA(nil, True, BAS_INSTANCE_MUTEX_NAME);
Result := Mutex <> 0;
if Result then
begin
MutexExists := GetLastError() = ERROR_ALREADY_EXISTS;
if MutexExists then
CloseHandle(Mutex);
end;
end;
Quitting the instance subsystem
Executed only if the instance subsystem has been initialized correctly. In my code, I add a pointer to this function to the stack of finalizer functions (more on that later):
procedure Bas_InstanceQuit (); inline;
begin
SDL_UnregisterApp();
CloseHandle(Mutex);
end;
Initialization of the instance subsystem
Currently, the sole purpose of this function is to instruct SDL to register a custom window class. A custom window class is needed to detect and activate the game window of an already running instance (relying solely on the window title is not safe):
function Bas_InstanceInitialize (): TBas_Bool8; inline;
begin
Result := SDL_RegisterApp(BAS_WINDOW_CLASS, 0, nil);
end;
Activating a window of an already running instance
This function is responsible for finding the existing game window based on a custom window class and its title. If the window is located and we obtain its handle, we send a message to it to activate it:
procedure Bas_InstanceActivate (); inline;
var
WindowHandle: TBas_UInt64;
begin
WindowHandle := FindWindowA(BAS_WINDOW_CLASS, BAS_WINDOW_TITLE);
if WindowHandle <> 0 then
PostMessageA(WindowHandle, WM_BAS_WINDOW_ACTIVATE, 0, 0);
end;
Checking for the existence of a mutex
This is a simple getter that returns the status of a mutex (i.e., another game instance), which is stored in a local module variable:
function Bas_InstanceGetExists (): TBas_Bool8; inline;
begin
Result := MutexExists;
end;
Session main code
Below is the order and purpose of executing all the functions listed above, as well as determining whether to continue booting the game (if no other instance exists) or to abort the boot process (if another instance is detected):
function Bas_SessionOpen (): TBas_Bool8;
begin
Result := False;
// If the mutex failed to create, abort booting.
if not Bas_InstanceOpen() then
begin
// Display a system message box that the game instance cannot be properly opened.
exit;
end;
// If there is another instance running, activate it and abort booting new one.
if Bas_InstanceGetExists() then
begin
Bas_InstanceActivate();
exit;
end;
// This is the first game instance. If it cannot be initialized, abort booting.
if Bas_InstanceInitialize() then
Bas_SessionQuitPush(@Bas_InstanceQuit)
else
begin
// Display a system message box that the game instance failed to initialize.
exit;
end;
// This is the first instance and it was initialized, so continue booting.
// ...
end;
First, we attempt to create a global mutex, and if that fails, we abort the boot process. If the mutex is created successfully, we check whether another instance of the game is already running; if so, we activate it and abort the boot of the new instance. If this is the first instance, we register our own window class, push the instance subsystem’s finalization function onto the stack (this isn’t relevant to the example, ignore it), and continue booting.
The Bas_SessionOpen function simply tries to initialize all subsystems of the game. The result of this function determines whether the booting can continue (next step is loading data from the game files, etc.), or whether the booting must be terminated (due to the detection of another instance or the inability to initialize the required subsystems). Just to be clear.
Activating the window of the running instance
All of the above applies to the subsystem responsible for handling instances, detecting another instance, and sending a custom message to it to activate its window. In order for the game instance to receive this message and respond to it, it must be listening for it. To do this, the game window subsystem registers its own callback as a message hook:
function Bas_WindowOpen (): TBas_Bool8;
begin
// ...
SDL_SetWindowsMessageHook(@Bas_WindowCallbackMessage, nil);
// ...
end;
Upon receiving our message, this callback restores the window to the screen and brings it into focus:
function Bas_WindowCallbackMessage (userdata: TBas_Pointer; msg: PMSG): TBas_Bool8; cdecl;
begin
case msg^.message of
// ...
// Custom message with the request to activate the game window.
WM_BAS_WINDOW_ACTIVATE:
begin
// Restore the window if it is minimized (otherwise do not do this, as the window may be maximized).
if SDL_GetWindowFlags(Window.Handle) and SDL_WINDOW_MINIMIZED <> 0 then
SDL_RestoreWindow(Window.Handle);
// Bring the window to the foreground, focus it and flash the taskbar button.
SDL_RaiseWindow (Window.Handle);
SDL_SyncWindow (Window.Handle);
SDL_FlashWindow (Window.Handle, SDL_FLASH_BRIEFLY);
end;
end;
Result := True;
end;
This hook is used in my game to listen for many types of messages, so handling a new (custom) message type simply extends its functionality.
Summary
I know this code seems like a lot, but that’s mainly because I copied it from my own project. In reality, the entire implementation for detecting another instance and activating it can easily fit into 20 lines of code (if you write the code in a single block instead of splitting it into multiple functions).
The solution described has been extensively tested (Windows 10 and 11) and works properly. As a kernel object, a mutex guarantees that there is no race condition—and thus ensures the uniqueness of the instance—while activating the game window in the callback ensures that the window is always restored and brought to the foreground.
And since the game window’s class is a GUID string, it’s virtually impossible that the detected window belongs to an application other than our game.