1
0
mirror of synced 2024-09-16 16:46:09 +00:00

Fixed: D7 compatibility

Fixed: SingleInstance global/local ondersteuning + refactoring
This commit is contained in:
Mark van Renswoude 2007-06-13 07:26:50 +00:00
parent af655e8083
commit 59d62f8d8c
6 changed files with 457 additions and 157 deletions

View File

@ -1,5 +1,7 @@
unit HashesTest;
{$I X2UtCompilerVersion.inc}
interface
uses
TestFramework,
@ -145,6 +147,7 @@ var
sKey: String;
begin
{$IFDEF D2006}
FillTestItems();
FillChar(aPresent, SizeOf(aPresent), #0);
@ -161,6 +164,7 @@ begin
CheckTrue(aPresent[1], 'Key1 was not in the enumeration!');
CheckTrue(aPresent[2], 'Key2 was not in the enumeration!');
CheckTrue(aPresent[3], 'Key3 was not in the enumeration!');
{$ENDIF}
{ Not supported yet, maybe in the future.
FillChar(aPresent, SizeOf(aPresent), #0);
@ -245,6 +249,7 @@ var
pKey: Pointer;
begin
{$IFDEF D2006}
FillTestItems();
FillChar(aPresent, SizeOf(aPresent), #0);
@ -254,6 +259,7 @@ begin
CheckTrue(aPresent[0], 'Key1 was not in the enumeration!');
CheckTrue(aPresent[1], 'Key2 was not in the enumeration!');
CheckTrue(aPresent[2], 'Key3 was not in the enumeration!');
{$ENDIF}
end;
procedure THashesPOTest.testIterate;

View File

@ -32,12 +32,12 @@
-M
-$M16384,1048576
-K$00400000
-LE"C:\Documents and Settings\PsychoMark\My Documents\Borland Studio Projects\Bpl"
-LN"C:\Documents and Settings\PsychoMark\My Documents\Borland Studio Projects\Bpl"
-U"..\"
-O"..\"
-I"..\"
-R"..\"
-LE"c:\program files\borland\delphi7\Projects\Bpl"
-LN"c:\program files\borland\delphi7\Projects\Bpl"
-U".."
-O".."
-I".."
-R".."
-w-SYMBOL_PLATFORM
-w-UNIT_PLATFORM
-w-UNSAFE_TYPE

View File

@ -94,7 +94,7 @@ OutputDir=
UnitOutputDir=
PackageDLLOutputDir=
PackageDCPOutputDir=
SearchPath=
SearchPath=..
Packages=vcl;rtl;dbrtl;vcldb;vclx;dss;dsnapcrba;dsnapcon;inetdb;webdsnap;websnap;dbxcds;Irc;parsdpk;hotspotter
Conditionals=
DebugSourceDirs=
@ -105,6 +105,10 @@ HostApplication=
Launcher=
UseLauncher=0
DebugCWD=
[Language]
ActiveLang=
ProjectLang=
RootDir=
[Version Info]
IncludeVerInfo=0
AutoIncBuild=0
@ -130,6 +134,11 @@ OriginalFilename=
ProductName=
ProductVersion=1.0.0.0
Comments=
[Excluded Packages]
C:\Program Files\Borland\Indy\D7\dclIndy70.bpl=Internet Direct (Indy) for D7 Property and Component Editors
[HistoryLists\hlUnitAliases]
Count=1
Item0=WinTypes=Windows;WinProcs=Windows;DbiTypes=BDE;DbiProcs=BDE;DbiErrs=BDE;
[HistoryLists\hlSearchPath]
Count=1
Item0=..

View File

@ -6,7 +6,8 @@ uses
Variants,
BitsTest in 'Units\BitsTest.pas',
HashesTest in 'Units\HashesTest.pas',
PersistTest in 'Units\PersistTest.pas';
PersistTest in 'Units\PersistTest.pas',
X2UtSingleInstance in '..\X2UtSingleInstance.pas';
//SettingsTest in 'Units\SettingsTest.pas',
//IniParserTest in 'Units\IniParserTest.pas';

View File

@ -25,6 +25,8 @@
}
unit X2UtHashes;
{$I X2UtCompilerVersion.inc}
interface
uses
Classes,
@ -43,7 +45,10 @@ type
// Forward declarations
TX2CustomHash = class;
{$IFDEF D2005}
{$REGION 'Internal hash structures'}
{$ENDIF}
{
:$ Internal representation of a hash item.
}
@ -92,9 +97,11 @@ type
property Current: PX2HashValue read GetCurrent;
end;
{$IFDEF D2005}
{$ENDREGION}
{$REGION 'Internal value managers'}
{$ENDIF}
{
:$ Base value manager.
}
@ -161,9 +168,11 @@ type
function Compare(const AData: Pointer; const AValue: Pointer;
const ASize: Cardinal): Boolean; override;
end;
{$IFDEF D2005}
{$ENDREGION}
{$REGION 'Delphi 2006 enumerator support'}
{$ENDIF}
{
:$ Base enumerator class.
}
@ -224,9 +233,11 @@ type
public
property Current: String read GetCurrent;
end;
{$IFDEF D2005}
{$ENDREGION}
{$REGION 'Abstract hash implementation'}
{$ENDIF}
{
:$ Hash implementation.
}
@ -277,9 +288,11 @@ type
property Count: Integer read FCount;
end;
{$IFDEF D2005}
{$ENDREGION}
{$REGION 'Base hash classes'}
{$ENDIF}
{
:$ Base hash implementation for pointer keys.
}
@ -355,9 +368,11 @@ type
property CurrentKey: String read GetCurrentKey;
end;
{$IFDEF D2005}
{$ENDREGION}
{$REGION 'Concrete hash classes'}
{$ENDIF}
{
:$ Pointer-to-Pointer hash.
}
@ -617,7 +632,9 @@ type
property CurrentValue: String read GetCurrentValue;
property Values[Key: String]: String read GetValue write SetValue; default;
end;
{$IFDEF D2005}
{$ENDREGION}
{$ENDIF}
implementation
const
@ -672,7 +689,9 @@ begin
end;
{$IFDEF D2005}
{$REGION 'Internal hash structures'}
{$ENDIF}
{========================================
TX2HashCursor
========================================}
@ -774,10 +793,12 @@ begin
break;
until False;
end;
{$IFDEF D2005}
{$ENDREGION}
{$REGION 'Internal value managers'}
{$ENDIF}
{========================================
TX2CustomHashManager
========================================}
@ -948,10 +969,12 @@ begin
Result := CompareMem(pSource, AValue, ASize);
end;
{$IFDEF D2005}
{$ENDREGION}
{$REGION 'Abstract hash implementation'}
{$ENDIF}
{========================== TX2CustomHash
Initialization
========================================}
@ -1285,10 +1308,12 @@ begin
Result := Cursor.Next();
end;
{$IFDEF D2005}
{$ENDREGION}
{$REGION 'Delphi 2006 enumerator support'}
{$ENDIF}
{========================================
TX2HashEnumerator
========================================}
@ -1352,10 +1377,12 @@ function TX2HashStringEnumerator.GetCurrent(): String;
begin
Result := TX2HashStringManager(Manager).ToValue(Cursor);
end;
{$IFDEF D2005}
{$ENDREGION}
{$REGION 'Base hash classes'}
{$ENDIF}
{========================================
TX2CustomPointerHash
========================================}
@ -1499,10 +1526,12 @@ function TX2CustomStringHash.Delete(const AKey: String): Boolean;
begin
Result := inherited Delete(PChar(AKey), Length(AKey));
end;
{$IFDEF D2005}
{$ENDREGION}
{$REGION 'Concrete hash classes'}
{$ENDIF}
{========================================
TX2PPHash
========================================}
@ -2058,7 +2087,9 @@ begin
inherited SetValue(Find(Key, True),
TX2HashStringManager(ValueManager).ToPointer(Value));
end;
{$IFDEF D2005}
{$ENDREGION}
{$ENDIF}
initialization

View File

@ -10,83 +10,408 @@ unit X2UtSingleInstance;
interface
uses
SysUtils;
Classes,
SysUtils,
Messages;
const
IM_COMMANDLINE = $00000001;
IM_APP = $00000100;
type
{
:$ Notifier interface
EInstanceNotActive = class(Exception);
EInstanceNoAppID = class(Exception);
:: Applications who want to receive notifications on new instances must
:: implements this interface and call RegisterInstance.
{
:$ Notifier observer interface.
:: Applications that want to receive notifications on new instances must
:: implement this interface and call Attach(Instance).
}
IX2InstanceNotifier = interface
IX2InstanceObserver = interface
['{4C435D46-6A7F-4CD7-9400-338E3E8FB5C6}']
procedure OnInstance(const ACmdLine: String);
end;
//:$ 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.
{
:$ Extended notifier observer interface.
:: Applications that want to receive custom notifications as well must
:: implement this interface and call Attach(Instance).
}
IX2InstanceObserverEx = interface(IX2InstanceObserver)
['{755A6548-3EA8-46C2-9FF5-FDE4BD67B699}']
procedure OnNotify(AMessage: Integer; const AData: String);
end;
{
:$ Internal file mapping layout.
}
PX2InstanceMapData = ^TX2InstanceMapData;
TX2InstanceMapData = record
RefCount: Integer;
Window: THandle;
end;
{
:$ Instance object.
:: Manages an instance. Instances are identified by an ApplicationID, which
:: must be unique. For simple single instance checking you can use the
:: SingleInstance wrapper function.
}
TX2Instance = class(TObject)
private
FActive: Boolean;
FApplicationID: String;
FFirstInstance: Boolean;
FGlobal: Boolean;
FFileMapData: PX2InstanceMapData;
FFileMapping: THandle;
FObservers: TInterfaceList;
protected
function GetCount(): Integer; virtual;
procedure SetApplicationID(const Value: String); virtual;
procedure SetGlobal(const Value: Boolean); virtual;
procedure SetActive(const Value: Boolean); virtual;
procedure WindowProc(var Message: TMessage); virtual;
property FileMapping: THandle read FFileMapping;
property FileMapData: PX2InstanceMapData read FFileMapData;
property Observers: TInterfaceList read FObservers;
public
constructor Create();
destructor Destroy(); override;
procedure Open(); virtual;
procedure Close(); virtual;
//:$ Sends a notification to the first instance.
//:! For custom messages you are recommended to start message IDs counting
//:! from IM_APP. Anything below is reserved for internal use.
procedure Notify(AMessage: Integer; const AData: String); virtual;
//:$ Registers the instance for notifications.
//:: If an application wants to be notified of new instances it must
//:: implement the IInstanceNotifier and register the interface using
//:: this function.
procedure Attach(const ANotifier: IX2InstanceObserver);
//:$ Unregisters a previously registered instance.
procedure Detach(const ANotifier: IX2InstanceObserver);
property Active: Boolean read FActive write SetActive;
property ApplicationID: String read FApplicationID write SetApplicationID;
property FirstInstance: Boolean read FFirstInstance;
property Global: Boolean read FGlobal write SetGlobal;
property Count: Integer read GetCount;
end;
{
:$ 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.
::
:: If AGlobal is True, the check is performed system-wide. This only
:: affects Terminal Services and XP Fast User Switching sessions.
::
:: This function is a wrapper for the TX2Instance object. You can access
:: the created object through the Instance function.
:! Set ANotify to False if you're using SingleInstance in a console
:! application without a message loop.
:!
:! If AGlobal is True, ANotify only works if the previous instance was
:! started by the same user.
}
function SingleInstance(const AApplicationID: String;
const ANotify: Boolean = True): Boolean;
ANotify: Boolean = True;
AGlobal: Boolean = True): Boolean;
//:$ 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.
procedure RegisterInstance(const ANotifier: IX2InstanceNotifier);
{
:$ Returns a singleton TX2Instance object.
//:$ Unregisters a previously registered instance
procedure UnregisterInstance(const ANotifier: IX2InstanceNotifier);
:: The object is automatically configured when using the SingleInstance
:: function.
}
function Instance(): TX2Instance;
//:$ Works like System.ParamCount, but uses the specified string instead
//:$ of the actual command line
{
:$ Registers the instance for notifications
:: Calls Attach on the singleton Instance.
}
procedure AttachInstance(const ANotifier: IX2InstanceObserver);
{
:$ Unregisters a previously registered instance.
:: Calls Detach on the singleton Instance.
}
procedure DetachInstance(const ANotifier: IX2InstanceObserver);
{
:$ Works like System.ParamCount, but uses the specified string instead
:$ of the actual command line.
}
function ParamCountEx(const ACmdLine: String): Integer;
//:$ Works like System.ParamStr, but uses the specified string instead
//:$ of the actual command line
{
:$ Works like System.ParamStr, but uses the specified string instead
:$ of the actual command line
}
function ParamStrEx(const ACmdLine: String; AIndex: Integer): String;
//:$ Works like SysUtils.FindCmdLineSwitch, but uses the specified string
//:$ instead of the actual command line
{
:$ Works like SysUtils.FindCmdLineSwitch, but uses the specified string
:$ instead of the actual command line
}
function FindCmdLineSwitchEx(const ACmdLine, ASwitch: String;
const AChars: TSysCharSet;
const AIgnoreCase: Boolean): Boolean; overload;
//:$ Works like SysUtils.FindCmdLineSwitch, but uses the specified string
//:$ instead of the actual command line
{
:$ Works like SysUtils.FindCmdLineSwitch, but uses the specified string
:$ instead of the actual command line
}
function FindCmdLineSwitchEx(const ACmdLine, ASwitch: String): Boolean; overload;
//:$ Works like SysUtils.FindCmdLineSwitch, but uses the specified string
//:$ instead of the actual command line
{
:$ Works like SysUtils.FindCmdLineSwitch, but uses the specified string
:$ instead of the actual command line
}
function FindCmdLineSwitchEx(const ACmdLine, ASwitch: String;
const AIgnoreCase: Boolean): Boolean; overload;
implementation
uses
Classes,
Messages,
Windows;
const
CWindowClass = 'X2UtInstance.Window';
CDataCmdLine = $1010;
WindowClass = 'X2UtSingleInstance.Window';
var
GNotifiers: TInterfaceList;
GFileMapping: THandle;
GWindow: THandle;
GlobalInstance: TX2Instance;
{$WARN SYMBOL_PLATFORM OFF}
{$WARN SYMBOL_PLATFORM OFF}
{ TX2Instance }
constructor TX2Instance.Create();
begin
inherited;
FObservers := TInterfaceList.Create;
end;
destructor TX2Instance.Destroy();
begin
Active := False;
FreeAndNil(FObservers);
inherited;
end;
procedure TX2Instance.Notify(AMessage: Integer; const AData: String);
var
copyStruct: TCopyDataStruct;
begin
if not Active then
raise EInstanceNotActive.Create('Instance not Active');
if FileMapData^.Window = 0 then
Exit;
copyStruct.dwData := AMessage;
copyStruct.cbData := Length(AData);
copyStruct.lpData := PChar(AData);
SendMessage(FileMapData^.Window, WM_COPYDATA, 0, Integer(@copyStruct));
end;
procedure TX2Instance.Open();
const
ScopePrefix: array[Boolean] of String = ('Local\', 'Global\');
begin
if Active then
Exit;
if Length(ApplicationID) = 0 then
raise EInstanceNoAppID.Create('ApplicationID not specified');
FFirstInstance := True;
{ Attempt to create shared memory }
SetLastError(0);
FFileMapping := CreateFileMapping($FFFFFFFF, nil, PAGE_READWRITE, 0,
SizeOf(TX2InstanceMapData),
PChar(ScopePrefix[Global] +
'SingleInstance.' + ApplicationID));
if FFileMapping = 0 then
RaiseLastOSError();
FActive := True;
try
FFirstInstance := (GetLastError() <> ERROR_ALREADY_EXISTS);
FFileMapData := MapViewOfFile(FFileMapping, FILE_MAP_WRITE, 0, 0, 0);
if not Assigned(FFileMapData) then
RaiseLastOSError();
if FFirstInstance then
begin
FileMapData^.Window := CreateWindow(WindowClass, '', 0, 0, 0, 0, 0, 0,
0, SysInit.HInstance, nil);
if FileMapData^.Window = 0 then
RaiseLastOSError();
SetWindowLong(FileMapData^.Window, GWL_WNDPROC,
Integer(MakeObjectInstance(WindowProc)));
end;
Inc(FFileMapData^.RefCount);
except
Close();
end;
end;
procedure TX2Instance.Close();
begin
if not Active then
Exit;
if Assigned(FileMapData) then
begin
Dec(FileMapData^.RefCount);
if FirstInstance then
DestroyWindow(FileMapData^.Window);
UnmapViewOfFile(FileMapData);
end;
if FileMapping <> 0 then
CloseHandle(FileMapping);
FActive := False;
end;
procedure TX2Instance.Attach(const ANotifier: IX2InstanceObserver);
begin
if Observers.IndexOf(ANotifier) = -1 then
Observers.Add(ANotifier as IX2InstanceObserver);
end;
procedure TX2Instance.Detach(const ANotifier: IX2InstanceObserver);
begin
Observers.Remove(ANotifier as IX2InstanceObserver);
end;
procedure TX2Instance.WindowProc(var Message: TMessage);
var
copyData: PCopyDataStruct;
data: String;
observerIndex: Integer;
observerExIntf: IX2InstanceObserverEx;
begin
if Assigned(FileMapData) then
case Message.Msg of
WM_COPYDATA:
begin
copyData := PCopyDataStruct(Message.LParam);
data := '';
if copyData^.cbData > 0 then
SetString(data, PChar(copyData^.lpData), copyData^.cbData);
case copyData^.dwData of
IM_COMMANDLINE:
for observerIndex := 0 to Pred(Observers.Count) do
IX2InstanceObserver(Observers[observerIndex]).OnInstance(data);
else
for observerIndex := 0 to Pred(Observers.Count) do
if Supports(Observers[observerIndex], IX2InstanceObserverEx, observerExIntf) then
observerExIntf.OnNotify(copyData^.dwData, data);
end;
end;
else
Message.Result := DefWindowProc(FileMapData^.Window, Message.Msg,
Message.WParam, Message.LParam);
end;
end;
function TX2Instance.GetCount(): Integer;
begin
Result := 0;
if Active then
Result := FileMapData^.RefCount;
end;
procedure TX2Instance.SetActive(const Value: Boolean);
begin
if Value then
Open
else
Close;
end;
procedure TX2Instance.SetApplicationID(const Value: String);
var
wasActive: Boolean;
begin
if Value <> FApplicationID then
begin
wasActive := Active;
Active := False;
FApplicationID := Value;
Active := wasActive;
end;
end;
procedure TX2Instance.SetGlobal(const Value: Boolean);
var
wasActive: Boolean;
begin
if Value <> FGlobal then
begin
wasActive := Active;
Active := False;
FGlobal := Value;
Active := wasActive;
end;
end;
// Copied from System unit because Borland didn't make it public
function GetParamStr(P: PChar; var Param: string): PChar;
function GetParamStr(P: PChar; var Param: String): PChar;
var
i, Len: Integer;
Start, S, Q: PChar;
@ -159,117 +484,53 @@ begin
end;
{========================================
Window Procedure
========================================}
function WndProc(hWnd: HWND; uMsg: UINT; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;
{ Single instance wrappers }
function SingleInstance(const AApplicationID: String;
ANotify, AGlobal: Boolean): Boolean;
var
sCmdLine: String;
iNotifier: Integer;
newCmdLine: String;
dummy: String;
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;
with Instance do
begin
Result := False;
if GFileMapping <> 0 then
exit;
ApplicationID := AApplicationID;
Global := AGlobal;
Active := True;
// Attempt to create shared memory
GFileMapping := CreateFileMapping($ffffffff, nil, PAGE_READWRITE, 0,
SizeOf(THandle), PChar('X2UtInstance.' +
AApplicationID));
if GFileMapping = 0 then
exit;
Result := FirstInstance;
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);
if (not Result) and ANotify then
begin
{ For compatibility with ParamStr(0), we'll modify the command-line to
include the full executable path. }
newCmdLine := '"' + ParamStr(0) + '" ' + GetParamStr(CmdLine, dummy);
Notify(IM_COMMANDLINE, newCmdLine);
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;
function Instance(): TX2Instance;
begin
if GNotifiers.IndexOf(ANotifier) = -1 then
GNotifiers.Add(ANotifier);
if not Assigned(GlobalInstance) then
GlobalInstance := TX2Instance.Create;
Result := GlobalInstance;
end;
procedure UnregisterInstance;
var
iIndex: Integer;
procedure AttachInstance(const ANotifier: IX2InstanceObserver);
begin
iIndex := GNotifiers.IndexOf(ANotifier);
if iIndex > -1 then
GNotifiers.Delete(iIndex);
Instance.Attach(ANotifier);
end;
procedure DetachInstance(const ANotifier: IX2InstanceObserver);
begin
Instance.Detach(ANotifier);
end;
{========================================
Parameter Functions
========================================}
function ParamCountEx;
{ Parameter helpers }
function ParamCountEx(const ACmdLine: String): Integer;
var
pCmdLine: PChar;
sParam: String;
@ -288,7 +549,7 @@ begin
end;
end;
function ParamStrEx;
function ParamStrEx(const ACmdLine: String; AIndex: Integer): String;
var
pCmdLine: PChar;
@ -351,28 +612,20 @@ var
wndClass: TWndClass;
initialization
GNotifiers := TInterfaceList.Create();
// Register window class
{ Register window class }
FillChar(wndClass, SizeOf(wndClass), #0);
with wndClass do begin
lpfnWndProc := @WndProc;
with wndClass do
begin
lpfnWndProc := @DefWindowProc;
hInstance := SysInit.HInstance;
lpszClassName := CWindowClass;
lpszClassName := WindowClass;
end;
Windows.RegisterClass(wndClass);
finalization
FreeAndNil(GNotifiers);
Windows.UnregisterClass(WindowClass, SysInit.HInstance);
if GFileMapping <> 0 then
// Free file mapping
CloseHandle(GFileMapping);
if GWindow <> 0 then
DestroyWindow(GWindow);
Windows.UnregisterClass(CWindowClass, SysInit.HInstance);
FreeAndNil(GlobalInstance);
end.