2006. június 25., vasárnap

Handling previous instances of an application


Problem/Question/Abstract:

I was surprised to find that I didn't have an article covering this topic - I wrote a couple of 'em for inquiry.com a couple of years ago. Oh well... they're in another place and time... In any case, what I'll give you here are two techniques for handling previous instances of your applications.

Answer:

Oh! Were that only the phrase I used when I was single! Alas, like most, it unfortunately wasn't. But at least I could apply that phrase in my professional life; of course, in a totally different way. Okay, enough of the play on words.

There are many applications that lend themselves to having more than one copy running at any time. But in my experience, most of the applications I build only lend themselves to a single instance. For instance, it isn't practical to run more than one instance of a data-entry application; especially when it updates local data. In cases such as this, I limit the execution of another instance of a program by executing some simple code.

What I'm going to show you is two different functions that do exactly the same thing. The only difference between the two is that the first function can only be run in Win32, and the other function can run in either Win16 or Win32. Here's the code:

// ===================================================
// Called by your project file, prevents a 2nd
// instance of the program from executing and
// instead activates the already executing instance.
// Returns TRUE if a previous instance of the
// program is already running. Win32 ONLY
// ===================================================

function IsPrevInst: Boolean;
var
  semName,
    appClass: PChar;
  hSem: THandle;
  hWndMe: HWnd;
  appTitle: array[0..MAX_PATH] of Char;
begin
  // Init
  Result := FALSE;
  GetMem(semName, 15);
  GetMem(appClass, 15);
  StrPCopy(semName, 'SemaphoreName');
  StrPCopy(appClass, 'TApplication');
  StrPCopy(appTitle, ExtractFileName(Application.Title));

  // Create a Semaphore in memory.  If this is the
  // first instance, then hSem's value should be 0.
  hSem := CreateSemaphore(nil, 0, 1, semName);

  // Check to see if the semaphore exists
  if (hSem <> 0) and (GetLastError() =
    ERROR_ALREADY_EXISTS) then
  begin
    CloseHandle(hSem);

    // Get the current window's handle then change
    // its title so we can look for the other instance
    hWndMe := FindWindow(appClass, appTitle);
    SetWindowText(hWndMe, 'ZZZZZZZ');

    // Search for other instance of this window then bring
    // it to the top of the Z-order stack.  We find it by
    // matching the Application Class and
    // Application Title.
    hWndMe := FindWindow(appClass, appTitle);
    if (hWndMe <> 0) then
    begin
      BringWindowToTop(hWndMe);
      ShowWindow(hWndMe, SW_SHOWNORMAL);
    end;

    Result := TRUE;
  end;

  // Destroy PChars
  FreeMem(semName, 15);
  FreeMem(appClass, 15);
end;

//This is a different twist on the previous example.
//It uses a mutex (MUTually EXclusive) instead of a sema-
//phore.

procedure CheckPrevInstEx(MainFormClassName,
  MainFormCaption: string);
var
  PrevWnd: HWnd;
  Mutex: THandle;
begin
{$IFDEF Win32}
  Mutex := CreateMutex(nil, False, 'InstanceMutex');
  if WaitForSingleObject(Mutex, 10000) = WAIT_TIMEOUT then
    Application.Terminate;
{$ELSE}
  if HPrevInst = 0 then
    Application.Terminate;
{$ENDIF}

  PrevWnd := FindWindow(PChar(MainFormClassName),
    PChar(MainFormCaption));
  if PrevWnd <> 0 then
    PrevWnd := GetWindow(PrevWnd, GW_OWNER);
  if PrevWnd <> 0 then
  begin
    if IsIconic(PrevWnd) then
      ShowWindow(PrevWnd, SW_SHOWNORMAL)
    else
{$IFDEF Win32}
      SetForegroundWindow(PrevWnd);
{$ELSE}
      BringWindowToTop(PrevWnd);
{$ENDIF}
    Application.Terminate;
  end;
  ReleaseMutex(Mutex);
  CloseHandle(Mutex);
end;

To use the functions above, you can either embed them in the project file, or better yet, place them in a globally accessible library for use in all your applications that need them. Here's some example code for implementing them:

//This example uses the IsPrevInst function
program RxProto;

uses
  Forms,
  Main in 'Main.pas' {MainForm},
  Proc in 'Proc.pas',
  //This is my global library
  UTIL32 in '..\Lib\UTIL\Util32.pas',
  LoopPnThr in '..\Packages\LoopPnThr.pas';

{$R *.RES}

begin
  if not IsPrevInst then
  begin
    Application.Initialize;
    Application.CreateForm(TMainForm, MainForm);
    Application.Run;
  end
  else
    Application.Terminate;
end.
//Here's the other way...
program RxProto;

uses
  Forms,
  Main in 'Main.pas' {MainForm},
  Proc in 'Proc.pas',
  UTIL32 in '..\Lib\UTIL\Util32.pas',
  LoopPnThr in '..\Packages\LoopPnThr.pas';

{$R *.RES}

begin
  CheckPrevInstEx('TApplication', 'My Application');
  //This code won't do anything if CheckPrevInstEx doesn't
  //pass muster
  Application.Initialize;
  Application.CreateForm(TMainForm, MainForm);
  Application.Run;
end.

As you can see, pretty simple stuff. Have fun with it!

Nincsenek megjegyzések:

Megjegyzés küldése