Running a single instance of our program – Passing command line from one instance to another instance

Some of my programs need to run as single instance. This means that if an instance of my program is already running and the user is trying to start another instance, the second instance should not run. However, before closing the second instance, I want to pass the command line parameters from that second instance to the first instance. This is necessary because the second instance might have been started when the user double clicked a file type that is associated with my program (my program is set to open that file type when that file type is double clicked in explorer).

There is some code that we need to write for this, but not much because my Delphi Light Saber library already provides support for it.
By opening the ccAppData.pas file we see that the TAppData class provides the following public methods:

property  SingleInstClassName: string read FSingleInstClassName;
procedure ResurectInstance(CONST CommandLine: string);
function  InstanceRunning: Boolean;
procedure SetSingleInstanceName(var Params: TCreateParams);
function ExtractData(VAR Msg: TWMCopyData; OUT s: string): Boolean;

How does it work?

During start up, our app uses SetSingleInstanceName to give itself a unique name.
When the user double clicks a file in Explorer (that will open our app) or simply starts our app, we need to check if this is the only instance running in the system or if there is already and instance running. If there is already an instance running, then this second instance needs to close itself. If not, we let this (first) instance run.
We detect an already running instance with InstanceRunning().
But before this second instance closes itself, it needs to send the command line to the first instance. For this we use ResurectInstance. This method uses SendMessage to send a WM_COPYDATA message to the first instance. Inside WM_COPYDATA we put the data we want to send (the command line parameter).

Let’s see the code

We modify the DPR of our project to something like this:

 AppData:= TAppDataEx.Create(‘My cool app’, ctAppWinClassName);

 if AppData.InstanceRunning
 then TAppData.ResurectInstance(ParamStr(1))
 else
  begin
   Application.Initialize;
   Application.CreateForm(TMainForm, MainForm);
   Application.Run;
  end;

ctAppWinClassName is a constant representing our application’s name/ID. This string must be unique! No other application in the system can have the same ID. We use this unique ID in InstanceRunning() to detect if the application is already running, by looking for a window that has the specified class name (ctAppWinClassName):

{ True if an instance of this app was found already running }
function TAppData.InstanceRunning: Boolean;
begin
  Result:= WinApi.Windows.FindWindow(PWideChar(SingleInstClassName), NIL) > 0;
end;

In the MainForm.CreateParams we also need call SetSingleInstanceName to set the class name for our main form:

procedure TMainFrom.CreateParams(var Params: TCreateParams);
begin
inherited;
AppData.SetSingleInstanceName(Params);
end;

The main form also needs to implement this code in WMCopyData. WMCopyData is the message handler that responds to the CopyData message that is sent to our instance when data is passed to it (from the second instance):

{ DATA RECEIVER }
procedure TMainFrom.WMCopyData(var Msg: TWMCopyData);
VAR FileName: string;
begin
if AppData.ExtractData(Msg, FileName)
then Open(FileName)
else inherited;
end;

Yes, the technique might seem complicated. If you don’t fully understand it, no problem: you can still use it with the help from my Light Saber lib. Just modify the program in the three places as shown above. 😊

Warning: We should never use PostMessage() with the WM_COPYDATA message because the data that is passed to the receiving application is only valid during the call. Finally, we need to be aware that the call to SendMessage will not return until the message is processed.

Recommendation:
1. The receiver procedure should also restore (bring to front) that instance.
2. We should close this second instance after we sent the message to the first instance.

Alternatives

Note that the cmMutexSingleInstance.pas implements an alternative way of doing this, this time using a mutex.

Leave a Comment

Scroll to Top