How to run a single instance of an application
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
8 Result := True
9 else
10
11
12
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:
- When there is no other instance of the application running.
- 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:
- We can't pass pointers, only actual data.
- The sending application must allocate and free the memory required for the data.
- The receiving application must not free the data.
- 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).
- 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;
4 Param: string;
5begin
6
7 if Msg.CopyDataStruct.dwData <> cCopyDataWaterMark then
8 raise Exception.Create(
9 'Invalid data structure passed in WM_COPYDATA'
10 );
11
12 PData := Msg.CopyDataStruct.lpData;
13 while PData^ <> #0 do
14 begin
15 Param := PData;
16
17
18 ProcessParam(Param);
19 Inc(PData, Length(Param) + 1);
20 end;
21
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:
- To ensure that any command line parameters are sent to the previous instance.
- 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:
-
TemplateDemo.dpr
(Listing 12)
A project file incorporating the required modifications to the Delphi-generated code.
-
FmTemplate.pas
(Listing 13)
A skeleton form unit containing the various message handlers and other necessary form code. The form itself contains no components.
-
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' ,
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
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
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
10 cWindowClassName = 'DelphiDabbler.SingleApp.Demo';
11
12 cCopyDataWaterMark = $DE1F1DAB;
13
14
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:
- 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.
- 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:
- SingleInst – used to return an instance of the required TSingleInst (or descendant) singleton object.
- 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
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:
- 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).
- 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
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
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.
- 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".
- For bugs in the demo code see the
article-demo
project's README.md
file for details of how to report them.