1. Home
  2. Computing & Technology
  3. Delphi Programming

Close Previous Application Instances / Ensure One Instance Running

Don't Activate the Running Instance, Terminate Any Running!

By Zarko Gajic, About.com

A common Delphi question asked too many times all around the Internet is "how to activate the already running instance of my application when the user wants to start another instance"?

Well I have the answer for this question: Controlling the number of application instances.

The article you are reading discusses a similar idea but from a different angle: how to ensure that when a new instance is started, all other (running) instances are terminated?

Terminate Running Instances

If, for whatever the reason, you need to terminate the running instance(s) of your application when the user decides to start a new instance, you need to find a mechanism to notify all the running instances (and optionaly) close them.

Instance Identifier

There are dozens of ways two Delphi applications can communicate - exchange information. One of the most common is to use Windows messaging system to send and receive data.

When a new instance is executed, you might need to pass some data to all the running instances before you make sure they are terminated. Talking about Windows messages + talking about passing data -> conclusion: use WM_CopyData.

Since you will "only" notify all the running instances they should close (using a message box) you cannot be sure that only one instance is already running - maybe an instance will *refuse* to close. To be sure the message is sent to all the running instances you need to enumerate all top level windows (forms). Windows enumeration? Windows Callbacks!

Before I actually show you the source code you need to be aware of one fact: the application sending the message and the application receiving the message is, well, the same application.

Each instance can be assigned a unique identifier, a GUID (Global Unique IDentifier) "number" - you can use this identifier to identify the application instance.

Close Previous Application Instances

Suppose TMainForm is the main form for the application. TMainForm exposes a string field: InstanceId - it's a string representation of a GUID value - unique for each instance.
type
  TMainForm = class(TForm)
    procedure FormCreate(Sender: TObject) ;
  private //methods
    procedure WMCopyData(var Msg : TWMCopyData) ; message WM_COPYDATA;
    procedure SendData(const copyDataStruct: TCopyDataStruct) ;
  private //properties
    InstanceId : string;
  end;
In the OnCreate event handler for the main form you notify all the running instances of the application that a new instance is just executed:
procedure TMainForm.FormCreate(Sender: TObject) ;
var
  copyDataStruct: TCopyDataStruct;
begin
//  InstanceId := TGuidEx.ToString(NewGuid);

  //send guid to all instances
  //you could use the same approach to
  //send/receive back some more complex data

  copyDataStruct.cbData := -1 + Length(InstanceId) ;
  copyDataStruct.lpData := PChar(InstanceId) ;

  NotifyInstances(copyDataStruct) ;
end;
The NotifyInstances sends a string value, in this example the GUID instance identifier, to all the running instances.
procedure TMainForm.NotifyInstances(const copyDataStruct: TCopyDataStruct) ;
var
  instances : TStringList;
  sHandle : string;
  instanceHandle : THandle;
begin
  instances := TStringList.Create;
  try
    EnumWindows(@SendToAllInstances, LParam(instances)) ;
    for sHandle in instances do
    begin
      instanceHandle := StrToInt(sHandle) ;
      if instanceHandle <> self.Handle then
        SendMessage(instanceHandle, WM_COPYDATA, WParam(Handle), Integer(@copyDataStruct)) ;
    end;
  finally
    instances.Free;
  end;
end;
In order to send the message to all the running instances, you first need to find them. By using Windows callbacks along with the EnumWindows API you make sure the message is sent to the "right" window.

The instances list, after the enumeration is done, holds the "Handle" values of the running instances (their main form).

function SendToAllInstances(handle: THandle; List: TStringList): boolean; stdcall;
var
  className: array[0..255] of Char;
begin
  GetClassName(handle, className, SizeOf(className)) ;

  if (string(className) = TMainForm.ClassName) then List.Add(IntToStr(Handle)) ;

  result := true;
end;
Here's the implemetation of the WM_CopyData message method - the one executed when an already running instance is notified about the newly started instance :
//handle message from the instance just being run
procedure TMainForm.WMCopyData(var Msg: TWMCopyData) ;
var
  newInstaceGuidString : string;
begin
  newInstaceGuidString := PChar(Msg.CopyDataStruct.lpData) ;

  //this should always be TRUE
  if (newInstaceGuidString <> InstanceId) then
  begin
    Application.BringToFront;
    if IDYES = Windows.MessageBox(
      Handle,
      'Close this instance?',
      'New instance started!',
      MB_SYSTEMMODAL or MB_SETFOREGROUND or MB_TOPMOST
      or MB_ICONASTERISK or MB_YESNO) then
    begin
      msg.Result := integer(true) ;
      //close main form = close application
      self.Close;
    end;
  end
  else
    msg.Result := integer(false) ;
end;
When the application receives the message, it can inspect the values passed to it (in this case the instance identifier) and notify the user that the application should close. The code above creates a system modal top-most dialog message.

And basically, that does it!

Explore Delphi Programming

More from About.com

  1. Home
  2. Computing & Technology
  3. Delphi Programming
  4. Advanced Delphi Techniques
  5. Close Previous Application Instances / Ensure One Instance Running using Delphi

©2008 About.com, a part of The New York Times Company.

All rights reserved.