|
@@ -0,0 +1,1094 @@
|
|
|
+{
|
|
|
+ This file is part of the Free Component Library (FCL)
|
|
|
+ Copyright (c) 2015 by Ondrej Pokorny
|
|
|
+
|
|
|
+ Unit implementing two-way (request/response) IPC between 1 server and more
|
|
|
+ clients, based on files.
|
|
|
+ The order of message processing is not deterministic (if there are more
|
|
|
+ pending messages, the server won't process them in the order they have
|
|
|
+ been sent to the server.
|
|
|
+ SendRequest and PostRequest+PeekResponse sequences from 1 client are
|
|
|
+ blocking and processed in correct order.
|
|
|
+
|
|
|
+ See the file COPYING.FPC, included in this distribution,
|
|
|
+ for details about the copyright.
|
|
|
+
|
|
|
+ This program is distributed in the hope that it will be useful,
|
|
|
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
|
+
|
|
|
+ **********************************************************************}
|
|
|
+
|
|
|
+unit AdvancedIPC;
|
|
|
+
|
|
|
+{$mode objfpc}
|
|
|
+{$H+}
|
|
|
+
|
|
|
+interface
|
|
|
+
|
|
|
+uses
|
|
|
+ {$IFDEF UNIX}
|
|
|
+ baseunix,
|
|
|
+ {$endif}
|
|
|
+ sysutils, Classes, singleinstance;
|
|
|
+
|
|
|
+const
|
|
|
+ HEADER_VERSION = 2;
|
|
|
+
|
|
|
+type
|
|
|
+ TMessageType = LongInt;
|
|
|
+ TMessageHeader = packed record
|
|
|
+ HeaderVersion: Byte;
|
|
|
+ FileLock: Byte;//0 = unlocked, 1 = locked
|
|
|
+ MsgType: TMessageType;
|
|
|
+ MsgLen: Integer;
|
|
|
+ MsgVersion: Integer;
|
|
|
+ end;
|
|
|
+
|
|
|
+ TFileHandle = Classes.THandle;
|
|
|
+
|
|
|
+ TReleaseHandleStream = class(THandleStream)
|
|
|
+ public
|
|
|
+ destructor Destroy; override;
|
|
|
+ end;
|
|
|
+
|
|
|
+ TIPCBase = class(TComponent)
|
|
|
+ private
|
|
|
+ FGlobal: Boolean;
|
|
|
+ FFileName: string;
|
|
|
+ FServerID: string;
|
|
|
+ FMessageVersion: Integer;
|
|
|
+ protected
|
|
|
+ class function ServerIDToFileName(const aServerID: string; const aGlobal: Boolean): string;
|
|
|
+ function GetResponseFileName(const aRequestID: Integer): string;
|
|
|
+ function GetResponseFileName(const aRequestFileName: string): string;
|
|
|
+ function GetPeekedRequestFileName(const aRequestID: Integer): string;
|
|
|
+ function GetPeekedRequestFileName(const aRequestFileName: string): string;
|
|
|
+ function GetRequestPrefix: string;
|
|
|
+ function GetRequestFileName(const aRequestID: Integer): string;
|
|
|
+ function RequestFileNameToID(const aFileName: string): Integer;
|
|
|
+ function RequestExists(const aRequestFileName: string): Boolean;
|
|
|
+
|
|
|
+ procedure SetServerID(const aServerID: string); virtual;
|
|
|
+ procedure SetGlobal(const aGlobal: Boolean); virtual;
|
|
|
+
|
|
|
+ function CanReadMessage(const aFileName: string; out outStream: TStream; out outMsgType: TMessageType; out outMsgLen: Integer): Boolean;
|
|
|
+ procedure DoPostMessage(const aFileName: string; const aMsgType: TMessageType; const aStream: TStream); overload;
|
|
|
+ procedure DoPostMessage(const aFileStream: TFileStream; const aMsgType: TMessageType; const aStream: TStream); overload;
|
|
|
+ function DoReadMessage(const aFileName: string; const aStream: TStream; out outMsgType: TMessageType): Boolean;
|
|
|
+
|
|
|
+ property FileName: string read FFileName;
|
|
|
+ public
|
|
|
+ class procedure FindRunningServers(const aServerIDPrefix: string;
|
|
|
+ const outServerIDs: TStrings; const aGlobal: Boolean = False);
|
|
|
+ class function ServerRunning(const aServerID: string; const aGlobal: Boolean = False): Boolean; overload;
|
|
|
+ public
|
|
|
+ //ServerID: name/ID of the server. Use only ['a'..'z', 'A'..'Z', '0'..'9', '_'] characters
|
|
|
+ property ServerID: string read FServerID write SetServerID;
|
|
|
+ //Global: if true, processes from different users can communicate; false, processes only from current user can communicate
|
|
|
+ property Global: Boolean read FGlobal write SetGlobal;
|
|
|
+ //MessageVersion: only messages with the same MessageVersion can be delivered between server/client
|
|
|
+ property MessageVersion: Integer read FMessageVersion write FMessageVersion;
|
|
|
+ end;
|
|
|
+
|
|
|
+ TIPCClient = class(TIPCBase)
|
|
|
+ private
|
|
|
+ FLastRequestID: Integer;
|
|
|
+
|
|
|
+ function CreateUniqueRequest(out outFileStream: TFileStream): Integer;
|
|
|
+ function DoPeekResponse(const aResponseFileName: string; const aStream: TStream; out outMsgType: TMessageType; const aTimeOut: Integer): Boolean;
|
|
|
+ public
|
|
|
+ constructor Create(aOwner: TComponent); override;
|
|
|
+ public
|
|
|
+ //post request to server, do not wait until request is peeked; returns request ID
|
|
|
+ function PostRequest(const aMsgType: TMessageType; const aStream: TStream): Integer;
|
|
|
+ //send request to server, wait until request is peeked; returns True if request was peeked within the aTimeOut limit
|
|
|
+ function SendRequest(const aMsgType: TMessageType; const aStream: TStream; const aTimeOut: Integer): Boolean;
|
|
|
+ function SendRequest(const aMsgType: TMessageType; const aStream: TStream; const aTimeOut: Integer; out outRequestID: Integer): Boolean;
|
|
|
+ //peek a response from last request from this client
|
|
|
+ function PeekResponse(const aStream: TStream; out outMsgType: TMessageType): Boolean; overload;
|
|
|
+ function PeekResponse(const aStream: TStream; out outMsgType: TMessageType; const aTimeOut: Integer): Boolean; overload;
|
|
|
+ //peek a response from request by ID
|
|
|
+ function PeekResponse(const aRequestID: Integer; const aStream: TStream; out outMsgType: TMessageType): Boolean; overload;
|
|
|
+ function PeekResponse(const aRequestID: Integer; const aStream: TStream; out outMsgType: TMessageType; const aTimeOut: Integer): Boolean; overload;
|
|
|
+ //delete last request from this client, returns true if request file existed and was deleted
|
|
|
+ function DeleteRequest: Boolean; overload;
|
|
|
+ //delete request by ID, returns true if request existed file and was deleted
|
|
|
+ function DeleteRequest(const aRequestID: Integer): Boolean; overload;
|
|
|
+ //check if server is running
|
|
|
+ function ServerRunning: Boolean; overload;
|
|
|
+ end;
|
|
|
+
|
|
|
+ TIPCServer = class(TIPCBase)
|
|
|
+ private
|
|
|
+ FFileHandle: TFileHandle;
|
|
|
+ FActive: Boolean;
|
|
|
+
|
|
|
+ function FindFirstRequest(out outFileName: string; out outStream: TStream; out outMsgType: TMessageType; out outMsgLen: Integer): Integer;
|
|
|
+
|
|
|
+ protected
|
|
|
+ procedure SetServerID(const aServerID: string); override;
|
|
|
+ procedure SetGlobal(const aGlobal: Boolean); override;
|
|
|
+ public
|
|
|
+ constructor Create(aOwner: TComponent); override;
|
|
|
+ destructor Destroy; override;
|
|
|
+ public
|
|
|
+ //peek request and read the message into a stream
|
|
|
+ function PeekRequest(const aStream: TStream; out outMsgType: TMessageType): Boolean; overload;
|
|
|
+ function PeekRequest(const aStream: TStream; out outRequestID: Integer; out outMsgType: TMessageType): Boolean; overload;
|
|
|
+ function PeekRequest(const aStream: TStream; out outRequestID: Integer; out outMsgType: TMessageType; const aTimeOut: Integer): Boolean; overload;
|
|
|
+ //only peek request, you have to read/delete the request manually with ReadRequest/DeleteRequest
|
|
|
+ function PeekRequest(out outMsgType: TMessageType): Boolean; overload;
|
|
|
+ function PeekRequest(out outRequestID: Integer; out outMsgType: TMessageType): Boolean; overload;
|
|
|
+ function PeekRequest(out outRequestID: Integer; out outMsgType: TMessageType; const aTimeOut: Integer): Boolean; overload;
|
|
|
+ //read a peeked request (that hasn't been read yet)
|
|
|
+ function ReadRequest(const aRequestID: Integer; const aStream: TStream): Boolean;
|
|
|
+ //delete a peeked request (that hasn't been read yet), returns true if request file existed and was deleted
|
|
|
+ function DeleteRequest(const aRequestID: Integer): Boolean;
|
|
|
+
|
|
|
+ //post response to a request
|
|
|
+ procedure PostResponse(const aRequestID: Integer; const aMsgType: TMessageType; const aStream: TStream);
|
|
|
+
|
|
|
+ //find the highest request ID from all pending requests
|
|
|
+ function FindHighestPendingRequestId: Integer;
|
|
|
+ //get the pending request count
|
|
|
+ function GetPendingRequestCount: Integer;
|
|
|
+
|
|
|
+ //start server: returns true if unique and started
|
|
|
+ function StartServer(const aDeletePendingRequests: Boolean = True): Boolean;
|
|
|
+ //stop server: returns true if stopped
|
|
|
+ function StopServer(const aDeletePendingRequests: Boolean = True): Boolean;
|
|
|
+
|
|
|
+ //delete all pending requests and responses
|
|
|
+ procedure DeletePendingRequests;
|
|
|
+ public
|
|
|
+ //true if server runs (was started)
|
|
|
+ property Active: Boolean read FActive;
|
|
|
+ end;
|
|
|
+
|
|
|
+ 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
|
|
|
+ SErrInvalidServerID = 'Invalid server ID "%s". Please use only alphanumerical characters and underlines.';
|
|
|
+ SErrSetGlobalActive = 'You cannot change the global property when the server is active.';
|
|
|
+ SErrSetServerIDActive = 'You cannot change the server ID when the server is active.';
|
|
|
+
|
|
|
+implementation
|
|
|
+
|
|
|
+type
|
|
|
+ TIPCSearchRec = TRawByteSearchRec;
|
|
|
+
|
|
|
+const
|
|
|
+ {$IFDEF UNIX}
|
|
|
+ GLOBAL_RIGHTS = S_IRUSR or S_IWUSR or S_IRGRP or S_IWGRP or S_IROTH or S_IWOTH;
|
|
|
+ {$ELSE}
|
|
|
+ GLOBAL_RIGHTS = 0;
|
|
|
+ {$ENDIF}
|
|
|
+
|
|
|
+var
|
|
|
+ CreateUniqueRequestCritSec: TRTLCriticalSection;
|
|
|
+
|
|
|
+{ TIPCBase }
|
|
|
+
|
|
|
+function TIPCBase.CanReadMessage(const aFileName: string; out
|
|
|
+ outStream: TStream; out outMsgType: TMessageType; out outMsgLen: Integer
|
|
|
+ ): Boolean;
|
|
|
+var
|
|
|
+ xFileHandle: TFileHandle;
|
|
|
+ xHeader: TMessageHeader;
|
|
|
+begin
|
|
|
+ outStream := nil;
|
|
|
+ outMsgType := -1;
|
|
|
+ outMsgLen := 0;
|
|
|
+ Result := FileExists(aFileName);
|
|
|
+ if not Result then
|
|
|
+ Exit;
|
|
|
+
|
|
|
+ xFileHandle := FileOpen(aFileName, fmOpenRead or fmShareExclusive);
|
|
|
+ Result := xFileHandle <> feInvalidHandle;
|
|
|
+ if not Result then
|
|
|
+ Exit;
|
|
|
+
|
|
|
+ outStream := TReleaseHandleStream.Create(xFileHandle);
|
|
|
+
|
|
|
+ Result := (outStream.Size >= SizeOf(xHeader));
|
|
|
+ if not Result then
|
|
|
+ begin
|
|
|
+ FreeAndNil(outStream);
|
|
|
+ Exit;
|
|
|
+ end;
|
|
|
+
|
|
|
+ outStream.ReadBuffer(xHeader{%H-}, SizeOf(xHeader));
|
|
|
+ Result :=
|
|
|
+ (xHeader.HeaderVersion = HEADER_VERSION) and (xHeader.FileLock = 0) and
|
|
|
+ (xHeader.MsgVersion = MessageVersion) and
|
|
|
+ (outStream.Size = Int64(SizeOf(xHeader))+Int64(xHeader.MsgLen));
|
|
|
+ if not Result then
|
|
|
+ begin
|
|
|
+ FreeAndNil(outStream);
|
|
|
+ Exit;
|
|
|
+ end;
|
|
|
+ outMsgType := xHeader.MsgType;
|
|
|
+ outMsgLen := xHeader.MsgLen;
|
|
|
+end;
|
|
|
+
|
|
|
+function TIPCBase.DoReadMessage(const aFileName: string;
|
|
|
+ const aStream: TStream; out outMsgType: TMessageType): Boolean;
|
|
|
+var
|
|
|
+ xStream: TStream;
|
|
|
+ xMsgLen: Integer;
|
|
|
+begin
|
|
|
+ aStream.Size := 0;
|
|
|
+ xStream := nil;
|
|
|
+ try
|
|
|
+ Result := CanReadMessage(aFileName, xStream, outMsgType, xMsgLen);
|
|
|
+ if Result then
|
|
|
+ begin
|
|
|
+ if xMsgLen > 0 then
|
|
|
+ aStream.CopyFrom(xStream, xMsgLen);
|
|
|
+ FreeAndNil(xStream);
|
|
|
+ aStream.Position := 0;
|
|
|
+ DeleteFile(aFileName);
|
|
|
+ end;
|
|
|
+ finally
|
|
|
+ xStream.Free;
|
|
|
+ end;
|
|
|
+end;
|
|
|
+
|
|
|
+function TIPCBase.RequestExists(const aRequestFileName: string): Boolean;
|
|
|
+begin
|
|
|
+ Result :=
|
|
|
+ (FileExists(aRequestFileName) or
|
|
|
+ FileExists(GetResponseFileName(aRequestFileName)) or
|
|
|
+ FileExists(GetPeekedRequestFileName(aRequestFileName)));
|
|
|
+end;
|
|
|
+
|
|
|
+class function TIPCBase.ServerRunning(const aServerID: string;
|
|
|
+ const aGlobal: Boolean): Boolean;
|
|
|
+var
|
|
|
+ xServerFileHandle: TFileHandle;
|
|
|
+ xFileName: String;
|
|
|
+begin
|
|
|
+ xFileName := ServerIDToFileName(aServerID, aGlobal);
|
|
|
+ Result := FileExists(xFileName);
|
|
|
+ if Result then
|
|
|
+ begin//+ check -> we should not be able to access the file
|
|
|
+ xServerFileHandle := FileCreate(xFileName, fmOpenReadWrite or fmShareExclusive, GLOBAL_RIGHTS);
|
|
|
+ Result := (xServerFileHandle=feInvalidHandle);
|
|
|
+ if not Result then
|
|
|
+ FileClose(xServerFileHandle);
|
|
|
+ end;
|
|
|
+end;
|
|
|
+
|
|
|
+class function TIPCBase.ServerIDToFileName(const aServerID: string;
|
|
|
+ const aGlobal: Boolean): string;
|
|
|
+begin
|
|
|
+ Result := GetTempDir(aGlobal)+aServerID;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TIPCBase.SetGlobal(const aGlobal: Boolean);
|
|
|
+begin
|
|
|
+ if FGlobal = aGlobal then Exit;
|
|
|
+
|
|
|
+ FGlobal := aGlobal;
|
|
|
+ FFileName := ServerIDToFileName(FServerID, FGlobal);
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TIPCBase.DoPostMessage(const aFileName: string;
|
|
|
+ const aMsgType: TMessageType; const aStream: TStream);
|
|
|
+var
|
|
|
+ xStream: TFileStream;
|
|
|
+begin
|
|
|
+ xStream := TFileStream.Create(aFileName, fmCreate or fmShareExclusive, GLOBAL_RIGHTS);
|
|
|
+ try
|
|
|
+ DoPostMessage(xStream, aMsgType, aStream);
|
|
|
+ finally
|
|
|
+ xStream.Free;
|
|
|
+ end;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TIPCBase.DoPostMessage(const aFileStream: TFileStream;
|
|
|
+ const aMsgType: TMessageType; const aStream: TStream);
|
|
|
+var
|
|
|
+ xHeader: TMessageHeader;
|
|
|
+begin
|
|
|
+ xHeader.HeaderVersion := HEADER_VERSION;
|
|
|
+ xHeader.FileLock := 1;//locking
|
|
|
+ xHeader.MsgType := aMsgType;
|
|
|
+ if Assigned(aStream) then
|
|
|
+ xHeader.MsgLen := aStream.Size-aStream.Position
|
|
|
+ else
|
|
|
+ xHeader.MsgLen := 0;
|
|
|
+ xHeader.MsgVersion := MessageVersion;
|
|
|
+
|
|
|
+ aFileStream.WriteBuffer(xHeader, SizeOf(xHeader));
|
|
|
+ if Assigned(aStream) and (aStream.Size-aStream.Position > 0) then
|
|
|
+ aFileStream.CopyFrom(aStream, aStream.Size-aStream.Position);
|
|
|
+
|
|
|
+ aFileStream.Position := 0;//unlocking
|
|
|
+ xHeader.FileLock := 0;
|
|
|
+ aFileStream.WriteBuffer(xHeader, SizeOf(xHeader));
|
|
|
+ aFileStream.Seek(0, soEnd);
|
|
|
+end;
|
|
|
+
|
|
|
+function TIPCBase.RequestFileNameToID(const aFileName: string): Integer;
|
|
|
+begin
|
|
|
+ //the function prevents all responses/temp files to be handled
|
|
|
+ //only valid response files are returned
|
|
|
+ if (Length(aFileName) > 9) and (aFileName[Length(aFileName)-8] = '-') then
|
|
|
+ Result := StrToIntDef('$'+Copy(aFileName, Length(aFileName)-7, 8), -1)
|
|
|
+ else
|
|
|
+ Result := -1;
|
|
|
+end;
|
|
|
+
|
|
|
+class procedure TIPCBase.FindRunningServers(const aServerIDPrefix: string;
|
|
|
+ const outServerIDs: TStrings; const aGlobal: Boolean);
|
|
|
+var
|
|
|
+ xRec: TIPCSearchRec;
|
|
|
+begin
|
|
|
+ if FindFirst(ServerIDToFileName(aServerIDPrefix+AllFilesMask, aGlobal), faAnyFile, xRec) = 0 then
|
|
|
+ begin
|
|
|
+ repeat
|
|
|
+ if (Pos('-', xRec.Name) = 0) and//file that we found is a pending message
|
|
|
+ ServerRunning(xRec.Name, aGlobal)
|
|
|
+ then
|
|
|
+ outServerIDs.Add(xRec.Name);
|
|
|
+ until FindNext(xRec) <> 0;
|
|
|
+ end;
|
|
|
+ FindClose(xRec);
|
|
|
+end;
|
|
|
+
|
|
|
+function TIPCBase.GetPeekedRequestFileName(const aRequestID: Integer): string;
|
|
|
+begin
|
|
|
+ Result := GetPeekedRequestFileName(GetRequestFileName(aRequestID));
|
|
|
+end;
|
|
|
+
|
|
|
+function TIPCBase.GetPeekedRequestFileName(const aRequestFileName: string
|
|
|
+ ): string;
|
|
|
+begin
|
|
|
+ Result := aRequestFileName+'-t';
|
|
|
+end;
|
|
|
+
|
|
|
+function TIPCBase.GetRequestFileName(const aRequestID: Integer): string;
|
|
|
+begin
|
|
|
+ Result := GetRequestPrefix+IntToHex(aRequestID, 8);
|
|
|
+end;
|
|
|
+
|
|
|
+function TIPCBase.GetRequestPrefix: string;
|
|
|
+begin
|
|
|
+ Result := FFileName+'-';
|
|
|
+end;
|
|
|
+
|
|
|
+function TIPCBase.GetResponseFileName(const aRequestID: Integer): string;
|
|
|
+begin
|
|
|
+ Result := GetResponseFileName(GetRequestFileName(aRequestID));
|
|
|
+end;
|
|
|
+
|
|
|
+function TIPCBase.GetResponseFileName(const aRequestFileName: string): string;
|
|
|
+begin
|
|
|
+ Result := aRequestFileName+'-r';
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TIPCBase.SetServerID(const aServerID: string);
|
|
|
+var
|
|
|
+ I: Integer;
|
|
|
+begin
|
|
|
+ if FServerID = aServerID then Exit;
|
|
|
+
|
|
|
+ for I := 1 to Length(aServerID) do
|
|
|
+ if not (aServerID[I] in ['a'..'z', 'A'..'Z', '0'..'9', '_']) then
|
|
|
+ raise EICPException.CreateFmt(SErrInvalidServerID , [aServerID]);
|
|
|
+
|
|
|
+ FServerID := aServerID;
|
|
|
+
|
|
|
+ FFileName := ServerIDToFileName(FServerID, FGlobal);
|
|
|
+end;
|
|
|
+
|
|
|
+{ TIPCClient }
|
|
|
+
|
|
|
+constructor TIPCClient.Create(aOwner: TComponent);
|
|
|
+begin
|
|
|
+ inherited Create(aOwner);
|
|
|
+
|
|
|
+ FLastRequestID := -1;
|
|
|
+end;
|
|
|
+
|
|
|
+function TIPCClient.DeleteRequest(const aRequestID: Integer): Boolean;
|
|
|
+var
|
|
|
+ xRequestFileName: string;
|
|
|
+begin
|
|
|
+ xRequestFileName := GetRequestFileName(aRequestID);
|
|
|
+ Result := DeleteFile(xRequestFileName);
|
|
|
+ if (aRequestID = FLastRequestID) and not FileExists(xRequestFileName) then
|
|
|
+ FLastRequestID := -1;
|
|
|
+end;
|
|
|
+
|
|
|
+function TIPCClient.DeleteRequest: Boolean;
|
|
|
+begin
|
|
|
+ if FLastRequestID >= 0 then
|
|
|
+ Result := DeleteRequest(FLastRequestID)
|
|
|
+ else
|
|
|
+ Result := False;
|
|
|
+end;
|
|
|
+
|
|
|
+function TIPCClient.DoPeekResponse(const aResponseFileName: string;
|
|
|
+ const aStream: TStream; out outMsgType: TMessageType; const aTimeOut: Integer
|
|
|
+ ): Boolean;
|
|
|
+var
|
|
|
+ xStart: QWord;
|
|
|
+begin
|
|
|
+ aStream.Size := 0;
|
|
|
+ Result := False;
|
|
|
+ xStart := GetTickCount64;
|
|
|
+ repeat
|
|
|
+ if DoReadMessage(aResponseFileName, aStream, outMsgType) then
|
|
|
+ Exit(True)
|
|
|
+ else if aTimeOut > 20 then
|
|
|
+ Sleep(10);
|
|
|
+ until (GetTickCount64-xStart > aTimeOut);
|
|
|
+end;
|
|
|
+
|
|
|
+function TIPCClient.CreateUniqueRequest(out outFileStream: TFileStream): Integer;
|
|
|
+var
|
|
|
+ xFileName: string;
|
|
|
+begin
|
|
|
+ outFileStream := nil;
|
|
|
+ EnterCriticalsection(CreateUniqueRequestCritSec);
|
|
|
+ try
|
|
|
+ Randomize;
|
|
|
+ repeat
|
|
|
+ //if Randomize/Random is started from 2 processes at exactly same moment, it returns the same number! -> prevent duplicates by xor GetProcessId
|
|
|
+ //the result must be of range 0..$7FFFFFFF (High(Integer))
|
|
|
+ Result := Integer((PtrInt(Random($7FFFFFFF)) xor PtrInt(GetProcessID)) and $7FFFFFFF);
|
|
|
+ xFileName := GetRequestFileName(Result);
|
|
|
+ until not RequestExists(xFileName);
|
|
|
+
|
|
|
+ outFileStream := TFileStream.Create(xFileName, fmCreate or fmShareExclusive, GLOBAL_RIGHTS);
|
|
|
+ finally
|
|
|
+ LeaveCriticalsection(CreateUniqueRequestCritSec);
|
|
|
+ end;
|
|
|
+end;
|
|
|
+
|
|
|
+function TIPCClient.PeekResponse(const aRequestID: Integer;
|
|
|
+ const aStream: TStream; out outMsgType: TMessageType): Boolean;
|
|
|
+begin
|
|
|
+ Result := DoReadMessage(GetResponseFileName(aRequestID), aStream, outMsgType);
|
|
|
+end;
|
|
|
+
|
|
|
+function TIPCClient.PeekResponse(const aRequestID: Integer;
|
|
|
+ const aStream: TStream; out outMsgType: TMessageType; const aTimeOut: Integer
|
|
|
+ ): Boolean;
|
|
|
+begin
|
|
|
+ Result := DoPeekResponse(GetResponseFileName(aRequestID), aStream, outMsgType, aTimeOut);
|
|
|
+end;
|
|
|
+
|
|
|
+function TIPCClient.PeekResponse(const aStream: TStream; out
|
|
|
+ outMsgType: TMessageType): Boolean;
|
|
|
+begin
|
|
|
+ Result := DoReadMessage(GetResponseFileName(FLastRequestID), aStream, outMsgType);
|
|
|
+end;
|
|
|
+
|
|
|
+function TIPCClient.PeekResponse(const aStream: TStream; out
|
|
|
+ outMsgType: TMessageType; const aTimeOut: Integer): Boolean;
|
|
|
+begin
|
|
|
+ Result := DoPeekResponse(GetResponseFileName(FLastRequestID), aStream, outMsgType, aTimeOut);
|
|
|
+end;
|
|
|
+
|
|
|
+function TIPCClient.PostRequest(const aMsgType: TMessageType;
|
|
|
+ const aStream: TStream): Integer;
|
|
|
+var
|
|
|
+ xRequestFileStream: TFileStream;
|
|
|
+begin
|
|
|
+ xRequestFileStream := nil;
|
|
|
+ try
|
|
|
+ Result := CreateUniqueRequest(xRequestFileStream);
|
|
|
+ DoPostMessage(xRequestFileStream, aMsgType, aStream);
|
|
|
+ finally
|
|
|
+ xRequestFileStream.Free;
|
|
|
+ end;
|
|
|
+ FLastRequestID := Result;
|
|
|
+end;
|
|
|
+
|
|
|
+function TIPCClient.SendRequest(const aMsgType: TMessageType;
|
|
|
+ const aStream: TStream; const aTimeOut: Integer): Boolean;
|
|
|
+var
|
|
|
+ xRequestID: Integer;
|
|
|
+begin
|
|
|
+ Result := SendRequest(aMsgType, aStream, aTimeOut, xRequestID);
|
|
|
+end;
|
|
|
+
|
|
|
+function TIPCClient.SendRequest(const aMsgType: TMessageType;
|
|
|
+ const aStream: TStream; const aTimeOut: Integer; out outRequestID: Integer
|
|
|
+ ): Boolean;
|
|
|
+var
|
|
|
+ xStart: QWord;
|
|
|
+ xRequestFileName: string;
|
|
|
+begin
|
|
|
+ outRequestID := PostRequest(aMsgType, aStream);
|
|
|
+ Result := False;
|
|
|
+
|
|
|
+ xRequestFileName := GetRequestFileName(outRequestID);
|
|
|
+ xStart := GetTickCount64;
|
|
|
+ repeat
|
|
|
+ if not FileExists(xRequestFileName) then
|
|
|
+ Exit(True)
|
|
|
+ else if aTimeOut > 20 then
|
|
|
+ Sleep(10);
|
|
|
+ until (GetTickCount64-xStart > aTimeOut);
|
|
|
+end;
|
|
|
+
|
|
|
+function TIPCClient.ServerRunning: Boolean;
|
|
|
+begin
|
|
|
+ Result := ServerRunning(ServerID, Global);
|
|
|
+end;
|
|
|
+
|
|
|
+{ TReleaseHandleStream }
|
|
|
+
|
|
|
+destructor TReleaseHandleStream.Destroy;
|
|
|
+begin
|
|
|
+ FileClose(Handle);
|
|
|
+
|
|
|
+ inherited Destroy;
|
|
|
+end;
|
|
|
+
|
|
|
+{ TIPCServer }
|
|
|
+
|
|
|
+procedure TIPCServer.DeletePendingRequests;
|
|
|
+var
|
|
|
+ xRec: TIPCSearchRec;
|
|
|
+ xDir: string;
|
|
|
+begin
|
|
|
+ xDir := ExtractFilePath(FFileName);
|
|
|
+ if FindFirst(GetRequestPrefix+AllFilesMask, faAnyFile, xRec) = 0 then
|
|
|
+ begin
|
|
|
+ repeat
|
|
|
+ DeleteFile(xDir+xRec.Name);
|
|
|
+ until FindNext(xRec) <> 0;
|
|
|
+ end;
|
|
|
+ FindClose(xRec);
|
|
|
+end;
|
|
|
+
|
|
|
+function TIPCServer.DeleteRequest(const aRequestID: Integer): Boolean;
|
|
|
+begin
|
|
|
+ Result := DeleteFile(GetPeekedRequestFileName(aRequestID));
|
|
|
+end;
|
|
|
+
|
|
|
+constructor TIPCServer.Create(aOwner: TComponent);
|
|
|
+begin
|
|
|
+ inherited Create(aOwner);
|
|
|
+
|
|
|
+ FFileHandle := feInvalidHandle;
|
|
|
+end;
|
|
|
+
|
|
|
+destructor TIPCServer.Destroy;
|
|
|
+begin
|
|
|
+ if Active then
|
|
|
+ StopServer;
|
|
|
+
|
|
|
+ inherited Destroy;
|
|
|
+end;
|
|
|
+
|
|
|
+function TIPCServer.FindFirstRequest(out outFileName: string; out
|
|
|
+ outStream: TStream; out outMsgType: TMessageType; out outMsgLen: Integer
|
|
|
+ ): Integer;
|
|
|
+var
|
|
|
+ xRec: TIPCSearchRec;
|
|
|
+begin
|
|
|
+ outFileName := '';
|
|
|
+ outStream := nil;
|
|
|
+ outMsgType := -1;
|
|
|
+ outMsgLen := 0;
|
|
|
+ Result := -1;
|
|
|
+ if FindFirst(GetRequestPrefix+AllFilesMask, faAnyFile, xRec) = 0 then
|
|
|
+ begin
|
|
|
+ repeat
|
|
|
+ Result := RequestFileNameToID(xRec.Name);
|
|
|
+ if Result >= 0 then
|
|
|
+ begin
|
|
|
+ outFileName := GetRequestFileName(Result);
|
|
|
+ if not CanReadMessage(outFileName, outStream, outMsgType, outMsgLen) then
|
|
|
+ Result := -1;
|
|
|
+ end;
|
|
|
+ until (Result >= 0) or (FindNext(xRec) <> 0);
|
|
|
+ end;
|
|
|
+ FindClose(xRec);
|
|
|
+end;
|
|
|
+
|
|
|
+function TIPCServer.FindHighestPendingRequestId: Integer;
|
|
|
+var
|
|
|
+ xRec: TIPCSearchRec;
|
|
|
+ xRequestID: LongInt;
|
|
|
+begin
|
|
|
+ Result := -1;
|
|
|
+ if FindFirst(GetRequestPrefix+AllFilesMask, faAnyFile, xRec) = 0 then
|
|
|
+ begin
|
|
|
+ repeat
|
|
|
+ xRequestID := RequestFileNameToID(xRec.Name);
|
|
|
+ if xRequestID > Result then
|
|
|
+ Result := xRequestID;
|
|
|
+ until FindNext(xRec) <> 0;
|
|
|
+ end;
|
|
|
+ FindClose(xRec);
|
|
|
+end;
|
|
|
+
|
|
|
+function TIPCServer.GetPendingRequestCount: Integer;
|
|
|
+var
|
|
|
+ xRec: TIPCSearchRec;
|
|
|
+begin
|
|
|
+ Result := 0;
|
|
|
+ if FindFirst(GetRequestPrefix+AllFilesMask, faAnyFile, xRec) = 0 then
|
|
|
+ begin
|
|
|
+ repeat
|
|
|
+ if RequestFileNameToID(xRec.Name) >= 0 then
|
|
|
+ Inc(Result);
|
|
|
+ until FindNext(xRec) <> 0;
|
|
|
+ end;
|
|
|
+ FindClose(xRec);
|
|
|
+end;
|
|
|
+
|
|
|
+function TIPCServer.PeekRequest(out outRequestID: Integer; out
|
|
|
+ outMsgType: TMessageType): Boolean;
|
|
|
+var
|
|
|
+ xStream: TStream;
|
|
|
+ xMsgLen: Integer;
|
|
|
+ xMsgFileName: string;
|
|
|
+begin
|
|
|
+ outMsgType := -1;
|
|
|
+ xMsgFileName := '';
|
|
|
+ xStream := nil;
|
|
|
+ try
|
|
|
+ outRequestID := FindFirstRequest(xMsgFileName, xStream, outMsgType, xMsgLen);
|
|
|
+ Result := outRequestID >= 0;
|
|
|
+ if Result then
|
|
|
+ begin
|
|
|
+ FreeAndNil(xStream);
|
|
|
+ RenameFile(xMsgFileName, GetPeekedRequestFileName(xMsgFileName));
|
|
|
+ end;
|
|
|
+ finally
|
|
|
+ xStream.Free;
|
|
|
+ end;
|
|
|
+end;
|
|
|
+
|
|
|
+function TIPCServer.PeekRequest(out outRequestID: Integer; out
|
|
|
+ outMsgType: TMessageType; const aTimeOut: Integer): Boolean;
|
|
|
+var
|
|
|
+ xStart: QWord;
|
|
|
+begin
|
|
|
+ Result := False;
|
|
|
+ xStart := GetTickCount64;
|
|
|
+ repeat
|
|
|
+ if PeekRequest(outRequestID, outMsgType) then
|
|
|
+ Exit(True)
|
|
|
+ else if aTimeOut > 20 then
|
|
|
+ Sleep(10);
|
|
|
+ until (GetTickCount64-xStart > aTimeOut);
|
|
|
+end;
|
|
|
+
|
|
|
+function TIPCServer.PeekRequest(out outMsgType: TMessageType): Boolean;
|
|
|
+var
|
|
|
+ xRequestID: Integer;
|
|
|
+begin
|
|
|
+ Result := PeekRequest(xRequestID, outMsgType);
|
|
|
+end;
|
|
|
+
|
|
|
+function TIPCServer.PeekRequest(const aStream: TStream; out outRequestID: Integer;
|
|
|
+ out outMsgType: TMessageType): Boolean;
|
|
|
+begin
|
|
|
+ Result := PeekRequest(outRequestID, outMsgType);
|
|
|
+ if Result then
|
|
|
+ Result := ReadRequest(outRequestID, aStream);
|
|
|
+end;
|
|
|
+
|
|
|
+function TIPCServer.PeekRequest(const aStream: TStream; out outRequestID: Integer;
|
|
|
+ out outMsgType: TMessageType; const aTimeOut: Integer): Boolean;
|
|
|
+begin
|
|
|
+ Result := PeekRequest(outRequestID, outMsgType, aTimeOut);
|
|
|
+ if Result then
|
|
|
+ Result := ReadRequest(outRequestID, aStream);
|
|
|
+end;
|
|
|
+
|
|
|
+function TIPCServer.PeekRequest(const aStream: TStream; out
|
|
|
+ outMsgType: TMessageType): Boolean;
|
|
|
+var
|
|
|
+ xRequestID: Integer;
|
|
|
+begin
|
|
|
+ Result := PeekRequest(aStream, xRequestID, outMsgType);
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TIPCServer.PostResponse(const aRequestID: Integer;
|
|
|
+ const aMsgType: TMessageType; const aStream: TStream);
|
|
|
+begin
|
|
|
+ DoPostMessage(GetResponseFileName(aRequestID), aMsgType, aStream);
|
|
|
+end;
|
|
|
+
|
|
|
+function TIPCServer.ReadRequest(const aRequestID: Integer; const aStream: TStream
|
|
|
+ ): Boolean;
|
|
|
+var
|
|
|
+ xMsgType: TMessageType;
|
|
|
+begin
|
|
|
+ Result := DoReadMessage(GetPeekedRequestFileName(aRequestID), aStream, xMsgType);
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TIPCServer.SetGlobal(const aGlobal: Boolean);
|
|
|
+begin
|
|
|
+ if Active then
|
|
|
+ raise EICPException.Create(SErrSetGlobalActive);
|
|
|
+
|
|
|
+ inherited SetGlobal(aGlobal);
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TIPCServer.SetServerID(const aServerID: string);
|
|
|
+begin
|
|
|
+ if Active then
|
|
|
+ raise EICPException.Create(SErrSetServerIDActive);
|
|
|
+
|
|
|
+ inherited SetServerID(aServerID);
|
|
|
+end;
|
|
|
+
|
|
|
+function TIPCServer.StartServer(const aDeletePendingRequests: Boolean): Boolean;
|
|
|
+begin
|
|
|
+ if Active then
|
|
|
+ Exit(True);
|
|
|
+
|
|
|
+ FFileHandle := FileCreate(FFileName, fmCreate or fmShareExclusive, GLOBAL_RIGHTS);
|
|
|
+ Result := (FFileHandle<>feInvalidHandle);
|
|
|
+ FActive := Result;
|
|
|
+ if Result and aDeletePendingRequests then
|
|
|
+ DeletePendingRequests;
|
|
|
+end;
|
|
|
+
|
|
|
+function TIPCServer.StopServer(const aDeletePendingRequests: Boolean): Boolean;
|
|
|
+begin
|
|
|
+ if not Active then
|
|
|
+ Exit(True);
|
|
|
+
|
|
|
+ if FFileHandle<>feInvalidHandle then
|
|
|
+ FileClose(FFileHandle);
|
|
|
+ Result := DeleteFile(FFileName);
|
|
|
+
|
|
|
+ if aDeletePendingRequests then
|
|
|
+ DeletePendingRequests;
|
|
|
+
|
|
|
+ FActive := False;
|
|
|
+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
|
|
|
+ InitCriticalSection(CreateUniqueRequestCritSec);
|
|
|
+ DefaultSingleInstanceClass:=TAdvancedSingleInstance;
|
|
|
+
|
|
|
+finalization
|
|
|
+ DoneCriticalsection(CreateUniqueRequestCritSec);
|
|
|
+
|
|
|
+end.
|