mirror of
				https://gitlab.com/freepascal.org/lazarus/lazarus.git
				synced 2025-11-04 00:19:57 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			798 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			ObjectPascal
		
	
	
	
	
	
			
		
		
	
	
			798 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			ObjectPascal
		
	
	
	
	
	
{
 | 
						|
    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.
 | 
						|
 | 
						|
    ***
 | 
						|
    This unit is a duplicate of AdvancedIPC.pp from FPC (added in 3.1.1).
 | 
						|
    It should be removed when support of older FPC versions is dropped.
 | 
						|
 | 
						|
 **********************************************************************}
 | 
						|
 | 
						|
unit LazAdvancedIPC;
 | 
						|
 | 
						|
{$mode objfpc}
 | 
						|
{$H+}
 | 
						|
 | 
						|
interface
 | 
						|
 | 
						|
uses
 | 
						|
  {$IFDEF UNIX}
 | 
						|
  baseunix,
 | 
						|
  {$endif}
 | 
						|
  sysutils, Classes, LazFileUtils;
 | 
						|
 | 
						|
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);
 | 
						|
 | 
						|
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 FindFirstUTF8(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 FindNextUTF8(xRec) <> 0;
 | 
						|
  end;
 | 
						|
  FindCloseUTF8(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 {%H-}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 FindFirstUTF8(GetRequestPrefix+AllFilesMask, faAnyFile, xRec) = 0 then
 | 
						|
  begin
 | 
						|
    try
 | 
						|
      repeat
 | 
						|
        DeleteFile(xDir+xRec.Name);
 | 
						|
      until FindNextUTF8(xRec) <> 0;
 | 
						|
    finally
 | 
						|
      FindCloseUTF8(xRec);
 | 
						|
    end;
 | 
						|
  end;
 | 
						|
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 FindFirstUTF8(GetRequestPrefix+AllFilesMask, faAnyFile, xRec) = 0 then
 | 
						|
  begin
 | 
						|
    try
 | 
						|
      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 (FindNextUTF8(xRec) <> 0);
 | 
						|
    finally
 | 
						|
      FindCloseUTF8(xRec);
 | 
						|
    end;
 | 
						|
  end;
 | 
						|
end;
 | 
						|
 | 
						|
function TIPCServer.FindHighestPendingRequestId: Integer;
 | 
						|
var
 | 
						|
  xRec: TIPCSearchRec;
 | 
						|
  xRequestID: LongInt;
 | 
						|
begin
 | 
						|
  Result := -1;
 | 
						|
  if FindFirstUTF8(GetRequestPrefix+AllFilesMask, faAnyFile, xRec) = 0 then
 | 
						|
  begin
 | 
						|
    try
 | 
						|
      repeat
 | 
						|
        xRequestID := RequestFileNameToID(xRec.Name);
 | 
						|
        if xRequestID > Result then
 | 
						|
          Result := xRequestID;
 | 
						|
      until FindNextUTF8(xRec) <> 0;
 | 
						|
    finally
 | 
						|
      FindCloseUTF8(xRec);
 | 
						|
    end;
 | 
						|
  end;
 | 
						|
end;
 | 
						|
 | 
						|
function TIPCServer.GetPendingRequestCount: Integer;
 | 
						|
var
 | 
						|
  xRec: TIPCSearchRec;
 | 
						|
begin
 | 
						|
  Result := 0;
 | 
						|
  if FindFirstUTF8(GetRequestPrefix+AllFilesMask, faAnyFile, xRec) = 0 then
 | 
						|
  begin
 | 
						|
    try
 | 
						|
      repeat
 | 
						|
        if RequestFileNameToID(xRec.Name) >= 0 then
 | 
						|
          Inc(Result);
 | 
						|
      until FindNextUTF8(xRec) <> 0;
 | 
						|
    finally
 | 
						|
      FindCloseUTF8(xRec);
 | 
						|
    end;
 | 
						|
  end;
 | 
						|
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;
 | 
						|
 | 
						|
initialization
 | 
						|
  InitCriticalSection(CreateUniqueRequestCritSec);
 | 
						|
 | 
						|
finalization
 | 
						|
  DoneCriticalsection(CreateUniqueRequestCritSec);
 | 
						|
 | 
						|
end.
 |