* Refactored singleinstance so it is independent of any specific implementation

git-svn-id: trunk@32770 -
This commit is contained in:
michael 2015-12-27 18:46:08 +00:00
parent 2451f7596b
commit a2985a5572
4 changed files with 380 additions and 349 deletions

View File

@ -40,7 +40,7 @@ begin
WriteLn('Sending response to client.'); WriteLn('Sending response to client.');
xStringStream := TStringStream.Create('my response'); xStringStream := TStringStream.Create('my response');
try try
Sender.ServerPostCustomResponse(MsgID, MsgType_Response, xStringStream); (Sender as TAdvancedSingleInstance).ServerPostCustomResponse(MsgID, MsgType_Response, xStringStream);
finally finally
xStringStream.Free; xStringStream.Free;
end; end;
@ -66,9 +66,9 @@ var
begin begin
xApp := TMyCustomApplication.Create(nil); xApp := TMyCustomApplication.Create(nil);
try try
xApp.SingleInstance.Enabled := True; xApp.SingleInstanceEnabled := True;
xApp.SingleInstance.OnServerReceivedParams := @xApp.ServerReceivedParams; xApp.SingleInstance.OnServerReceivedParams := @xApp.ServerReceivedParams;
xApp.SingleInstance.OnServerReceivedCustomRequest := @xApp.ServerReceivedCustomRequest; (xApp.SingleInstance as TAdvancedSingleInstance).OnServerReceivedCustomRequest := @xApp.ServerReceivedCustomRequest;
xApp.Initialize; xApp.Initialize;
Writeln(xApp.SingleInstance.StartResult); Writeln(xApp.SingleInstance.StartResult);
xApp.Run; xApp.Run;
@ -79,15 +79,15 @@ begin
begin begin
xStream := TStringStream.Create('hello'); xStream := TStringStream.Create('hello');
try try
xApp.SingleInstance.ClientSendCustomRequest(MsgType_Request_No_Response, xStream); (xApp.SingleInstance as TAdvancedSingleInstance).ClientSendCustomRequest(MsgType_Request_No_Response, xStream);
finally finally
xStream.Free; xStream.Free;
end; end;
xStream := TStringStream.Create('I want a response'); xStream := TStringStream.Create('I want a response');
try try
xApp.SingleInstance.ClientSendCustomRequest(MsgType_Request_With_Response, xStream); (xApp.SingleInstance as TAdvancedSingleInstance).ClientSendCustomRequest(MsgType_Request_With_Response, xStream);
xStream.Size := 0; xStream.Size := 0;
if xApp.SingleInstance.ClientPeekCustomResponse(xStream, xMsgType) then if (xApp.SingleInstance as TAdvancedSingleInstance).ClientPeekCustomResponse(xStream, xMsgType) then
WriteLn('Response: ', xStream.DataString) WriteLn('Response: ', xStream.DataString)
else else
WriteLn('Error: no response'); WriteLn('Error: no response');

View File

@ -30,7 +30,7 @@ uses
{$IFDEF UNIX} {$IFDEF UNIX}
baseunix, baseunix,
{$endif} {$endif}
sysutils, Classes; sysutils, Classes, singleinstance;
const const
HEADER_VERSION = 2; HEADER_VERSION = 2;
@ -168,6 +168,43 @@ type
EICPException = class(Exception); EICPException = class(Exception);
TSingleInstanceReceivedCustomMessage = procedure(Sender: TBaseSingleInstance; MsgID: Integer; MsgType: Integer; MsgData: TStream) of object;
TAdvancedSingleInstance = class(TBaseSingleInstance)
private
FGlobal: Boolean;
FID: string;
FServer: TIPCServer;
FClient: TIPCClient;
FOnServerReceivedCustomRequest: TSingleInstanceReceivedCustomMessage;
function GetIsClient: Boolean; override;
function GetIsServer: Boolean; override;
function GetStartResult: TSingleInstanceStart; override;
procedure SetGlobal(const aGlobal: Boolean);
procedure SetID(const aID: string);
protected
procedure DoServerReceivedCustomRequest(const aMsgID: Integer; const aMsgType: Integer; const aStream: TStream);
public
constructor Create(aOwner: TComponent); override;
public
function Start: TSingleInstanceStart; override;
procedure Stop; override;
procedure ServerCheckMessages; override;
procedure ClientPostParams; override;
public
function ClientPostCustomRequest(const aMsgType: Integer; const aStream: TStream): Integer;
function ClientSendCustomRequest(const aMsgType: Integer; const aStream: TStream): Boolean; overload;
function ClientSendCustomRequest(const aMsgType: Integer; const aStream: TStream; out outRequestID: Integer): Boolean; overload;
procedure ServerPostCustomResponse(const aRequestID: Integer; const aMsgType: Integer; const aStream: TStream);
function ClientPeekCustomResponse(const aStream: TStream; out outMsgType: Integer): Boolean;
public
property ID: string read FID write SetID;
property Global: Boolean read FGlobal write SetGlobal;
property OnServerReceivedCustomRequest: TSingleInstanceReceivedCustomMessage read FOnServerReceivedCustomRequest write FOnServerReceivedCustomRequest;
end;
resourcestring resourcestring
SErrInvalidServerID = 'Invalid server ID "%s". Please use only alphanumerical characters and underlines.'; SErrInvalidServerID = 'Invalid server ID "%s". Please use only alphanumerical characters and underlines.';
SErrSetGlobalActive = 'You cannot change the global property when the server is active.'; SErrSetGlobalActive = 'You cannot change the global property when the server is active.';
@ -772,8 +809,284 @@ begin
FActive := False; FActive := False;
end; end;
Resourcestring
SErrSetSingleInstanceIDStarted = 'You cannot change the single instance ID when it''s been started.';
SErrSetSingleInstanceGlobalStarted = 'You cannot change the single instance global property when it''s been started.';
SErrStartSingleInstanceStarted = 'You cannot start single instance when it''s been already started.';
SErrSingleInstanceStartResultNotAvailable = 'Single instance hasn''t been started yet.';
SErrSingleInstanceNotClient = 'Current instance is not a client.';
SErrSingleInstanceNotServer = 'Current instance is not a server.';
Const
MSGTYPE_CHECK = -1;
MSGTYPE_CHECKRESPONSE = -2;
MSGTYPE_PARAMS = -3;
MSGTYPE_WAITFORINSTANCES = -4;
{ TAdvancedSingleInstance }
constructor TAdvancedSingleInstance.Create(aOwner: TComponent);
var
xID: RawByteString;
I: Integer;
begin
inherited Create(aOwner);
xID := 'SI_'+ExtractFileName(ParamStr(0));
for I := 1 to Length(xID) do
case xID[I] of
'a'..'z', 'A'..'Z', '0'..'9', '_': begin end;
else
xID[I] := '_';
end;
ID := xID;
end;
function TAdvancedSingleInstance.ClientPeekCustomResponse(
const aStream: TStream; out outMsgType: Integer): Boolean;
begin
if not Assigned(FClient) then
raise ESingleInstance.Create(SErrSingleInstanceNotClient);
Result := FClient.PeekResponse(aStream, outMsgType, TimeOutMessages);
end;
function TAdvancedSingleInstance.ClientPostCustomRequest(
const aMsgType: Integer; const aStream: TStream): Integer;
begin
if not Assigned(FClient) then
raise ESingleInstance.Create(SErrSingleInstanceNotClient);
Result := FClient.PostRequest(aMsgType, aStream);
end;
procedure TAdvancedSingleInstance.ClientPostParams;
var
xSL: TStringList;
xStringStream: TStringStream;
I: Integer;
begin
if not Assigned(FClient) then
raise ESingleInstance.Create(SErrSingleInstanceNotClient);
xSL := TStringList.Create;
try
for I := 0 to ParamCount do
xSL.Add(ParamStr(I));
xStringStream := TStringStream.Create(xSL.DelimitedText);
try
xStringStream.Position := 0;
FClient.PostRequest(MSGTYPE_PARAMS, xStringStream);
finally
xStringStream.Free;
end;
finally
xSL.Free;
end;
end;
function TAdvancedSingleInstance.ClientSendCustomRequest(
const aMsgType: Integer; const aStream: TStream): Boolean;
begin
if not Assigned(FClient) then
raise ESingleInstance.Create(SErrSingleInstanceNotClient);
Result := FClient.SendRequest(aMsgType, aStream, TimeOutMessages);
end;
function TAdvancedSingleInstance.ClientSendCustomRequest(
const aMsgType: Integer; const aStream: TStream; out
outRequestID: Integer): Boolean;
begin
if not Assigned(FClient) then
raise ESingleInstance.Create(SErrSingleInstanceNotClient);
Result := FClient.SendRequest(aMsgType, aStream, TimeOutMessages, outRequestID);
end;
procedure TAdvancedSingleInstance.DoServerReceivedCustomRequest(
const aMsgID: Integer; const aMsgType: Integer; const aStream: TStream);
begin
if Assigned(FOnServerReceivedCustomRequest) then
FOnServerReceivedCustomRequest(Self, aMsgID, aMsgType, aStream);
end;
function TAdvancedSingleInstance.GetIsClient: Boolean;
begin
Result := Assigned(FClient);
end;
function TAdvancedSingleInstance.GetIsServer: Boolean;
begin
Result := Assigned(FServer);
end;
function TAdvancedSingleInstance.GetStartResult: TSingleInstanceStart;
begin
if not(Assigned(FServer) or Assigned(FClient)) then
raise ESingleInstance.Create(SErrSingleInstanceStartResultNotAvailable);
Result := inherited GetStartResult;
end;
procedure TAdvancedSingleInstance.ServerCheckMessages;
var
xMsgID: Integer;
xMsgType: Integer;
xStream: TStream;
xStringStream: TStringStream;
begin
if not Assigned(FServer) then
raise ESingleInstance.Create(SErrSingleInstanceNotServer);
if not FServer.PeekRequest(xMsgID, xMsgType) then
Exit;
case xMsgType of
MSGTYPE_CHECK:
begin
FServer.DeleteRequest(xMsgID);
FServer.PostResponse(xMsgID, MSGTYPE_CHECKRESPONSE, nil);
end;
MSGTYPE_PARAMS:
begin
xStringStream := TStringStream.Create('');
try
FServer.ReadRequest(xMsgID, xStringStream);
DoServerReceivedParams(xStringStream.DataString);
finally
xStringStream.Free;
end;
end;
MSGTYPE_WAITFORINSTANCES:
FServer.DeleteRequest(xMsgID);
else
xStream := TMemoryStream.Create;
try
FServer.ReadRequest(xMsgID, xStream);
DoServerReceivedCustomRequest(xMsgID, xMsgType, xStream);
finally
xStream.Free;
end;
end;
end;
procedure TAdvancedSingleInstance.ServerPostCustomResponse(
const aRequestID: Integer; const aMsgType: Integer;
const aStream: TStream);
begin
if not Assigned(FServer) then
raise ESingleInstance.Create(SErrSingleInstanceNotServer);
FServer.PostResponse(aRequestID, aMsgType, aStream);
end;
procedure TAdvancedSingleInstance.SetGlobal(const aGlobal: Boolean);
begin
if FGlobal = aGlobal then Exit;
if Assigned(FServer) or Assigned(FClient) then
raise ESingleInstance.Create(SErrSetSingleInstanceGlobalStarted);
FGlobal := aGlobal;
end;
procedure TAdvancedSingleInstance.SetID(const aID: string);
begin
if FID = aID then Exit;
if Assigned(FServer) or Assigned(FClient) then
raise ESingleInstance.Create(SErrSetSingleInstanceIDStarted);
FID := aID;
end;
function TAdvancedSingleInstance.Start: TSingleInstanceStart;
{$IFNDEF MSWINDOWS}
procedure UnixWorkaround(var bServerStarted: Boolean);
var
xWaitRequestID, xLastCount, xNewCount: Integer;
xClient: TIPCClient;
begin
//file locking workaround for UNIX systems -> the server can be started twice if 2 processes are started in parallel
//wait some time to see other clients
FServer.StopServer(False);
xClient := TIPCClient.Create(Self);
try
xClient.ServerID := FID;
xClient.Global := FGlobal;
xWaitRequestID := xClient.PostRequest(MSGTYPE_WAITFORINSTANCES, nil);
xLastCount := -1;
xNewCount := FServer.GetPendingRequestCount;
while xLastCount <> xNewCount do
begin
xLastCount := xNewCount;
Sleep(TimeOutWaitForInstances);
xNewCount := FServer.GetPendingRequestCount;
end;
finally
FreeAndNil(xClient);
end;
//find highest client that will be the server
if FServer.FindHighestPendingRequestId = xWaitRequestID then
begin
bServerStarted := FServer.StartServer(False);
end else
begin
//something went wrong, there are not-deleted waiting requests
//use random sleep as workaround and try to restart the server
Randomize;
Sleep(Random(($3F+PtrInt(GetProcessID)) and $3F));//limit to $3F (63)
bServerStarted := FServer.StartServer(False) and (FServer.GetPendingRequestCount > 0);
end;
end;
{$ENDIF}
var
xStream: TStream;
xMsgType: Integer;
xServerStarted: Boolean;
begin
if Assigned(FServer) or Assigned(FClient) then
raise ESingleInstance.Create(SErrStartSingleInstanceStarted);
FServer := TIPCServer.Create(Self);
FServer.ServerID := FID;
FServer.Global := FGlobal;
xServerStarted := FServer.StartServer(False);
if xServerStarted then
begin//this is single instance -> be server
Result := siServer;
{$IFNDEF MSWINDOWS}
UnixWorkaround(xServerStarted);
{$ENDIF}
end;
if not xServerStarted then
begin//instance found -> be client
FreeAndNil(FServer);
FClient := TIPCClient.Create(Self);
FClient.ServerID := FID;
FClient.Global := FGlobal;
FClient.PostRequest(MSGTYPE_CHECK, nil);
xStream := TMemoryStream.Create;
try
if FClient.PeekResponse(xStream, xMsgType, TimeOutMessages) then
Result := siClient
else
Result := siNotResponding;
finally
xStream.Free;
end;
end;
SetStartResult(Result);
end;
procedure TAdvancedSingleInstance.Stop;
begin
FreeAndNil(FServer);
FreeAndNil(FClient);
end;
initialization initialization
InitCriticalSection(CreateUniqueRequestCritSec); InitCriticalSection(CreateUniqueRequestCritSec);
DefaultSingleInstanceClass:=TAdvancedSingleInstance;
finalization finalization
DoneCriticalsection(CreateUniqueRequestCritSec); DoneCriticalsection(CreateUniqueRequestCritSec);

View File

@ -25,16 +25,15 @@ Type
TExceptionEvent = Procedure (Sender : TObject; E : Exception) Of Object; TExceptionEvent = Procedure (Sender : TObject; E : Exception) Of Object;
TEventLogTypes = Set of TEventType; TEventLogTypes = Set of TEventType;
TCustomApplication = Class;
TCustomSingleInstance = Class;
{ TCustomApplication } { TCustomApplication }
TCustomApplication = Class(TComponent) TCustomApplication = Class(TComponent)
Private Private
FEventLogFilter: TEventLogTypes; FEventLogFilter: TEventLogTypes;
FOnException: TExceptionEvent; FOnException: TExceptionEvent;
FSingleInstance: TCustomSingleInstance; FSingleInstance: TBaseSingleInstance;
FSingleInstanceClass: TBaseSingleInstanceClass; // set before FSingleInstance is created
FSingleInstanceEnabled: Boolean; // set before Initialize is called
FTerminated : Boolean; FTerminated : Boolean;
FHelpFile, FHelpFile,
FTitle : String; FTitle : String;
@ -44,6 +43,9 @@ Type
function GetEnvironmentVar(VarName : String): String; function GetEnvironmentVar(VarName : String): String;
function GetExeName: string; function GetExeName: string;
Function GetLocation : String; Function GetLocation : String;
function GetSingleInstance: TBaseSingleInstance;
procedure SetSingleInstanceClass(
const ASingleInstanceClass: TBaseSingleInstanceClass);
function GetTitle: string; function GetTitle: string;
Protected Protected
function GetOptionAtIndex(AIndex: Integer; IsLong: Boolean): String; function GetOptionAtIndex(AIndex: Integer; IsLong: Boolean): String;
@ -96,15 +98,9 @@ Type
Property CaseSensitiveOptions : Boolean Read FCaseSensitiveOptions Write FCaseSensitiveOptions; Property CaseSensitiveOptions : Boolean Read FCaseSensitiveOptions Write FCaseSensitiveOptions;
Property StopOnException : Boolean Read FStopOnException Write FStopOnException; Property StopOnException : Boolean Read FStopOnException Write FStopOnException;
Property EventLogFilter : TEventLogTypes Read FEventLogFilter Write FEventLogFilter; Property EventLogFilter : TEventLogTypes Read FEventLogFilter Write FEventLogFilter;
Property SingleInstance: TCustomSingleInstance read FSingleInstance; Property SingleInstance: TBaseSingleInstance read GetSingleInstance;
end; Property SingleInstanceClass: TBaseSingleInstanceClass read FSingleInstanceClass write SetSingleInstanceClass;
Property SingleInstanceEnabled: Boolean read FSingleInstanceEnabled write FSingleInstanceEnabled;
TCustomSingleInstance = class(TBaseSingleInstance)
private
FEnabled: Boolean;
public
//you must set Enabled before CustomApplication.Initialize
property Enabled: Boolean read FEnabled write FEnabled;
end; end;
var CustomApplication : TCustomApplication = nil; var CustomApplication : TCustomApplication = nil;
@ -235,6 +231,17 @@ begin
Result:=ParamStr(Index); Result:=ParamStr(Index);
end; end;
function TCustomApplication.GetSingleInstance: TBaseSingleInstance;
begin
if FSingleInstance = nil then
begin
if FSingleInstanceClass=Nil then
Raise ESingleInstance.Create('No single instance provider class set! Include a single-instance class unit such as advsingleinstance');
FSingleInstance := FSingleInstanceClass.Create(Self);
end;
Result := FSingleInstance;
end;
procedure TCustomApplication.SetTitle(const AValue: string); procedure TCustomApplication.SetTitle(const AValue: string);
begin begin
FTitle:=AValue; FTitle:=AValue;
@ -247,8 +254,9 @@ end;
procedure TCustomApplication.DoRun; procedure TCustomApplication.DoRun;
begin begin
if FSingleInstance.IsServer then if Assigned(FSingleInstance) then
FSingleInstance.ServerCheckMessages; if FSingleInstance.IsServer then
FSingleInstance.ServerCheckMessages;
// Override in descendent classes. // Override in descendent classes.
end; end;
@ -283,7 +291,7 @@ begin
FOptionChar:='-'; FOptionChar:='-';
FCaseSensitiveOptions:=True; FCaseSensitiveOptions:=True;
FStopOnException:=False; FStopOnException:=False;
FSingleInstance := TCustomSingleInstance.Create(Self); FSingleInstanceClass := DefaultSingleInstanceClass;
end; end;
destructor TCustomApplication.Destroy; destructor TCustomApplication.Destroy;
@ -310,12 +318,12 @@ end;
procedure TCustomApplication.Initialize; procedure TCustomApplication.Initialize;
begin begin
FTerminated:=False; FTerminated:=False;
if FSingleInstance.Enabled then if FSingleInstanceEnabled then
begin begin
case FSingleInstance.Start of case SingleInstance.Start of
siClient: siClient:
begin begin
FSingleInstance.ClientPostParams; SingleInstance.ClientPostParams;
FTerminated:=True; FTerminated:=True;
end; end;
siNotResponding: siNotResponding:
@ -336,6 +344,13 @@ begin
Until FTerminated; Until FTerminated;
end; end;
procedure TCustomApplication.SetSingleInstanceClass(
const ASingleInstanceClass: TBaseSingleInstanceClass);
begin
Assert((FSingleInstance = nil) and (ASingleInstanceClass <> nil));
FSingleInstanceClass := ASingleInstanceClass;
end;
procedure TCustomApplication.ShowException(E: Exception); procedure TCustomApplication.ShowException(E: Exception);
begin begin

View File

@ -19,7 +19,7 @@ unit singleinstance;
interface interface
uses uses
SysUtils, Classes, advancedipc; SysUtils, Classes;
type type
@ -29,187 +29,58 @@ type
//siClient: There is another instance running. This instance is used as client. //siClient: There is another instance running. This instance is used as client.
//siNotResponding: There is another instance running but it doesn't respond. //siNotResponding: There is another instance running but it doesn't respond.
TSingleInstanceStart = (siServer, siClient, siNotResponding); TSingleInstanceStart = (siServer, siClient, siNotResponding);
TSingleInstanceParams = procedure(Sender: TBaseSingleInstance; Params: TStringList) of object; TSingleInstanceParamsEvent = procedure(Sender: TBaseSingleInstance; Params: TStringList) of object;
TSingleInstanceReceivedCustomMessage = procedure(Sender: TBaseSingleInstance; MsgID: Integer; MsgType: TMessageType; MsgData: TStream) of object;
TBaseSingleInstance = class(TComponent) TBaseSingleInstance = class(TComponent)
private private
FGlobal: Boolean;
FID: string;
FServer: TIPCServer;
FClient: TIPCClient;
FStartResult: TSingleInstanceStart; FStartResult: TSingleInstanceStart;
FTimeOutMessages: Integer; FTimeOutMessages: Integer;
FTimeOutWaitForInstances: Integer; FTimeOutWaitForInstances: Integer;
FOnServerReceivedCustomRequest: TSingleInstanceReceivedCustomMessage; FOnServerReceivedParams: TSingleInstanceParamsEvent;
FOnServerReceivedParams: TSingleInstanceParams; Protected
function GetIsClient: Boolean; function GetIsClient: Boolean; virtual; abstract;
function GetIsServer: Boolean; function GetIsServer: Boolean; virtual; abstract;
function GetStartResult: TSingleInstanceStart; function GetStartResult: TSingleInstanceStart; virtual;
procedure SetGlobal(const aGlobal: Boolean);
procedure SetID(const aID: string);
procedure DoServerReceivedParams(const aParamsDelimitedText: string); procedure DoServerReceivedParams(const aParamsDelimitedText: string);
procedure DoServerReceivedCustomRequest(const aMsgID: Integer; const aMsgType: TMessageType; const aStream: TStream); Procedure SetStartResult(AValue : TSingleInstanceStart);
protected
//call Start when you want to start single instance checking
function Start: TSingleInstanceStart;
//stop single instance server or client
procedure Stop;
procedure ServerCheckMessages;
procedure ClientPostParams;
public public
constructor Create(aOwner: TComponent); override; constructor Create(aOwner: TComponent); override;
destructor Destroy; override; destructor Destroy; override;
public public
function ClientPostCustomRequest(const aMsgType: TMessageType; const aStream: TStream): Integer; //call Start when you want to start single instance checking
function ClientSendCustomRequest(const aMsgType: TMessageType; const aStream: TStream): Boolean; overload; function Start: TSingleInstanceStart; virtual; abstract;
function ClientSendCustomRequest(const aMsgType: TMessageType; const aStream: TStream; out outRequestID: Integer): Boolean; overload; //stop single instance server or client
procedure ServerPostCustomResponse(const aRequestID: Integer; const aMsgType: TMessageType; const aStream: TStream); procedure Stop; virtual; abstract;
function ClientPeekCustomResponse(const aStream: TStream; out outMsgType: TMessageType): Boolean;
//check and handle pending messages on server
procedure ServerCheckMessages; virtual; abstract;
//post cmd parameters from client to server
procedure ClientPostParams; virtual; abstract;
public public
property ID: string read FID write SetID;
property Global: Boolean read FGlobal write SetGlobal;
property TimeOutMessages: Integer read FTimeOutMessages write FTimeOutMessages; property TimeOutMessages: Integer read FTimeOutMessages write FTimeOutMessages;
property TimeOutWaitForInstances: Integer read FTimeOutWaitForInstances write FTimeOutWaitForInstances; property TimeOutWaitForInstances: Integer read FTimeOutWaitForInstances write FTimeOutWaitForInstances;
property OnServerReceivedParams: TSingleInstanceParams read FOnServerReceivedParams write FOnServerReceivedParams; property OnServerReceivedParams: TSingleInstanceParamsEvent read FOnServerReceivedParams write FOnServerReceivedParams;
property OnServerReceivedCustomRequest: TSingleInstanceReceivedCustomMessage read FOnServerReceivedCustomRequest write FOnServerReceivedCustomRequest;
public public
property StartResult: TSingleInstanceStart read GetStartResult; property StartResult: TSingleInstanceStart read GetStartResult;
property IsServer: Boolean read GetIsServer; property IsServer: Boolean read GetIsServer;
property IsClient: Boolean read GetIsClient; property IsClient: Boolean read GetIsClient;
end; end;
TBaseSingleInstanceClass = class of TBaseSingleInstance;
TSingleInstance = class(TBaseSingleInstance)
public
function Start: TSingleInstanceStart;
procedure Stop;
procedure ServerCheckMessages;
procedure ClientPostParams;
end;
ESingleInstance = class(Exception); ESingleInstance = class(Exception);
Var
DefaultSingleInstanceClass : TBaseSingleInstanceClass = Nil;
implementation implementation
Resourcestring
SErrSetSingleInstanceIDStarted = 'You cannot change the single instance ID when it''s been started.';
SErrSetSingleInstanceGlobalStarted = 'You cannot change the single instance global property when it''s been started.';
SErrStartSingleInstanceStarted = 'You cannot start single instance when it''s been already started.';
SErrSingleInstanceStartResultNotAvailable = 'Single instance hasn''t been started yet.';
SErrSingleInstanceNotClient = 'Current instance is not a client.';
SErrSingleInstanceNotServer = 'Current instance is not a server.';
Const
MSGTYPE_CHECK = -1;
MSGTYPE_CHECKRESPONSE = -2;
MSGTYPE_PARAMS = -3;
MSGTYPE_WAITFORINSTANCES = -4;
{ TSingleInstance }
procedure TSingleInstance.ClientPostParams;
begin
inherited ClientPostParams;
end;
procedure TSingleInstance.ServerCheckMessages;
begin
inherited ServerCheckMessages;
end;
function TSingleInstance.Start: TSingleInstanceStart;
begin
Result := inherited Start;
end;
procedure TSingleInstance.Stop;
begin
inherited Stop;
end;
{ TBaseSingleInstance } { TBaseSingleInstance }
function TBaseSingleInstance.ClientPeekCustomResponse(const aStream: TStream; out
outMsgType: TMessageType): Boolean;
begin
if not Assigned(FClient) then
raise ESingleInstance.Create(SErrSingleInstanceNotClient);
Result := FClient.PeekResponse(aStream, outMsgType, FTimeOutMessages);
end;
function TBaseSingleInstance.ClientPostCustomRequest(const aMsgType: TMessageType;
const aStream: TStream): Integer;
begin
if not Assigned(FClient) then
raise ESingleInstance.Create(SErrSingleInstanceNotClient);
Result := FClient.PostRequest(aMsgType, aStream);
end;
procedure TBaseSingleInstance.ClientPostParams;
var
xSL: TStringList;
xStringStream: TStringStream;
I: Integer;
begin
if not Assigned(FClient) then
raise ESingleInstance.Create(SErrSingleInstanceNotClient);
xSL := TStringList.Create;
try
for I := 0 to ParamCount do
xSL.Add(ParamStr(I));
xStringStream := TStringStream.Create(xSL.DelimitedText);
try
xStringStream.Position := 0;
FClient.PostRequest(MSGTYPE_PARAMS, xStringStream);
finally
xStringStream.Free;
end;
finally
xSL.Free;
end;
end;
function TBaseSingleInstance.ClientSendCustomRequest(
const aMsgType: TMessageType; const aStream: TStream): Boolean;
begin
if not Assigned(FClient) then
raise ESingleInstance.Create(SErrSingleInstanceNotClient);
Result := FClient.SendRequest(aMsgType, aStream, FTimeOutMessages);
end;
function TBaseSingleInstance.ClientSendCustomRequest(const aMsgType: TMessageType;
const aStream: TStream; out outRequestID: Integer): Boolean;
begin
if not Assigned(FClient) then
raise ESingleInstance.Create(SErrSingleInstanceNotClient);
Result := FClient.SendRequest(aMsgType, aStream, FTimeOutMessages, outRequestID);
end;
constructor TBaseSingleInstance.Create(aOwner: TComponent); constructor TBaseSingleInstance.Create(aOwner: TComponent);
var
xID: RawByteString;
I: Integer;
begin begin
inherited Create(aOwner); inherited Create(aOwner);
FTimeOutMessages := 1000; FTimeOutMessages := 1000;
FTimeOutWaitForInstances := 100; FTimeOutWaitForInstances := 100;
xID := 'SI_'+ExtractFileName(ParamStr(0));
for I := 1 to Length(xID) do
case xID[I] of
'a'..'z', 'A'..'Z', '0'..'9', '_': begin end;
else
xID[I] := '_';
end;
ID := xID;
end; end;
destructor TBaseSingleInstance.Destroy; destructor TBaseSingleInstance.Destroy;
@ -219,13 +90,6 @@ begin
inherited Destroy; inherited Destroy;
end; end;
procedure TBaseSingleInstance.DoServerReceivedCustomRequest(
const aMsgID: Integer; const aMsgType: TMessageType; const aStream: TStream);
begin
if Assigned(FOnServerReceivedCustomRequest) then
FOnServerReceivedCustomRequest(Self, aMsgID, aMsgType, aStream);
end;
procedure TBaseSingleInstance.DoServerReceivedParams( procedure TBaseSingleInstance.DoServerReceivedParams(
const aParamsDelimitedText: string); const aParamsDelimitedText: string);
var var
@ -243,177 +107,16 @@ begin
end; end;
end; end;
function TBaseSingleInstance.GetIsClient: Boolean;
begin
Result := Assigned(FClient);
end;
function TBaseSingleInstance.GetIsServer: Boolean;
begin
Result := Assigned(FServer);
end;
function TBaseSingleInstance.GetStartResult: TSingleInstanceStart; function TBaseSingleInstance.GetStartResult: TSingleInstanceStart;
begin begin
if not(Assigned(FServer) or Assigned(FClient)) then
raise ESingleInstance.Create(SErrSingleInstanceStartResultNotAvailable);
Result := FStartResult; Result := FStartResult;
end; end;
procedure TBaseSingleInstance.ServerCheckMessages; Procedure TBaseSingleInstance.SetStartResult(AValue : TSingleInstanceStart);
var
xMsgID: Integer;
xMsgType: TMessageType;
xStream: TStream;
xStringStream: TStringStream;
begin begin
if not Assigned(FServer) then FStartResult:=AValue;
raise ESingleInstance.Create(SErrSingleInstanceNotServer); end;
if not FServer.PeekRequest(xMsgID, xMsgType) then
Exit;
case xMsgType of
MSGTYPE_CHECK:
begin
FServer.DeleteRequest(xMsgID);
FServer.PostResponse(xMsgID, MSGTYPE_CHECKRESPONSE, nil);
end;
MSGTYPE_PARAMS:
begin
xStringStream := TStringStream.Create('');
try
FServer.ReadRequest(xMsgID, xStringStream);
DoServerReceivedParams(xStringStream.DataString);
finally
xStringStream.Free;
end;
end;
MSGTYPE_WAITFORINSTANCES:
FServer.DeleteRequest(xMsgID);
else
xStream := TMemoryStream.Create;
try
FServer.ReadRequest(xMsgID, xStream);
DoServerReceivedCustomRequest(xMsgID, xMsgType, xStream);
finally
xStream.Free;
end;
end;
end;
procedure TBaseSingleInstance.ServerPostCustomResponse(
const aRequestID: Integer; const aMsgType: TMessageType;
const aStream: TStream);
begin
if not Assigned(FServer) then
raise ESingleInstance.Create(SErrSingleInstanceNotServer);
FServer.PostResponse(aRequestID, aMsgType, aStream);
end;
procedure TBaseSingleInstance.SetGlobal(const aGlobal: Boolean);
begin
if FGlobal = aGlobal then Exit;
if Assigned(FServer) or Assigned(FClient) then
raise ESingleInstance.Create(SErrSetSingleInstanceGlobalStarted);
FGlobal := aGlobal;
end;
procedure TBaseSingleInstance.SetID(const aID: string);
begin
if FID = aID then Exit;
if Assigned(FServer) or Assigned(FClient) then
raise ESingleInstance.Create(SErrSetSingleInstanceIDStarted);
FID := aID;
end;
procedure TBaseSingleInstance.Stop;
begin
FreeAndNil(FServer);
FreeAndNil(FClient);
end;
function TBaseSingleInstance.Start: TSingleInstanceStart;
{$IFNDEF MSWINDOWS}
procedure UnixWorkaround(var bServerStarted: Boolean);
var
xWaitRequestID, xLastCount, xNewCount: Integer;
xClient: TIPCClient;
begin
//file locking workaround for UNIX systems -> the server can be started twice if 2 processes are started in parallel
//wait some time to see other clients
FServer.StopServer(False);
xClient := TIPCClient.Create(Self);
try
xClient.ServerID := FID;
xClient.Global := FGlobal;
xWaitRequestID := xClient.PostRequest(MSGTYPE_WAITFORINSTANCES, nil);
xLastCount := -1;
xNewCount := FServer.GetPendingRequestCount;
while xLastCount <> xNewCount do
begin
xLastCount := xNewCount;
Sleep(FTimeOutWaitForInstances);
xNewCount := FServer.GetPendingRequestCount;
end;
finally
FreeAndNil(xClient);
end;
//find highest client that will be the server
if FServer.FindHighestPendingRequestId = xWaitRequestID then
begin
bServerStarted := FServer.StartServer(False);
end else
begin
//something went wrong, there are not-deleted waiting requests
//use random sleep as workaround and try to restart the server
Randomize;
Sleep(Random(($3F+PtrInt(GetCurrentThreadId)) and $3F));//limit to $3F (63)
bServerStarted := FServer.StartServer(False) and (FServer.GetPendingRequestCount > 0);
end;
end;
{$ENDIF}
var
xStream: TStream;
xMsgType: TMessageType;
xServerStarted: Boolean;
begin
if Assigned(FServer) or Assigned(FClient) then
raise ESingleInstance.Create(SErrStartSingleInstanceStarted);
FServer := TIPCServer.Create(Self);
FServer.ServerID := FID;
FServer.Global := FGlobal;
xServerStarted := FServer.StartServer(False);
if xServerStarted then
begin//this is single instance -> be server
Result := siServer;
{$IFNDEF MSWINDOWS}
UnixWorkaround(xServerStarted);
{$ENDIF}
end;
if not xServerStarted then
begin//instance found -> be client
FreeAndNil(FServer);
FClient := TIPCClient.Create(Self);
FClient.ServerID := FID;
FClient.Global := FGlobal;
FClient.PostRequest(MSGTYPE_CHECK, nil);
xStream := TMemoryStream.Create;
try
if FClient.PeekResponse(xStream, xMsgType, FTimeOutMessages) then
Result := siClient
else
Result := siNotResponding;
finally
xStream.Free;
end;
end;
FStartResult := Result;
end;
end. end.