by Evan Simpson - esimpson@eramp.net
For many programs, having more than one instance running at the same time isn't a problem. It may even be a desirable feature, in SDI applications like Notepad, for instance. Often, though, the program will read and write data files (or registry keys) in a way which will cause corruption if another copy is launched, or it may simply not make any sense to have multiple instances running. In these cases it is valuable to have a simple mechanism to detect and control instance launches, especially with Windows 98 and its single-click launch interface.
Under 16 bit Windows, the solution was simple; Just examine the previous instance global variable to see if a copy is already running, and do the right thing. Under Win32, things are a bit more complicated. Several solutions are available on the Web, including one on the Borland Tips page, but I still felt compelled to write my own for several reasons. First, I needed to transmit the command line parameters of a second launch attempt to the primary instance in one program I wrote (it processes BMP files dragged onto its Explorer icon). Second, I wanted a plug-in unit which required minimal alteration from one program to the next, and little or no foolery in my DPR file. Third, I wanted it to be secure, reliable, and fast; In particular, it should run before any other VCL code initializes or allocates anything.
To meet the first requirement, I used WM_COPYDATA on the command line string. This in turn required me to get a window handle from the primary instance to use as a target for the message. FindWindow handles this nicely, but I couldn't use the automatic application window or any other standard window of the application, because of the third requirement.
The second requirement led me to place all of the code into a unit which is simply placed in the DPR "uses" clause. The actual instance management code runs in the unit's initialization. Global variables in the interface part provide the event hook and passed data storage.
Due to the third requirement, the unit must be the very first unit used by the DPR, and could not use any other application unit. In particular, almost every unit is likely to use Forms, and Forms is normally the first unit used by a DPR, but Forms sets up and initializes the whole Application framework.
The result of all this is the InstanceManager unit below. Simply make it the first unit in your program's DPR's uses list, and edit the constant declarations to fit your needs. Note that by using the same string constant for two or more different programs, you can create a "family" of executables which share instance management - I use this ability in a timeclock program, which not only should not run more than once, but cannot run at the same time as its administative utility. Also, this unit doesn't just prevent a second instance from running. It can call a notification event, which can decide on a case-by-case basis whether to allow the other instance to run, perhaps by examining the command line.
interface
{Notes: make InstanceManager
the *very first* unit in your program's USES
clause. To take advantage
of the notification and launch-string, put a
method with no parameters
in one of your forms and assign it to triggerProc.
Once triggerProc is called,
rcvStr contains the command line of the launch
attempt.
If the only reaction you
want is to bring the first instance to the front,
just put a method like the
following in your main form, and in the form's
OnCreate set InstanceManager.triggerProc:=ToFront;
procedure
TForm1.ToFront;
begin
Application.Restore;
Application.BringToFront;
end;
If you don't have a dependable
main form, make ToFront a class procedure of
any old class.}
{ Customize these constants
before using }
const UniqueAppName = 'Unique application
name';
AppNotifyValue: integer
= 0;
var rcvStr: string;
rcvValue: integer;
ForbidOtherInstance: boolean =
True;
triggerProc: procedure of object;
implementation
uses Windows, SysUtils, Messages;
var mutex, thisWnd: HWND;
IMWndClass: TWndClassA;
mustHalt: boolean;
copydata: TCOPYDATASTRUCT;
function IMWndProc(HWindow: HWnd; Message,
WParam: Longint; LParam: Longint): Longint; stdcall;
begin
if Message=WM_COPYDATA then
begin
rcvStr := StrPas(PCOPYDATASTRUCT(lParam).lpData);
rcvValue := PCOPYDATASTRUCT(lParam).dwData;
if Assigned(triggerProc)
then triggerProc;
Result := Ord(ForbidOtherInstance);
end
else
Result := DefWindowProc(hWindow,
Message, WParam, LParam);
end;
initialization
FillChar(IMWndClass, SizeOf(IMWndClass), 0);
IMWndClass.lpfnWndProc := @IMWndProc;
IMWndClass.hInstance := HINSTANCE;
IMwndClass.lpszClassName := 'TInstanceManager';
if Windows.RegisterClass(IMWndClass)
= 0 then RaiseLastWin32Error;
mutex := CreateMutex(nil, True, UniqueAppName);
if GetLastError = ERROR_ALREADY_EXISTS
then
begin
mustHalt := True;
if WaitForSingleObject(mutex,
5000)=WAIT_OBJECT_0 then
begin
thisWnd := FindWindow(IMwndClass.lpszClassName, UniqueAppName);
if thisWnd = 0 then RaiseLastWin32Error;
CopyData.dwData := AppNotifyValue;
CopyData.lpData := CmdLine;
CopyData.cbData := StrLen(CmdLine);
mustHalt := (SendMessage(thisWnd,WM_COPYDATA,0,Integer(@CopyData))>0);
end;
thisWnd := 0;
ReleaseMutex(mutex);
if mustHalt
then Halt;
end
else
begin
thisWnd := CreateWindow(IMwndClass.lpszClassName,UniqueAppName,0,0,0,0,0,0,0,hInstance,
nil);
if thisWnd
= 0 then RaiseLastWin32Error;
ReleaseMutex(mutex);
end;
finalization
if thisWnd>0 then DestroyWindow(thisWnd);
end.