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.typeIn the OnCreate event handler for the main form you notify all the running instances of the application that a new instance is just executed:
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;
procedure TMainForm.FormCreate(Sender: TObject) ;The NotifyInstances sends a string value, in this example the GUID instance identifier, to all the running instances.
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;
procedure TMainForm.NotifyInstances(const copyDataStruct: TCopyDataStruct) ;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.
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;
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;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 :
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;
//handle message from the instance just being runWhen 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.
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;
And basically, that does it!

