2004-06-07 14:37:41 +00:00
|
|
|
{
|
|
|
|
:: X2UtSingleInstance provides functions to detect previous instances of an
|
|
|
|
:: application and pass it the new command-line parameters.
|
|
|
|
::
|
|
|
|
:: Last changed: $Date$
|
|
|
|
:: Revision: $Rev$
|
2004-06-07 19:17:31 +00:00
|
|
|
:: Author: $Author$
|
2004-06-07 14:37:41 +00:00
|
|
|
}
|
|
|
|
unit X2UtSingleInstance;
|
|
|
|
|
|
|
|
interface
|
|
|
|
uses
|
|
|
|
SysUtils;
|
|
|
|
|
|
|
|
type
|
|
|
|
{
|
|
|
|
:$ Notifier interface
|
|
|
|
|
|
|
|
:: Applications who want to receive notifications on new instances must
|
|
|
|
:: implements this interface and call RegisterInstance.
|
|
|
|
}
|
|
|
|
IX2InstanceNotifier = interface
|
|
|
|
['{4C435D46-6A7F-4CD7-9400-338E3E8FB5C6}']
|
|
|
|
procedure OnInstance(const ACmdLine: String);
|
|
|
|
end;
|
|
|
|
|
2004-06-07 19:17:31 +00:00
|
|
|
//:$ Checks for a previous instance of the application
|
|
|
|
//:: Returns False if a previous instance was found, True if this is the
|
|
|
|
//:: first registered instance. ApplicationID must be unique to prevent
|
|
|
|
//:: application conflicts, usage of a generated GUID is recommended.
|
|
|
|
//:! Set ANotify to False if you're using SingleInstance in a console
|
|
|
|
//:! application without a message loop.
|
2004-06-07 14:37:41 +00:00
|
|
|
function SingleInstance(const AApplicationID: String;
|
|
|
|
const ANotify: Boolean = True): Boolean;
|
|
|
|
|
2004-06-07 19:17:31 +00:00
|
|
|
//:$ Registers the instance for notifications
|
|
|
|
//:: If an application wants to be notified of new instances it must
|
|
|
|
//:: implement the IX2InstanceNotifier and register the interface using
|
|
|
|
//:: this function.
|
2004-06-07 14:37:41 +00:00
|
|
|
procedure RegisterInstance(const ANotifier: IX2InstanceNotifier);
|
|
|
|
|
2004-06-07 19:17:31 +00:00
|
|
|
//:$ Unregisters a previously registered instance
|
2004-06-07 14:37:41 +00:00
|
|
|
procedure UnregisterInstance(const ANotifier: IX2InstanceNotifier);
|
|
|
|
|
|
|
|
|
2004-06-07 19:17:31 +00:00
|
|
|
//:$ Works like System.ParamCount, but uses the specified string instead
|
|
|
|
//:$ of the actual command line
|
2004-06-07 14:37:41 +00:00
|
|
|
function ParamCountEx(const ACmdLine: String): Integer;
|
|
|
|
|
2004-06-07 19:17:31 +00:00
|
|
|
//:$ Works like System.ParamStr, but uses the specified string instead
|
|
|
|
//:$ of the actual command line
|
2004-06-07 14:37:41 +00:00
|
|
|
function ParamStrEx(const ACmdLine: String; AIndex: Integer): String;
|
|
|
|
|
2004-06-07 19:17:31 +00:00
|
|
|
//:$ Works like SysUtils.FindCmdLineSwitch, but uses the specified string
|
|
|
|
//:$ instead of the actual command line
|
2004-06-07 14:37:41 +00:00
|
|
|
function FindCmdLineSwitchEx(const ACmdLine, ASwitch: String;
|
|
|
|
const AChars: TSysCharSet;
|
|
|
|
const AIgnoreCase: Boolean): Boolean; overload;
|
|
|
|
|
2004-06-07 19:17:31 +00:00
|
|
|
//:$ Works like SysUtils.FindCmdLineSwitch, but uses the specified string
|
|
|
|
//:$ instead of the actual command line
|
2004-06-07 14:37:41 +00:00
|
|
|
function FindCmdLineSwitchEx(const ACmdLine, ASwitch: String): Boolean; overload;
|
|
|
|
|
2004-06-07 19:17:31 +00:00
|
|
|
//:$ Works like SysUtils.FindCmdLineSwitch, but uses the specified string
|
|
|
|
//:$ instead of the actual command line
|
2004-06-07 14:37:41 +00:00
|
|
|
function FindCmdLineSwitchEx(const ACmdLine, ASwitch: String;
|
|
|
|
const AIgnoreCase: Boolean): Boolean; overload;
|
|
|
|
|
|
|
|
implementation
|
|
|
|
uses
|
|
|
|
Classes,
|
|
|
|
Messages,
|
|
|
|
Windows;
|
|
|
|
|
|
|
|
const
|
|
|
|
CWindowClass = 'X2UtInstance.Window';
|
|
|
|
CDataCmdLine = $1010;
|
|
|
|
|
|
|
|
var
|
|
|
|
GNotifiers: TInterfaceList;
|
|
|
|
GFileMapping: THandle;
|
|
|
|
GWindow: THandle;
|
|
|
|
|
|
|
|
|
|
|
|
{$WARN SYMBOL_PLATFORM OFF}
|
|
|
|
|
|
|
|
|
|
|
|
// Copied from System unit because Borland didn't make it public
|
|
|
|
function GetParamStr(P: PChar; var Param: string): PChar;
|
|
|
|
var
|
|
|
|
i, Len: Integer;
|
|
|
|
Start, S, Q: PChar;
|
|
|
|
begin
|
|
|
|
while True do
|
|
|
|
begin
|
|
|
|
while (P[0] <> #0) and (P[0] <= ' ') do
|
|
|
|
P := CharNext(P);
|
|
|
|
if (P[0] = '"') and (P[1] = '"') then Inc(P, 2) else Break;
|
|
|
|
end;
|
|
|
|
Len := 0;
|
|
|
|
Start := P;
|
|
|
|
while P[0] > ' ' do
|
|
|
|
begin
|
|
|
|
if P[0] = '"' then
|
|
|
|
begin
|
|
|
|
P := CharNext(P);
|
|
|
|
while (P[0] <> #0) and (P[0] <> '"') do
|
|
|
|
begin
|
|
|
|
Q := CharNext(P);
|
|
|
|
Inc(Len, Q - P);
|
|
|
|
P := Q;
|
|
|
|
end;
|
|
|
|
if P[0] <> #0 then
|
|
|
|
P := CharNext(P);
|
|
|
|
end
|
|
|
|
else
|
|
|
|
begin
|
|
|
|
Q := CharNext(P);
|
|
|
|
Inc(Len, Q - P);
|
|
|
|
P := Q;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
SetLength(Param, Len);
|
|
|
|
|
|
|
|
P := Start;
|
|
|
|
S := Pointer(Param);
|
|
|
|
i := 0;
|
|
|
|
while P[0] > ' ' do
|
|
|
|
begin
|
|
|
|
if P[0] = '"' then
|
|
|
|
begin
|
|
|
|
P := CharNext(P);
|
|
|
|
while (P[0] <> #0) and (P[0] <> '"') do
|
|
|
|
begin
|
|
|
|
Q := CharNext(P);
|
|
|
|
while P < Q do
|
|
|
|
begin
|
|
|
|
S[i] := P^;
|
|
|
|
Inc(P);
|
|
|
|
Inc(i);
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
if P[0] <> #0 then P := CharNext(P);
|
|
|
|
end
|
|
|
|
else
|
|
|
|
begin
|
|
|
|
Q := CharNext(P);
|
|
|
|
while P < Q do
|
|
|
|
begin
|
|
|
|
S[i] := P^;
|
|
|
|
Inc(P);
|
|
|
|
Inc(i);
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
Result := P;
|
|
|
|
end;
|
|
|
|
|
|
|
|
|
|
|
|
{========================================
|
|
|
|
Window Procedure
|
|
|
|
========================================}
|
|
|
|
function WndProc(hWnd: HWND; uMsg: UINT; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;
|
|
|
|
var
|
|
|
|
sCmdLine: String;
|
|
|
|
iNotifier: Integer;
|
|
|
|
|
|
|
|
begin
|
|
|
|
Result := DefWindowProc(hWnd, uMsg, wParam, lParam);
|
|
|
|
|
|
|
|
case uMsg of
|
|
|
|
WM_COPYDATA:
|
|
|
|
if PCopyDataStruct(lParam)^.dwData = CDataCmdLine then begin
|
|
|
|
with PCopyDataStruct(lParam)^ do
|
|
|
|
SetString(sCmdLine, PChar(lpData), cbData - 1);
|
|
|
|
|
|
|
|
for iNotifier := GNotifiers.Count - 1 downto 0 do
|
|
|
|
IX2InstanceNotifier(GNotifiers[iNotifier]).OnInstance(sCmdLine);
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
|
|
|
|
{========================================
|
|
|
|
Single Instance Check
|
|
|
|
========================================}
|
|
|
|
function SingleInstance;
|
|
|
|
var
|
|
|
|
pData: ^THandle;
|
|
|
|
pCopy: TCopyDataStruct;
|
|
|
|
pCmdLine: PChar;
|
|
|
|
sDummy: String;
|
|
|
|
|
|
|
|
begin
|
|
|
|
Result := False;
|
|
|
|
if GFileMapping <> 0 then
|
|
|
|
exit;
|
|
|
|
|
|
|
|
// Attempt to create shared memory
|
|
|
|
GFileMapping := CreateFileMapping($ffffffff, nil, PAGE_READWRITE, 0,
|
|
|
|
SizeOf(THandle), PChar('X2UtInstance.' +
|
|
|
|
AApplicationID));
|
|
|
|
if GFileMapping = 0 then
|
|
|
|
exit;
|
|
|
|
|
|
|
|
if GetLastError() = ERROR_ALREADY_EXISTS then begin
|
|
|
|
if ANotify then begin
|
|
|
|
pData := MapViewOfFile(GFileMapping, FILE_MAP_READ, 0, 0, 0);
|
|
|
|
if Assigned(pData) then begin
|
|
|
|
// Pass command-line parameters
|
|
|
|
with pCopy do begin
|
|
|
|
pCmdLine := PChar('"' + ParamStr(0) + '" ' + GetParamStr(CmdLine, sDummy));
|
|
|
|
|
|
|
|
dwData := CDataCmdLine;
|
|
|
|
cbData := StrLen(pCmdLine) + 1;
|
|
|
|
|
|
|
|
GetMem(lpData, cbData);
|
|
|
|
StrCopy(lpData, pCmdLine);
|
|
|
|
end;
|
|
|
|
|
|
|
|
SendMessage(pData^, WM_COPYDATA, 0, Integer(@pCopy));
|
|
|
|
UnmapViewOfFile(pData);
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
CloseHandle(GFileMapping);
|
|
|
|
GFileMapping := 0;
|
|
|
|
exit;
|
|
|
|
end;
|
|
|
|
|
|
|
|
pData := MapViewOfFile(GFileMapping, FILE_MAP_WRITE, 0, 0, 0);
|
|
|
|
if Assigned(pData) then begin
|
|
|
|
// Create window
|
|
|
|
GWindow := CreateWindow(CWindowClass, '', 0, 0, 0, 0, 0, 0, 0,
|
|
|
|
SysInit.HInstance, nil);
|
|
|
|
pData^ := GWindow;
|
|
|
|
end else begin
|
|
|
|
CloseHandle(GFileMapping);
|
|
|
|
GFileMapping := 0;
|
|
|
|
exit;
|
|
|
|
end;
|
|
|
|
|
|
|
|
Result := True;
|
|
|
|
end;
|
|
|
|
|
|
|
|
|
|
|
|
{========================================
|
|
|
|
Notifier Registration
|
|
|
|
========================================}
|
|
|
|
procedure RegisterInstance;
|
|
|
|
begin
|
|
|
|
if GNotifiers.IndexOf(ANotifier) = -1 then
|
|
|
|
GNotifiers.Add(ANotifier);
|
|
|
|
end;
|
|
|
|
|
|
|
|
procedure UnregisterInstance;
|
|
|
|
var
|
|
|
|
iIndex: Integer;
|
|
|
|
|
|
|
|
begin
|
|
|
|
iIndex := GNotifiers.IndexOf(ANotifier);
|
|
|
|
if iIndex > -1 then
|
|
|
|
GNotifiers.Delete(iIndex);
|
|
|
|
end;
|
|
|
|
|
|
|
|
|
|
|
|
{========================================
|
|
|
|
Parameter Functions
|
|
|
|
========================================}
|
|
|
|
function ParamCountEx;
|
|
|
|
var
|
|
|
|
pCmdLine: PChar;
|
|
|
|
sParam: String;
|
|
|
|
|
|
|
|
begin
|
|
|
|
Result := 0;
|
|
|
|
pCmdLine := GetParamStr(PChar(ACmdLine), sParam);
|
|
|
|
|
|
|
|
while True do begin
|
|
|
|
pCmdLine := GetParamStr(pCmdLine, sParam);
|
|
|
|
|
|
|
|
if Length(sParam) = 0 then
|
|
|
|
break;
|
|
|
|
|
|
|
|
Inc(Result);
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
function ParamStrEx;
|
|
|
|
var
|
|
|
|
pCmdLine: PChar;
|
|
|
|
|
|
|
|
begin
|
|
|
|
Result := '';
|
|
|
|
pCmdLine := PChar(ACmdLine);
|
|
|
|
while True do begin
|
|
|
|
pCmdLine := GetParamStr(pCmdLine, Result);
|
|
|
|
|
|
|
|
if (AIndex = 0) or (Length(Result) = 0) then
|
|
|
|
break;
|
|
|
|
|
|
|
|
Dec(AIndex);
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
|
|
|
|
function FindCmdLineSwitchEx(const ACmdLine, ASwitch: String;
|
|
|
|
const AChars: TSysCharSet;
|
|
|
|
const AIgnoreCase: Boolean): Boolean;
|
|
|
|
var
|
|
|
|
iParam: Integer;
|
|
|
|
sParam: String;
|
|
|
|
|
|
|
|
begin
|
|
|
|
for iParam := 1 to ParamCountEx(ACmdLine) do begin
|
|
|
|
sParam := ParamStrEx(ACmdLine, iParam);
|
|
|
|
|
|
|
|
if (AChars = []) or (sParam[1] in AChars) then
|
|
|
|
if AIgnoreCase then begin
|
|
|
|
if (AnsiCompareText(Copy(sParam, 2, Maxint), ASwitch) = 0) then begin
|
|
|
|
Result := True;
|
|
|
|
exit;
|
|
|
|
end;
|
|
|
|
end else begin
|
|
|
|
if (AnsiCompareStr(Copy(sParam, 2, Maxint), ASwitch) = 0) then begin
|
|
|
|
Result := True;
|
|
|
|
exit;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
Result := False;
|
|
|
|
end;
|
|
|
|
|
|
|
|
function FindCmdLineSwitchEx(const ACmdLine, ASwitch: String): Boolean;
|
|
|
|
begin
|
|
|
|
Result := FindCmdLineSwitchEx(ACmdLine, ASwitch, SwitchChars, True);
|
|
|
|
end;
|
|
|
|
|
|
|
|
function FindCmdLineSwitchEx(const ACmdLine, ASwitch: String;
|
|
|
|
const AIgnoreCase: Boolean): Boolean;
|
|
|
|
begin
|
|
|
|
Result := FindCmdLineSwitchEx(ACmdLine, ASwitch, SwitchChars, AIgnoreCase);
|
|
|
|
end;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var
|
|
|
|
wndClass: TWndClass;
|
|
|
|
|
|
|
|
initialization
|
|
|
|
GNotifiers := TInterfaceList.Create();
|
|
|
|
|
|
|
|
// Register window class
|
|
|
|
FillChar(wndClass, SizeOf(wndClass), #0);
|
|
|
|
with wndClass do begin
|
|
|
|
lpfnWndProc := @WndProc;
|
|
|
|
hInstance := SysInit.HInstance;
|
|
|
|
lpszClassName := CWindowClass;
|
|
|
|
end;
|
|
|
|
|
|
|
|
Windows.RegisterClass(wndClass);
|
|
|
|
|
|
|
|
finalization
|
|
|
|
FreeAndNil(GNotifiers);
|
|
|
|
|
|
|
|
if GFileMapping <> 0 then
|
|
|
|
// Free file mapping
|
|
|
|
CloseHandle(GFileMapping);
|
|
|
|
|
|
|
|
if GWindow <> 0 then
|
|
|
|
DestroyWindow(GWindow);
|
|
|
|
|
|
|
|
Windows.UnregisterClass(CWindowClass, SysInit.HInstance);
|
|
|
|
|
|
|
|
end.
|