34

Is there support in the Delphi XE VCL for ensuring only a single instance of an application is running?

In the past, I've used library code to control a Mutex which has always seemed complicated. As I'm starting a new project in Delphi XE, I wonder if I need to dig up that old code, or if there is support built into XE already? Or is there another easy to apply code that is nice and modern?

NGLN
  • 43,011
  • 8
  • 105
  • 200
mj2008
  • 6,647
  • 2
  • 38
  • 56
  • 3
    What makes you think creating a mutex is not modern? – jachguate Mar 22 '11 at 17:01
  • 1
    I have implemented instancing for the following type which works across multiple user sessions: TEAppSingleInstance = (siYes, siMultipleAcrossUsers, siNo). Yes means a single instance across all users, no means each user can run multiple instances, and multiple across users means each user can run only a single instance for their session, but multiple users can run the application at the same time. – Misha Apr 19 '11 at 01:44
  • 2
    possible duplicate of [How can I tell if another instance of my program is already running?](http://stackoverflow.com/questions/459554/how-can-i-tell-if-another-instance-of-my-program-is-already-running) – David Heffernan Aug 28 '13 at 15:21

4 Answers4

45

You create a named Mutex when you start the application. Check GetLastError to see if an other instance is already running.

Put this code right after "begin" in your DPR file. Replace the GUID with one of your own. When I need a text constant that's unlikely to be used for anything else, I usually just hit Ctrl+G to get a GUID!

if CreateMutex(nil, True, '6EACD0BF-F3E0-44D9-91E7-47467B5A2B6A') = 0 then
  RaiseLastOSError;

if GetLastError = ERROR_ALREADY_EXISTS then
  Exit;

It might look like the code is leaking an handle because it's not saving the return of CreateMutex. It's not. Windows will automatically release the handle when our application is terminated, and that's absolutely fine with us.

Joris Groosman
  • 771
  • 8
  • 23
Cosmin Prund
  • 25,498
  • 2
  • 60
  • 104
  • yes, but is there any support in Delphi XE for it, or is it still DIY? – mj2008 Mar 22 '11 at 11:44
  • 4
    You don't need specific `Delphi XE` support for two lines of Windows API. Make sure you add `Windows` and `SysUtils` to the uses clause of your DPR. – Cosmin Prund Mar 22 '11 at 11:47
  • Ok - my old code brings the other instance to the front, but in this case it doesn't actually matter. Will accept with the new edit. – mj2008 Mar 22 '11 at 11:48
  • 10
    This will create the mutex in the session namespace. A process in a different session (think fast user switching) will be able to start a new process whilst the one in the other session is running. You can use Global\ as a prefix to the name to get a mutex in the global namespace. – David Heffernan Mar 22 '11 at 12:37
  • 7
    Good point David. However, maybe this behaviour (session namespace) might be what some developers actually want, even though they haven't thought about that. Imagine you wanted to deploy a rich database client application, that can be run using Windows Terminal Services, you might one one-app-per-desktop instead of one-app-per-machine. – Warren P Mar 22 '11 at 12:39
  • 2
    Even simpler example: Imagine you implement this for Your Next Big Chat Program; The Wife comes to the computer and actually does "Switch User" (my wife does!) and logs on to her account, attempts to start the Next Big Chat Program. Oooops! Any way, good point David, everyone should read the documentation anyway. – Cosmin Prund Mar 22 '11 at 20:03
  • @HeinduPlessis, two and a half years and no one spotted that, corrected it. Thanks. – Cosmin Prund Sep 29 '13 at 12:39
  • 1
    Your solution is simple. I like straight forward solutions one that doesn't involve downloading or installing tools and/or components or using third-party tool(s). This is just what I was looking for. Thanks. – ThN Oct 14 '13 at 13:40
  • 3
    +1 for going directly to the VCL and Win API instead of JCL. Nothing wrong with JCL, but why use an external tool when it's not necessary? We used this solution in our shop for many years without issue. As for a differnent user, as you mentioned, generally that's what you want - a different user **should** get a different session/instance. – Vector May 27 '14 at 01:15
  • How do you switch bring to front the previous instance? – Gabriel Jul 28 '17 at 19:18
30

I use JCL to do this:

program MyProgram;

uses
  JclAppInst;

begin
  JclAppInstances.CheckSingleInstance; // Added instance checking
  Application.Initialize;
  Application.CreateForm(TMainForm, MainForm);
  Application.Run;
end.

Documentation for this, and the notification scheme, is at the JCL Wiki.

mj2008
  • 6,647
  • 2
  • 38
  • 56
2

I use this, works in XE2 through to Alexandria, has the benefit of being able to bring the currently running instance to the front.

Those that say it shouldn't do that, well, given the last thing the user did was to try launch the app, bringing a currently running instance to the front makes sense

unit CheckPrevious;

interface

uses
  Windows, SysUtils, WinSock;

function RestoreIfRunning(const AppHandle : THandle; MaxInstances : integer = 1) : boolean;

implementation

type
  PInstanceInfo = ^TInstanceInfo;
  TInstanceInfo = packed record
    PreviousHandle : THandle;
    RunCounter : integer;
  end;
var
  MappingHandle: THandle;
  InstanceInfo: PInstanceInfo;
  MappingName : string;
  RemoveMe : boolean = True;

function RestoreIfRunning(const AppHandle : THandle; MaxInstances : integer = 1) : boolean;
begin
  Result := True;
  MappingName := StringReplace(ParamStr(0),'\','',[rfReplaceAll, rfIgnoreCase]);
  MappingHandle := CreateFileMapping($FFFFFFFF,nil,PAGE_READWRITE,0,SizeOf(TInstanceInfo),PChar(MappingName));
  if MappingHandle = 0 then
    RaiseLastOSError
  else
  begin
    if GetLastError <> ERROR_ALREADY_EXISTS then
    begin
      InstanceInfo := MapViewOfFile(MappingHandle,FILE_MAP_ALL_ACCESS,0,0,SizeOf(TInstanceInfo));
      InstanceInfo^.PreviousHandle := AppHandle;
      InstanceInfo^.RunCounter := 1;
      Result := False;
    end
    else //already runing
    begin
      MappingHandle := OpenFileMapping(FILE_MAP_ALL_ACCESS, False, PChar(MappingName));
      if MappingHandle <> 0 then
      begin
        InstanceInfo := MapViewOfFile(MappingHandle,FILE_MAP_ALL_ACCESS,0,0,SizeOf(TInstanceInfo));
        if InstanceInfo^.RunCounter >= MaxInstances then
        begin
          RemoveMe := False;
          if IsIconic(InstanceInfo^.PreviousHandle) then
            ShowWindow(InstanceInfo^.PreviousHandle, SW_RESTORE);
          SetForegroundWindow(InstanceInfo^.PreviousHandle);
        end
        else
        begin
          InstanceInfo^.PreviousHandle := AppHandle;
          InstanceInfo^.RunCounter := 1 + InstanceInfo^.RunCounter;
          Result := False;
        end
      end;
    end;
  end;
end;

initialization

finalization
  //remove one instance
  if RemoveMe then
  begin
    MappingHandle := OpenFileMapping(FILE_MAP_ALL_ACCESS, False, PChar(MappingName));
    if MappingHandle <> 0 then
    begin
      InstanceInfo := MapViewOfFile(MappingHandle, FILE_MAP_ALL_ACCESS, 0, 0, SizeOf(TInstanceInfo));
      InstanceInfo^.RunCounter := -1 + InstanceInfo^.RunCounter;
    end
    else
      RaiseLastOSError;
  end;
  if Assigned(InstanceInfo) then
    UnmapViewOfFile(InstanceInfo);
  if MappingHandle <> 0 then
    CloseHandle(MappingHandle);
end.

In your project DPR, add the CheckPrevious unit to the uses, then just after begin put the following

  if RestoreIfRunning(Application.Handle, 1) then
    Exit;

I have no idea of where this code originated, otherwise I would gladly credit the author. (A search of RestoreIfRunning may suggest it was from Zarko Gajic)

MarkoZaaz
  • 48
  • 3
  • This works for me where the JCL code doesn't. Running my app a second time with `JclAppInstances.CheckSingleInstance()` causes the first instance to terminate without starting the second instance. – SteveS Dec 07 '22 at 16:20
1

This is how i do it.

closeProc(extractfilename(paramstr(0)));

function TForm1.closeProc(pname : string): integer;
const
PROCESS_TERMINATE = $0001;
var
ContinueLoop: BOOL;
FSnapshotHandle: THandle;
FProcessEntry32: TProcessEntry32;
i : integer;
pname2 : string;
begin
try
Result := 0;
i := 0;
FSnapshotHandle := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
FProcessEntry32.dwSize := SizeOf(FProcessEntry32);
ContinueLoop := Process32First(FSnapshotHandle, FProcessEntry32);
while Integer(ContinueLoop) <> 0 do
    begin
    pname2 := trim(UpperCase(ExtractFileName(FProcessEntry32.szExeFile)));
    if ( pname2 = uppercase(pname)) then
      if FProcessEntry32.th32ProcessID <> GetCurrentProcessId then
        begin
          Result := Integer(TerminateProcess(OpenProcess(PROCESS_TERMINATE, BOOL(0), FProcessEntry32.th32ProcessID), 0));
          inc(i);
        end;
    ContinueLoop := Process32Next(FSnapshotHandle, FProcessEntry32);
    if i > 50 then
      break;
    end;
CloseHandle(FSnapshotHandle);
except
end;
end;
delphirules
  • 6,443
  • 17
  • 59
  • 108