Skip over navigation

How to run a single instance of an application

Contents

Abstract

This article discusses how to ensure that just a single instance of an application can be run. An application does this by checking if an instance is already running and terminating it if so. We also look at how a duplicate instance can pass its command line parameters to the existing instance before terminating.

Overview

The method we will use to prevent multiple application instances is based on the detection of an application with a known main window class. Once such an application is detected we then pass any command line data via the WM_COPYDATA message.

Listing 1 outlines the methodology we will be using in pseudo-code:

search for a top level window of the correct class
if such a window exists
  activate the window
  pass any command line data to the window using WM_COPYDATA
  terminate this application instance
else
  start this application as normal
  process the command line parameters
end
Listing 1

Alternatives

Mutexes can be used to detect muliple application instances and memory mapped files can be used to exchange data. These techniques are not persued here.

An Initial Approach

Finding if your program is running

We use the FindWindow Windows API routine to search for a top level window of the required class. This routine can check for window captions, (unreliable because they can change while a program is running), the window class name (better), or both. We opt to check for the window class name regardless of the caption.

There is a problem with this approach though – by default Delphi uses a form's class name as the name of the associated window class. For example, if a form's class is named TForm1 then its window class name is also TForm1. Since it's possible to have two or more different Delphi applications running at the same time, both of which use this window class name we need to give our program's main window a class name that is extremely unlikely to be used by another application. We do this by overriding the main form's CreateParams method as follows:

  1procedure TForm1.CreateParams(var Params: TCreateParams);
  2begin
  3  inherited;
  4  StrCopy(Params.WinClassName, cWindowClassName);
  5end;
Listing 2

cWindowClassName is a constant set to some suitable class name. To ensure uniqueness we could use a GUID. However I find that a name based on company name, the name of the application and possibly the major version number is human friendly and is sufficiently unlikely to be used by other applications. An example is DelphiDabbler.MyApp.2.

Now that we have a suitable window class name we can find the main window handle of any already existing instance of our application by using the following function:

  1function FindDuplicateMainWdw: HWND;
  2begin
  3  Result := FindWindow(cWindowClassName, nil);
  4end;
Listing 3

The function will return the handle of any top level window with the required class name or it will return 0 if there is no such window. So, if the function returns 0 we deduce that there is no pre-existing instance and can run our application. But, if the function returns a non zero value an existing instance is already running and we should terminate our current instance.

We need to be very careful where we place this test – if we run the check after we have created our application's main window then the function will always return a window handle, and this may be the handle of our own window! Therefore it is important to run this check before the application creates its main form. We can do this by performing the check in the project (.dpr) file, before the main form is created. The project file can be modified as follows:

  1function CanStart: Boolean;
  2var
  3  Wdw: HWND;
  4begin
  5  Wdw := FindDuplicateMainWdw;
  6  if Wdw = 0 then
  7    // no instance running: we can start our app
  8    Result := True
  9  else
 10    // instance running: try to pass command line to it
 11    // terminate this instance if this succeeds
 12    // (SwitchToPrevInst routine explained later)
 13    Result := not SwitchToPrevInst(Wdw);
 14end;
 15
 16begin
 17  if CanStart then
 18  begin
 19    Application.Initialize;
 20    Application.CreateForm(TForm1, Form1);
 21    Application.Run;
 22  end;
 23end.
Listing 4

As can be seen the application start-up code calls CanStart to determine whether this instance of the program should be started as normal. If CanStart returns true then the normal application start-up code is executed as normal. On the other hand if CanStart returns false then the application terminates straight away.

There are two circumstances under which CanStart returns true:

  1. When there is no other instance of the application running.
  2. When there is another instance running but for some reason we can't pass any command line parameters on to it. In this case we start our instance as well to ensure that the command line parameters get processed.

In the 2nd case we attempt to switch to the already running instance by calling the SwitchToPrevInst function. We will discuss this function later in the article (see Listing 11). For now it suffices to know that the function activates the main window of the previous application instance and attempts to pass any command line parameters to it. SwitchToPrevInst returns true if this attempt succeeds and false if it fails.

That just about wraps up how to start a single instance of the application. Now we need find out how to pass any command line data to a previous instance.

Passing the command line to a previous instance

When the program detects that another instance is running it must pass its command line parameters (if any) to that program before terminating itself. We will create a routine, SendParamsToPrevInst, to take care of this. This routine will send the parameters to the main window of the existing application instance by means of a WM_COPYDATA message.

Passing data to another application with WM_COPYDATA

This message is provided by Windows for the purpose of passing data across process boundaries (i.e. for sending data to other applications). Because the message sends data between process address spaces we have to be careful to adhere the rules governing its use. The rules are:

  1. We can't pass pointers, only actual data.
  2. The sending application must allocate and free the memory required for the data.
  3. The receiving application must not free the data.
  4. If the receiving application needs to access the data after the message handler has returned it must copy the data to local storage while still within the handler. This is because the data's memory will be freed when the handler returns (rule 2).
  5. We must use SendMessage and not PostMessage to send the message.

The WM_COPYDATA message uses a structure of type TCopyDataStruct to store and describe the data to be sent. This structure has three fields:

  • dwData: DWORD – holds a user defined 32 bit value.
  • lpData: Pointer – points to a user defined data block (used if more than 32 bits of data are to be sent).
  • cbData: DWORD – is the size of the user defined data block in bytes (zero if the data block is not used).

SendParamsToPrevInst function

As already noted, the SendParamsToPrevInst function is used when we have to pass command line parameters to a previous application instance. The function returns true if the receiving application processes the WM_COPYDATA message properly and false if the message is not handled, or if the receiving application reports an error. Listing 5 gives the routine's implementation.

  1function SendParamsToPrevInst(Wdw: HWND): Boolean;
  2var
  3  CopyData: TCopyDataStruct;
  4  I: Integer;
  5  CharCount: Integer;
  6  Data: PChar;
  7  PData: PChar;
  8begin
  9  CharCount := 0;
 10  for I := 1 to ParamCount do
 11    Inc(CharCount, Length(ParamStr(I)) + 1);
 12  Inc(CharCount);
 13  Data := StrAlloc(CharCount);
 14  try
 15    PData := Data;
 16    for I := 1 to ParamCount do
 17    begin
 18      StrPCopy(PData, ParamStr(I));
 19      Inc(PData, Length(ParamStr(I)) + 1);
 20    end;
 21    PData^ := #0;
 22    CopyData.lpData := Data;
 23    CopyData.cbData := CharCount * SizeOf(Char);
 24    CopyData.dwData := cCopyDataWaterMark;
 25    Result := SendMessage(
 26      Wdw, WM_COPYDATA, 0, LPARAM(@CopyData)
 27    ) = 1;
 28  finally
 29    StrDispose(Data);
 30  end;
 31end;
Listing 5

The function first bundles the command line parameters into a single data structure. The data structure we have chosen is a #0 separated sequence of the program's parameter strings, terminated by a further #0 character. For example the three parameters:

'One', 'Two' and 'Three'

would be stored as

One#0Two#0Three#0#0

Having allocated the required memory and stored the data we set TCopyDataStruct.lpData to point to it before recording the size of the data structure in TCopyDataStruct.cbData.

Unicode strings

SendParamsToPrevInst works correctly with both ANSI and Unicode strings.

Note that the CharCount local variable counts characters, not bytes. This character count is what we need when we call StrAlloc to allocate storage for the parameter strings because it accepts the length of the required string in characters as its parameter.

However, the cbData field of TCopyDataStruct requires the size of the data in bytes, so we multiply CharCount by SizeOf(Char) to get the required size.

When Unicode strings are being used, references to the #0 character above should be read as the WideChar zero character #00.

We use the TCopyDataStruct.dwData field to store a 32 bit "watermark" value that we use to validate the WM_COPYDATA message. While the use of such a watermark is not essential, it does provide a reality check on the data and indicates to the receiving application that the message and its data are legitimate. The watermark can be any 32 bit value – we use the value stored in the cCopyDataWaterMark constant (not defined in the listing).

SendMessage is now used to send the message to the existing application's main window. The address of the TCopyData structure is passed as the message's LParam. We set the function result to True if the message returns 1 and False otherwise.

Finally, after SendMessage has returned, we free the memory used to store the command line parameters.

The receiving application needs to know how to extract the data from the WM_COPYDATA message. We discuss how to do this next.

Handling WM_COPYDATA

As well as knowing how to send data to other instances of itself, our application also needs to know how to handle the WM_COPYDATA messsage and to decode data received from another instance. We do this in the program's main form by writing a standard Delphi message handler, which is declared in the form's class declaration as per Listing 6. Listing 7 shows the implementation of the message handler.

  1private
  2  // ...
  3  procedure WMCopyData(var Msg: TWMCopyData);
  4    message WM_COPYDATA;
  5  // ...
Listing 6
  1procedure TForm1.WMCopyData(var Msg: TWMCopyData);
  2var
  3  PData: PChar;  // walks thru data
  4  Param: string; // a parameter
  5begin
  6  // check watermark is valid
  7  if Msg.CopyDataStruct.dwData <> cCopyDataWaterMark then
  8    raise Exception.Create(
  9      'Invalid data structure passed in WM_COPYDATA'
 10    );
 11  // extract strings from data
 12  PData := Msg.CopyDataStruct.lpData;
 13  while PData^ <> #0 do
 14  begin
 15    Param := PData;
 16    // process the parameter:
 17    // (write this method to do required processing)
 18    ProcessParam(Param);
 19    Inc(PData, Length(Param) + 1);
 20  end;
 21  // set return value to indicate we handled message
 22  Msg.Result := 1;
 23end;
Listing 7

This message handler processes the data referenced by the TCopyDataStruct data structure. The WM_COPYDATA message has a pointer to the record in its LParam field. Rather than accessing the LParam value directly (and type casting) we use TWMCopyData to "crack" the message record – its CopyDataStruct field provides easy access to the data structure.

We first check the watermark stored in the structure's dwData member, raising an exception if it is not valid. Then we get a pointer to the #0 delimited list of parameters and walk the buffer, making a copy of each parameter before passing it to the ProcessParam method (which is just a placeholder for whatever processing is to be applied to each parameter). The loop ends when a terminating #0 character is found. Before returning we set the message result to 1 to indicate it has been handled successfully.

† In production code you would want to wrap the code the TForm1.WMCopyData in a try … except block to swallow any exceptions.

Activating the previous instance

At the time a previous instance of an application is activated by our new instance there is no way of telling whether its main window is displayed normally, maximized, minimized or invisible. The window may be partially or fully hidden behind other windows. If it is obscured there will be no visual feedback to the user of the application they believe they started. There are various different design decisions that could be taken to handle these situations. We will opt to ensure the window it is made prominent when it is activated. If the window is shown normally or is maximised we simply bring it to the front of the z-order. If, however, the window is minimized we restore it and if it is hidden we show it.

When an application starts and finds there is a previous instance, the previous instance's main window is sent a custom message telling it to "wake up". This message is defined in Listing 8:

  1const
  2  UM_ENSURERESTORED = WM_USER + 1;
Listing 8

We send this message in the SwitchToPrevInst routine mentioned earlier (see Listing 11).

The receiving application must handle and act on this message. We do this in the application's main form, again using a Delphi message handler. The handler's declaration is shown in Listing 9 and it's implementation is in Listing 10.

  1private
  2  // ...
  3  procedure UMEnsureRestored(var Msg: TMessage);
  4    message UM_ENSURERESTORED;
  5  // ...
Listing 9
  1procedure TForm1.UMEnsureRestored(var Msg: TMessage);
  2begin
  3  if IsIconic(Application.Handle) then
  4    Application.Restore;
  5  if not Visible then
  6    Visible := True;
  7  Application.BringToFront;
  8  SetForegroundWindow(Self.Handle);
  9end;
Listing 10

In the message handler we check to see if the application is minimized and restore it if so. We then ensure that the main form is visible. Next we bring the main form to the front of the z-order before calling the SetForegroundWindow API function to bring our window to the fore.

Using Application.MainFormOnTaskbar

If you have set Application.MainFormOnTaskbar to True in your project then change IsIconic(Application.Handle) to IsIconic(Application.MainForm.Handle) in Listing 10.

Thanks to Ron Tadmor for pointing this out.

SetForegroundWindow Issues

Microsoft's documentation of this function indicates that it may not always actually bring the Window to the fore – the circumstances when it may not do this are quite complex. Please check the function's documentation on MSDN for further details.

Finishing touches

It only remains to implement the SwitchToPrevInst function we referred to earlier in the article. This function has two main purposes:

  1. To ensure that any command line parameters are sent to the previous instance.
  2. To activate the previous instance, ensuring its main window is prominently displayed.

The handle of the main window of the previous application instance is passed to the function as a parameter. This handle must represent an actual window, so it can't be zero. Recall that we must return True if parameters are successfully sent to the previous instance and False on error. Listing 11 has the implementation of SwitchToPrevInst:

  1function SwitchToPrevInst(Wdw: HWND): Boolean;
  2begin
  3  Assert(Wdw <> 0);
  4  if ParamCount > 0 then
  5    Result := SendParamsToPrevInst(Wdw)
  6  else
  7    Result := True;
  8  if Result then
  9    SendMessage(Wdw, UM_ENSURERESTORED, 0, 0);
 10end;
Listing 11

The first thing to note is that this function returns True only if command line parameters were sent to the previous instance successfully or if there are no parameters to send. We use SendParamsToPrevInst (Listing 5) to send any parameters to the previous instance. That routine also returns True on success so we use its return value as our result. If we have successfully transferred any command line parameters (or there are none) we must activate the previous instance. We do this by sending it a UM_ENSURERESTORED message. As we have seen in Listing 10 the previous instance must respond to this message by ensuring its main window is restored and active.

Putting it all together

In this section we present the code of a skeleton Delphi project that draws together all the code discussed in this article. The code can be used as a template for a real application. The project comprises three modules:

  1. TemplateDemo.dpr (Listing 12)

    A project file incorporating the required modifications to the Delphi-generated code.

  2. FmTemplate.pas (Listing 13)

    A skeleton form unit containing the various message handlers and other necessary form code. The form itself contains no components.

  3. UStartup.pas (Listing 14)

    A unit containing the various functions used to manage and activate the single application instance.

These modules are all included in the demo code that accompanies this article.

  1program TemplateDemo;
  2
  3uses
  4  Forms,
  5  Windows,
  6  FmTemplate in 'FmTemplate.pas' {Form1},
  7  UStartup in 'UStartup.pas';
  8
  9{$R *.res}
 10
 11function CanStart: Boolean;
 12var
 13  Wdw: HWND;
 14begin
 15  Wdw := FindDuplicateMainWdw;
 16  if Wdw = 0 then
 17    Result := True
 18  else
 19    Result := not SwitchToPrevInst(Wdw);
 20end;
 21
 22begin
 23  if CanStart then
 24  begin
 25    Application.Initialize;
 26    Application.CreateForm(TForm1, Form1);
 27    Application.Run;
 28  end;
 29end.
Listing 12
  1unit FmTemplate;
  2
  3interface
  4
  5uses
  6  Windows, Messages, SysUtils, Controls, Forms,
  7  UStartup;
  8
  9type
 10  TForm1 = class(TForm)
 11    procedure FormCreate(Sender: TObject);
 12  private
 13    procedure ProcessParam(const Param: string);
 14    procedure UMEnsureRestored(var Msg: TMessage);
 15      message UM_ENSURERESTORED;
 16    procedure WMCopyData(var Msg: TWMCopyData);
 17      message WM_COPYDATA;
 18  protected
 19    procedure CreateParams(var Params: TCreateParams);
 20      override;
 21  public
 22  end;
 23
 24var
 25  Form1: TForm1;
 26
 27implementation
 28
 29{$R *.dfm}
 30
 31{ TForm1 }
 32
 33procedure TForm1.CreateParams(var Params: TCreateParams);
 34begin
 35  inherited;
 36  StrCopy(Params.WinClassName, cWindowClassName);
 37end;
 38
 39procedure TForm1.FormCreate(Sender: TObject);
 40var
 41  I: Integer;
 42begin
 43  for I := 1 to ParamCount do
 44    ProcessParam(ParamStr(I));
 45end;
 46
 47procedure TForm1.ProcessParam(const Param: string);
 48begin
 49  { TODO : Code to process a parameter }
 50end;
 51
 52procedure TForm1.UMEnsureRestored(var Msg: TMessage);
 53begin
 54  if IsIconic(Application.Handle) then
 55    Application.Restore;
 56  if not Visible then
 57    Visible := True;
 58  Application.BringToFront;
 59  SetForegroundWindow(Self.Handle);
 60end;
 61
 62procedure TForm1.WMCopyData(var Msg: TWMCopyData);
 63var
 64  PData: PChar;
 65  Param: string;
 66begin
 67  if Msg.CopyDataStruct.dwData <> cCopyDataWaterMark then
 68    raise Exception.Create(
 69      'Invalid data structure passed in WM_COPYDATA'
 70    );
 71  PData := Msg.CopyDataStruct.lpData;
 72  while PData^ <> #0 do
 73  begin
 74    Param := PData;
 75    ProcessParam(Param);
 76    Inc(PData, Length(Param) + 1);
 77  end;
 78  Msg.Result := 1;
 79end;
 80
 81end.
Listing 13
  1unit UStartup;
  2
  3interface
  4
  5uses
  6  Windows, Messages;
  7
  8const
  9  // Name of main window class
 10  cWindowClassName = 'DelphiDabbler.SingleApp.Demo';
 11  // Any 32 bit number here to perform check on copied data
 12  cCopyDataWaterMark = $DE1F1DAB;
 13  // User window message handled by main form ensures that
 14  // app not minimized or hidden and is in foreground
 15  UM_ENSURERESTORED = WM_USER + 1;
 16
 17function FindDuplicateMainWdw: HWND;
 18function SwitchToPrevInst(Wdw: HWND): Boolean;
 19
 20implementation
 21
 22uses
 23  SysUtils;
 24
 25function SendParamsToPrevInst(Wdw: HWND): Boolean;
 26var
 27  CopyData: TCopyDataStruct;
 28  I: Integer;
 29  CharCount: Integer;
 30  Data: PChar;
 31  PData: PChar;
 32begin
 33  CharCount := 0;
 34  for I := 1 to ParamCount do
 35    Inc(CharCount, Length(ParamStr(I)) + 1);
 36  Inc(CharCount);
 37  Data := StrAlloc(CharCount);
 38  try
 39    PData := Data;
 40    for I := 1 to ParamCount do
 41    begin
 42      StrPCopy(PData, ParamStr(I));
 43      Inc(PData, Length(ParamStr(I)) + 1);
 44    end;
 45    PData^ := #0;
 46    CopyData.lpData := Data;
 47    CopyData.cbData := CharCount * SizeOf(Char);
 48    CopyData.dwData := cCopyDataWaterMark;
 49    Result := SendMessage(
 50      Wdw, WM_COPYDATA, 0, LPARAM(@CopyData)
 51    ) = 1;
 52  finally
 53    StrDispose(Data);
 54  end;
 55end;
 56
 57function FindDuplicateMainWdw: HWND;
 58begin
 59  Result := FindWindow(cWindowClassName, nil);
 60end;
 61
 62function SwitchToPrevInst(Wdw: HWND): Boolean;
 63begin
 64  Assert(Wdw <> 0);
 65  if ParamCount > 0 then
 66    Result := SendParamsToPrevInst(Wdw)
 67  else
 68    Result := True;
 69  if Result then
 70    SendMessage(Wdw, UM_ENSURERESTORED, 0, 0);
 71end;
 72
 73end.
Listing 14

The above approach to the problem works and provides a template for use in any application. However the source code needs to be ammended for each application. This is crying out for a reusable, preferably object oriented, solution – but can we find one?

Well partly! In the next section we develop at an object oriented solution that, while still requiring some modification of the main form and project file, does at least move most of the code to an extensible class.

An object oriented solution

Overview

In this section we present an object oriented, extensible, solution to our problem. Most of the solution's code can be found in a unit named USingleInst.pas.

Our solution hinges around a singleton object, of base type TSingleInst, that determines whether or not an application is the only instance running. If there is already an instance of the application running when a second copy is started the object passes the second copy's command line to the original instance before terminating the second copy. The object also names the main form's window class and can supply a watermark to be used to test the integrity of WM_COPYDATA messages.

Users are expected to override TSingleInst in order to provide a suitably strong window class name for the main form and, optionally, to provide a watermark. We must register the newly derived class so that a singleton of the correct type can be created. We will provide a routine, RegisterSingleInstClass, to perform the registration. Our singleton is always accessed via the SingleInst function, which creates the object the first time the function is called. It is apparent, therefore, that we must register our TSingleInst descendant before we access the singleton for the first time. The safest way to accomplish this is by placing our call to RegisterSingleInstClass in the initialization section of the unit where the derived class is implemented.

Minimal modifications must still be made to the application's project file and the main form. In each case we simply call one of the singletion object's methods.

To summarise then, the procedure for enabling single application instance support in a project using USingleInst.pas is as follows:

  • Add USingleInst.pas to the project.
  • Create a new class derived from TSingleInst and override the methods that return the window class name and the watermark.
  • Register the new class as the one to be used when creating the SingleInst singleton object by adding a call to RegisterSingleInstClass in the initialization section of the derived class's unit.
  • Modify the main form and project sources to call relevant methods in the SingleInst object.

In the rest of this section we will first review the USingleInst unit, then look at how to derive and register a TSingleInst descendant class. Finally we will describe the changes to be made to the project and main form source files.

The USingleInst unit

The full source of this unit is included in the demo project that accompanies this article.

Let us begin with an examination of Listing 15, where the interface part of the unit is presented.

  1unit USingleInst;
  2
  3interface
  4
  5uses
  6  Windows, Controls, Messages;
  7
  8type
  9  TSingleInstParamHandler =
 10    procedure(const Param: string) of object;
 11
 12  TSingleInstClass = class of TSingleInst;
 13
 14  TSingleInst = class(TObject)
 15  private
 16    fOnProcessParam: TSingleInstParamHandler;
 17    fEnsureRestoreMsg: UINT;
 18  protected
 19    function WdwClassName: string; virtual;
 20    function WaterMark: DWORD; virtual;
 21    function FindDuplicateMainWdw: HWND; virtual;
 22    function SendParamsToPrevInst(Wdw: HWND): Boolean;
 23      virtual;
 24    function SwitchToPrevInst(Wdw: HWND): Boolean;
 25    procedure EnsureRestore(Wdw: HWND; var Msg: TMessage);
 26      dynamic;
 27    procedure WMCopyData(var Msg: TMessage); dynamic;
 28  public
 29    constructor Create;
 30    procedure CreateParams(var Params: TCreateParams);
 31    function HandleMessages(Wdw: HWND;
 32      var Msg: TMessage): Boolean;
 33    function CanStartApp: Boolean;
 34    property OnProcessParam: TSingleInstParamHandler
 35      read fOnProcessParam write fOnProcessParam;
 36  end;
 37
 38function SingleInst: TSingleInst;
 39
 40procedure RegisterSingleInstClass(Cls: TSingleInstClass);
Listing 15

TSingleInstParamHandler is the type of the OnProcessParam event discussed below. TSingleInstClass is the class type of TSingleInst and any derived classes.

Moving on to the TSingleInst class, we have two fields:

  1. fOnProcessParam – stores the user defined handler for the OnProcessParam event. This event is triggered once for each parameter that was passed to the application via a WM_COPYDATA message.
  2. fEnsureRestoreMsg – stores the id of a custom message used to ask an application to restore and display its main window. The message id is provided by Windows and guaranteed to be unique. Unfortunately, because the message is not a constant we can't use a Delphi message method to handle it. Instead we have to resort to intercepting messages from the message loop (see later).

The methods of TSingleInst will be discussed in detail as we review their implementation.

Following the TSingleInst declaration, we declare two functions, both of which we have already discussed in the Overview:

  1. SingleInst – used to return an instance of the required TSingleInst (or descendant) singleton object.
  2. RegisterSingleInstClass – called to register the TSingleInst descendant class that is to be used to ceate the singleton.

The source code for these functions, along with the declaration of two global variables required by the functions, is shown in Listing 16.

  1implementation
  2
  3uses
  4  SysUtils, Forms;
  5
  6var
  7  // Globals storing singleton and class of SingleInst
  8  gSingleInst: TSingleInst = nil;
  9  gSingleInstClass: TSingleInstClass = nil;
 10
 11function SingleInst: TSingleInst;
 12begin
 13  if not Assigned(gSingleInst) then
 14  begin
 15    if Assigned(gSingleInstClass) then
 16      gSingleInst := gSingleInstClass.Create
 17    else
 18      gSingleInst := TSingleInst.Create;
 19  end;
 20  Result := gSingleInst;
 21end;
 22
 23procedure RegisterSingleInstClass(Cls: TSingleInstClass);
 24begin
 25  gSingleInstClass := Cls;
 26end;
Listing 16

The private global variables gSingleInst and gSingleInstClass store a reference to the singleton object and the type of the singleton object respectively.

Class variables

In later versions of Delphi, gSingleInst and gSingleInstClass could be made into class variables of type TSingleInst and the functions could become class methods.

As already noted, SingleInst creates the singleton object the first time the function is referenced. If a derived class has been registered then this class is used to create the singleton, otherwise the base class is used. RegisterSingleInstClass simply stores the class reference passed to it.

Moving on to the class definition, we first look at the constructor which simply registers the custom message with Windows, as Listing 17 reveals.

  1constructor TSingleInst.Create;
  2begin
  3  inherited;
  4  fEnsureRestoreMsg := RegisterWindowMessage(
  5    'USINGLEINST_ENSURERESTORE'
  6  );
  7end;
Listing 17

There are various "entry points" into the class that must be called from either the main unit or the project file. Listing 18 has the details:

  1function TSingleInst.CanStartApp: Boolean;
  2var
  3  Wdw: HWND;
  4begin
  5  Wdw := FindDuplicateMainWdw;
  6  if Wdw = 0 then
  7    Result := True
  8  else
  9    Result := not SwitchToPrevInst(Wdw);
 10end;
 11
 12procedure TSingleInst.CreateParams(
 13  var Params: TCreateParams);
 14begin
 15  inherited;
 16  StrPLCopy(
 17    Params.WinClassName,
 18    WdwClassName,
 19    SizeOf(Params.WinClassName) div SizeOf(Char) - 1
 20  );
 21end;
 22
 23function TSingleInst.HandleMessages(Wdw: HWND;
 24  var Msg: TMessage): Boolean;
 25begin
 26  if Msg.Msg = WM_COPYDATA then
 27  begin
 28    WMCopyData(Msg);
 29    Result := True;
 30  end
 31  else if Msg.Msg = fEnsureRestoreMsg then
 32  begin
 33    EnsureRestore(Wdw, Msg);
 34    Result := True;
 35  end
 36  else
 37    Result := False;
 38end;
Listing 18

The first of these "entry points" is CanStartApp which is called from the project file and indicates whether the application should be started or not. It works in a very similar way to the CanStart function we saw earlier.

The CreateParams method sets up the main form's window class name by updating the form's window creation parameters. The main form must override its inherited TForm.CreateParams method and call SingleInst.CreateParams from within that method.

The most complex of the "entry point" methods is the HandleMessages method. This intercepts WM_COPYDATA and the custom fEnsureRestoreMsg messages from the main form and handles the processing by delegating to WMCopyData and EnsureRestore respectively. This method returns true if it handles a message and false otherwise. The main form must override the TForm.WndProc method and call SingleInst.HandleMessages from there, calling its inherited TForm.WndProc method if HandleMessages returns false.

Let us now examine the protected helper methods. Firstly, Listing 19 shows two methods that should be overridden in descendant classes:

  1function TSingleInst.WdwClassName: string;
  2begin
  3  Result := 'SingleInst.MainWdw';
  4end;
  5
  6function TSingleInst.WaterMark: DWORD;
  7begin
  8  Result := 0;
  9end;
Listing 19

WdwClassName simply returns the name of the main form's window class. This should be overridden to return some unique value to the application. Similarly, Watermark returns 0. Again, this method should be overridden to return some unusual value if watermarks are to be used.

Next up, in Listing 20, are two methods that assist in handling messages intercepted in the main form:

  1procedure TSingleInst.EnsureRestore(Wdw: HWND;
  2  var Msg: TMessage);
  3begin
  4  if IsIconic(Application.Handle) then
  5    Application.Restore;
  6  if Assigned(Application.MainForm)
  7    and not Application.MainForm.Visible then
  8    Application.MainForm.Visible := True;
  9  Application.BringToFront;
 10  SetForegroundWindow(Wdw);
 11end;
 12
 13procedure TSingleInst.WMCopyData(var Msg: TMessage);
 14var
 15  PData: PChar;
 16  Param: string;
 17begin
 18  if TWMCopyData(Msg).CopyDataStruct.dwData = WaterMark then
 19  begin
 20    PData := TWMCopyData(Msg).CopyDataStruct.lpData;
 21    while PData^ <> #0 do
 22    begin
 23      Param := PData;
 24      if Assigned(fOnProcessParam) then
 25        fOnProcessParam(Param);
 26      Inc(PData, Length(Param) + 1);
 27    end;
 28    Msg.Result := 1;
 29  end
 30  else
 31    Msg.Result := 0;
 32end;
Listing 20

Notice that these methods are based closely on the UMEnsureRestored and WMCopyData message handler methods that were discussed earlier. Note though that the methods are no longer message handlers – they are now dynamic methods. There are other changes to the methods that should be noted:

  1. EnsureRestore is now passed a handle to the window to be brought to the foreground. (this will be the window handle of the main form).
  2. WMCopyData now triggers the OnProcessParam event rather than calling a hard-wired method to process parameters.

Finally we examine the three remaining protected methods:

  1function TSingleInst.FindDuplicateMainWdw: HWND;
  2begin
  3  Result := FindWindow(PChar(WdwClassName), nil);
  4end;
  5
  6function TSingleInst.SendParamsToPrevInst(
  7  Wdw: HWND): Boolean;
  8var
  9  CopyData: TCopyDataStruct;
 10  I: Integer;
 11  CharCount: Integer;
 12  Data: PChar;
 13  PData: PChar;
 14begin
 15  CharCount := 0;
 16  for I := 1 to ParamCount do
 17    Inc(CharCount, Length(ParamStr(I)) + 1);
 18  Inc(CharCount);
 19  Data := StrAlloc(CharCount);
 20  try
 21    PData := Data;
 22    for I := 1 to ParamCount do
 23    begin
 24      StrPCopy(PData, ParamStr(I));
 25      Inc(PData, Length(ParamStr(I)) + 1);
 26    end;
 27    PData^ := #0;
 28    CopyData.lpData := Data;
 29    CopyData.cbData := CharCount * SizeOf(Char);
 30    CopyData.dwData := WaterMark;
 31    Result := SendMessage(
 32      Wdw, WM_COPYDATA, 0, LPARAM(@CopyData)
 33    ) = 1;
 34  finally
 35    StrDispose(Data);
 36  end;
 37end;
 38
 39function TSingleInst.SwitchToPrevInst(Wdw: HWND): Boolean;
 40begin
 41  Assert(Wdw <> 0);
 42  if ParamCount > 0 then
 43    Result := SendParamsToPrevInst(Wdw)
 44  else
 45    Result := True;
 46  if Result then
 47    SendMessage(Wdw, fEnsureRestoreMsg, 0, 0);
 48end;
Listing 21

These methods are analogues of the routines of the same name in the previous section, with the following differences:

  • FindDuplicateMainWdw gets the the name of the required window class from the WdwClassName method rather than a constant.
  • SendParamsToPrevInst gets its watermark from the Watermark method rather than a constant.
  • SwitchToPrevInst sends the registered message identified by fEnsureRestoreMsg to the main form window rather the hard-wired UM_ENSURERESTORED message.

Modifications to the project file

We must make a simple change to the project file so that it calls the CanStartApp method of the SingleInst singleton object to check whether the application can be started. First of all add USingleInst to the project then change the project file as shown in Listing 22:

  1...
  2begin
  3  if SingleInst.CanStartApp then
  4  begin
  5    Application.Initialize;
  6    Application.CreateForm(TForm1, Form1);
  7    Application.Run;
  8  end;
  9end.
Listing 22

Modifications to the main form

Slightly more work needs to be done in the main form unit. First, add the following declarations to the form class's protected section:

  1  protected
  2    procedure WndProc(var Msg: TMessage); override;
  3    procedure CreateParams(
  4      var Params: TCreateParams); override;
Listing 23

Now implement these methods to call into SingleInst as per Listing 24:

  1procedure TForm1.CreateParams(var Params: TCreateParams);
  2begin
  3  inherited;
  4  SingleInst.CreateParams(Params);
  5end;
  6
  7procedure TForm1.WndProc(var Msg: TMessage);
  8begin
  9  if not SingleInst.HandleMessages(Msg) then
 10    inherited;
 11end;
Listing 24

CreateParams calls its ancestor method then calls SingleInst.CreateParams. This method sets the window class name.

The overridden WndProc method calls SingleInst.HandleMessages which checks if the message is one of those handled by SingleInst. The method returns true if the message was handled and false otherwise. If the message was not handled then the message is passed to the forms's inherited WndProc method.

We also need to handle the TSingleInst.OnProcessParam event so the application gets notified when parameters are passed to it via the WM_COPYDATA message. We define a new method, HandleParam to handle this event. This method is declared in the form's protected section as per Listing 25:

  1  protected
  2    procedure HandleParam(const Param: string);
Listing 25

HandleParam is where we add whatever processing we wish to do with the parameters. For example, if we just wanted to display the parameters in a list box (say ListBox1), we could implement the method as follows:

  1procedure TForm1.HandleParam(const Param: string);
  2begin
  3  ListBox1.Items.Add(Param);
  4end;
Listing 26

We assign the event handler in FormCreate, where we also process any parameters passed to the program when it first starts:

  1procedure TForm1.FormCreate(Sender: TObject);
  2var
  3  I: Integer;
  4begin
  5  SingleInst.OnProcessParam := HandleParam;
  6  for I := 1 to ParamCount do
  7    HandleParam(ParamStr(I));
  8end;
Listing 27

Overriding TSingleInst

We now look at an example of how to override TSingleInst. The base class was designed so that we only need to override the WdwClassName and Watermark methods to provide a unique windows class name and stronger watermark.

The whole unit (UMySingleInst.pas) is listed below. The only point of note is how the new class is registered so that it is used to create the SingleInst singleton object.

  1unit UMySingleInst;
  2
  3interface
  4
  5uses
  6  Windows,
  7  USingleInst;
  8
  9type
 10  TMySingleInst = class(TSingleInst)
 11  protected
 12    function WdwClassName: string; override;
 13    function WaterMark: DWORD; override;
 14  end;
 15
 16implementation
 17
 18{ TMySingleInst }
 19
 20function TMySingleInst.WaterMark: DWORD;
 21begin
 22  Result := $DE1F1DAB;
 23end;
 24
 25function TMySingleInst.WdwClassName: string;
 26begin
 27  Result := 'DelphiDabbler.SingleInst.1';
 28end;
 29
 30initialization
 31
 32// Make sure we use this class for the singleton
 33RegisterSingleInstClass(TMySingleInst);
 34
 35end.
Listing 28

And that completes the presentation of the object oriented solution.

Demonstration code

A demo program to accompany this article can be found in the delphidabbler/article-demos Git repository on GitHub.

You can view the code in the article-13 sub-directory. Alternatively download a zip file containing all the demos by going to the repository's landing page and clicking the Clone or download button and selecting Download ZIP.

See the demo's README.md file for details.

This source code is merely a proof of concept and is intended only to illustrate this article. It is not designed for use in its current form in finished applications. The code is provided on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.

The demo is open source. See the demo's LICENSE.md file for licensing details.

Conclusion

This article has demonstrated how to ensure that only a single instance of an application is run. We have also shown how to pass command line parameters to an already running instance, ensuring its window is displayed prominently.

We detected previous instances by searching for the application's main window class among the top level windows. Communication with a previous instance was by means of messages sent to the instance's main window. In particular, a user defined message was used to ensure the window was restored and visible and the WM_COPYDATA message was used to send the command line parameters across the process boundary.

Skeletal source code for an application that runs as a single instance was developed. A reusable class was then presented that can be used in developing such a program.

Demonstration code incorporating the source code developed in the article was made available.

Feedback

I hope you found this article useful.

If you have any observations, comments, or have found any errors there are two places you can report them.

  1. For anything to do with the article content, but not the downloadable demo code, please use this website's Issues page on GitHub. Make sure you mention that the issue relates to "article #13".
  2. For bugs in the demo code see the article-demo project's README.md file for details of how to report them.