1
0
mirror of synced 2024-09-07 21:55:05 +00:00
x2log/X2Log.Client.NamedPipe.pas
Mark van Renswoude 06734475e9 Support for Delphi 10.2 Tokyo
Added packages
Changed deprecated Stream.Seek to Stream.Position
2017-07-06 16:58:29 +02:00

522 lines
12 KiB
ObjectPascal

unit X2Log.Client.NamedPipe;
interface
uses
System.Classes,
System.Generics.Collections,
X2Log.Intf,
X2Log.Client.Base;
type
TX2LogNamedPipeServerInfo = class(TObject)
private
FDisplayName: string;
FPipeName: string;
public
constructor Create(const APipeName: string);
property DisplayName: string read FDisplayName;
property PipeName: string read FPipeName;
end;
TX2LogNamedPipeClient = class(TX2LogBaseClient)
private
FWorkerThread: TThread;
protected
property WorkerThread: TThread read FWorkerThread;
public
constructor Create(const APipeName: string);
destructor Destroy; override;
class function ActiveServers: IEnumerable<TX2LogNamedPipeServerInfo>;
end;
implementation
uses
System.SyncObjs,
System.StrUtils,
System.SysUtils,
System.Types,
Winapi.Windows,
X2Log.Details.Default,
X2Log.Details.Registry,
X2Log.Intf.NamedPipe,
X2Log.Util.Stream;
type
TX2LogNamedPipeClientWorkerThread = class(TThread)
private
FClient: TX2LogNamedPipeClient;
FPipeName: string;
FTerminateEvent: TEvent;
FReadEvent: TEvent;
FPipeHandle: THandle;
FOverlappedRead: TOverlapped;
FReadBuffer: array[0..4095] of Byte;
FMessageData: TMemoryStream;
protected
procedure Execute; override;
procedure TerminatedSet; override;
procedure ConnectPipe;
procedure ReadPipe;
procedure ClosePipe;
procedure ReadMessage;
procedure HandleMessage;
property Client: TX2LogNamedPipeClient read FClient;
property PipeName: string read FPipeName;
property ReadEvent: TEvent read FReadEvent;
property TerminateEvent: TEvent read FTerminateEvent;
property PipeHandle: THandle read FPipeHandle;
property MessageData: TMemoryStream read FMessageData;
public
constructor Create(AClient: TX2LogNamedPipeClient; const APipeName: string);
destructor Destroy; override;
end;
TX2LogNamedPipeServerInfoList = class(TInterfacedObject, IEnumerable<TX2LogNamedPipeServerInfo>, IEnumerable)
private
FServers: TList<TX2LogNamedPipeServerInfo>;
protected
{ IEnumerable }
function GetEnumerator: IEnumerator;
function IEnumerable<TX2LogNamedPipeServerInfo>.GetEnumerator = GetGenericEnumerator;
function GetGenericEnumerator: IEnumerator<TX2LogNamedPipeServerInfo>;
procedure EnumerateServers;
property Servers: TList<TX2LogNamedPipeServerInfo> read FServers;
public
constructor Create;
destructor Destroy; override;
end;
TX2LogNamedPipeServerInfoEnumerator = class(TInterfacedObject, IEnumerator<TX2LogNamedPipeServerInfo>, IEnumerator)
private
FList: TList<TX2LogNamedPipeServerInfo>;
FEnumerator: TEnumerator<TX2LogNamedPipeServerInfo>;
protected
{ IEnumerator }
function GetCurrent: TObject;
function MoveNext: Boolean;
procedure Reset;
function IEnumerator<TX2LogNamedPipeServerInfo>.GetCurrent = GetGenericCurrent;
function GetGenericCurrent: TX2LogNamedPipeServerInfo;
property List: TList<TX2LogNamedPipeServerInfo> read FList;
property Enumerator: TEnumerator<TX2LogNamedPipeServerInfo> read FEnumerator;
public
constructor Create(AList: TList<TX2LogNamedPipeServerInfo>);
destructor Destroy; override;
end;
const
PipePrefix = '\\.\pipe\';
PipeNamePrefix = 'X2Log.';
TimeoutBusyPipe = 5000;
TimeoutNoPipe = 1000;
ClearBufferTreshold = 4096;
{ TX2LogNamedPipeClient }
constructor TX2LogNamedPipeClient.Create(const APipeName: string);
begin
inherited Create;
FWorkerThread := TX2LogNamedPipeClientWorkerThread.Create(Self, APipeName);
end;
destructor TX2LogNamedPipeClient.Destroy;
begin
FreeAndNil(FWorkerThread);
inherited Destroy;
end;
class function TX2LogNamedPipeClient.ActiveServers: IEnumerable<TX2LogNamedPipeServerInfo>;
begin
Result := TX2LogNamedPipeServerInfoList.Create;
end;
{ TX2LogNamedPipeClientWorkerThread }
constructor TX2LogNamedPipeClientWorkerThread.Create(AClient: TX2LogNamedPipeClient; const APipeName: string);
begin
FTerminateEvent := TEvent.Create(nil, True, False, '');
FReadEvent := TEvent.Create(nil, True, False, '');
FMessageData := TMemoryStream.Create;
FClient := AClient;
FPipeName := APipeName;
inherited Create(False);
end;
destructor TX2LogNamedPipeClientWorkerThread.Destroy;
begin
inherited Destroy;
FreeAndNil(FMessageData);
FreeAndNil(FReadEvent);
FreeAndNil(FTerminateEvent);
end;
procedure TX2LogNamedPipeClientWorkerThread.Execute;
begin
FPipeHandle := INVALID_HANDLE_VALUE;
try
while not Terminated do
begin
ConnectPipe;
if not Terminated then
ReadPipe;
end;
finally
ClosePipe;
end;
end;
procedure TX2LogNamedPipeClientWorkerThread.TerminatedSet;
begin
inherited TerminatedSet;
TerminateEvent.SetEvent;
end;
procedure TX2LogNamedPipeClientWorkerThread.ConnectPipe;
var
lastError: Cardinal;
mode: Cardinal;
begin
while not Terminated do
begin
FPipeHandle := CreateFile(PChar(PipePrefix + PipeNamePrefix + PipeName), GENERIC_READ or FILE_WRITE_ATTRIBUTES,
0, nil, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, 0);
if PipeHandle = INVALID_HANDLE_VALUE then
begin
lastError := GetLastError;
case lastError of
ERROR_PIPE_BUSY:
{ Pipe exists but is connecting to another client, wait for a new slot }
WaitNamedPipe(PChar(PipePrefix + PipeNamePrefix + PipeName), TimeoutBusyPipe);
ERROR_FILE_NOT_FOUND:
{ Pipe does not exist, try again later }
WaitForSingleObject(TerminateEvent.Handle, TimeoutNoPipe);
else
RaiseLastOSError;
end;
end else
begin
{ Change to message mode }
mode := PIPE_READMODE_MESSAGE;
if not SetNamedPipeHandleState(PipeHandle, mode, nil, nil) then
RaiseLastOSError;
break;
end;
end;
end;
procedure TX2LogNamedPipeClientWorkerThread.ReadPipe;
var
events: array[0..1] of THandle;
waitResult: Cardinal;
bytesTransferred: Cardinal;
begin
events[0] := TerminateEvent.Handle;
events[1] := ReadEvent.Handle;
FOverlappedRead.hEvent := readEvent.Handle;
ReadMessage;
while (not Terminated) and (PipeHandle <> INVALID_HANDLE_VALUE) do
begin
waitResult := WaitForMultipleObjects(Length(events), @events, False, INFINITE);
case waitResult of
WAIT_OBJECT_0:
{ Terminated }
break;
WAIT_OBJECT_0 + 1:
{ Read event completed }
if GetOverlappedResult(PipeHandle, FOverlappedRead, bytesTransferred, False) then
begin
MessageData.WriteBuffer(FReadBuffer[0], bytesTransferred);
HandleMessage;
ReadMessage;
end else
begin
if GetLastError = ERROR_MORE_DATA then
begin
MessageData.WriteBuffer(FReadBuffer[0], bytesTransferred);
ReadMessage;
end else
begin
ClosePipe;
break;
end;
end;
end;
end;
end;
procedure TX2LogNamedPipeClientWorkerThread.ClosePipe;
begin
if PipeHandle <> INVALID_HANDLE_VALUE then
begin
CloseHandle(PipeHandle);
FPipeHandle := INVALID_HANDLE_VALUE;
end;
end;
procedure TX2LogNamedPipeClientWorkerThread.ReadMessage;
var
bytesRead: Cardinal;
lastError: Cardinal;
begin
while PipeHandle <> INVALID_HANDLE_VALUE do
begin
if ReadFile(PipeHandle, FReadBuffer, SizeOf(FReadBuffer), bytesRead, @FOverlappedRead) then
begin
{ Immediate result }
MessageData.WriteBuffer(FReadBuffer[0], bytesRead);
HandleMessage;
end else
begin
{ More data, pending I/O or an actual error }
lastError := GetLastError;
if lastError = ERROR_IO_PENDING then
break
else if lastError = ERROR_MORE_DATA then
MessageData.WriteBuffer(FReadBuffer[0], SizeOf(FReadBuffer))
else
begin
ClosePipe;
break;
end;
end;
end;
end;
procedure TX2LogNamedPipeClientWorkerThread.HandleMessage;
var
header: TX2LogMessageHeaderV1;
headerDiff: Integer;
msg: string;
details: IX2LogDetails;
serializerIID: TGUID;
detailsSize: Cardinal;
detailsStream: TMemoryStream;
serializer: IX2LogDetailsSerializer;
category: string;
begin
if MessageData.Size > 0 then
begin
try
{ Header }
MessageData.Position := 0;
MessageData.ReadBuffer(header, SizeOf(header));
if header.ID <> X2LogMessageHeader then
raise EReadError.Create('Invalid header ID');
headerDiff := SizeOf(header) - header.Size;
if headerDiff > 0 then
begin
{ A larger, most likely newer version, header }
MessageData.Seek(headerDiff, soFromCurrent)
end else if headerDiff < 0 then
raise EReadError.Create('Header too small');
{ Category }
category := TStreamUtil.ReadString(MessageData);
{ Message }
msg := TStreamUtil.ReadString(MessageData);
{ Details }
details := nil;
MessageData.ReadBuffer(serializerIID, SizeOf(TGUID));
if serializerIID <> GUID_NULL then
begin
detailsSize := TStreamUtil.ReadCardinal(MessageData);
if detailsSize > 0 then
begin
if TX2LogDetailsRegistry.GetSerializer(serializerIID, serializer) then
begin
detailsStream := TMemoryStream.Create;
try
detailsStream.CopyFrom(MessageData, detailsSize);
detailsStream.Position := 0;
details := serializer.Deserialize(detailsStream);
finally
FreeAndNil(detailsStream);
end;
end else
MessageData.Position := MessageData.Position + detailsSize;
end;
end;
Client.Log(header.Level, header.DateTime, msg, category, details);
except
on E:EReadError do
ClosePipe;
on E:Exception do
RaiseLastOSError;
end;
if MessageData.Size > ClearBufferTreshold then
MessageData.Clear
else
MessageData.Position := 0;
end;
end;
{ TX2LogNamedPipeServerInfoList }
constructor TX2LogNamedPipeServerInfoList.Create;
begin
inherited Create;
FServers := TObjectList<TX2LogNamedPipeServerInfo>.Create(True);
EnumerateServers;
end;
destructor TX2LogNamedPipeServerInfoList.Destroy;
begin
FreeAndNil(FServers);
inherited Destroy;
end;
procedure TX2LogNamedPipeServerInfoList.EnumerateServers;
var
searchRec: TSearchRec;
begin
if System.SysUtils.FindFirst(PipePrefix + '*', faAnyFile, searchRec) = 0 then
try
repeat
if StartsText(PipeNamePrefix, searchRec.Name) then
Servers.Add(TX2LogNamedPipeServerInfo.Create(searchRec.Name));
until System.SysUtils.FindNext(searchRec) <> 0;
finally
System.SysUtils.FindClose(searchRec);
end;
end;
function TX2LogNamedPipeServerInfoList.GetEnumerator: IEnumerator;
begin
Result := GetGenericEnumerator;
end;
function TX2LogNamedPipeServerInfoList.GetGenericEnumerator: IEnumerator<TX2LogNamedPipeServerInfo>;
begin
Result := TX2LogNamedPipeServerInfoEnumerator.Create(Servers);
end;
{ TX2LogNamedPipeServerInfoEnumerator }
constructor TX2LogNamedPipeServerInfoEnumerator.Create(AList: TList<TX2LogNamedPipeServerInfo>);
begin
inherited Create;
FList := AList;
Reset;
end;
destructor TX2LogNamedPipeServerInfoEnumerator.Destroy;
begin
FreeAndNil(FEnumerator);
inherited Destroy;
end;
function TX2LogNamedPipeServerInfoEnumerator.GetCurrent: TObject;
begin
Result := GetGenericCurrent;
end;
function TX2LogNamedPipeServerInfoEnumerator.MoveNext: Boolean;
begin
Result := Enumerator.MoveNext;
end;
procedure TX2LogNamedPipeServerInfoEnumerator.Reset;
begin
FreeAndNil(FEnumerator);
FEnumerator := List.GetEnumerator;
end;
function TX2LogNamedPipeServerInfoEnumerator.GetGenericCurrent: TX2LogNamedPipeServerInfo;
begin
Result := Enumerator.Current;
end;
{ TX2LogNamedPipeServerInfo }
constructor TX2LogNamedPipeServerInfo.Create(const APipeName: string);
begin
inherited Create;
FPipeName := APipeName;
if StartsText(PipeNamePrefix, APipeName) then
FDisplayName := Copy(APipeName, Succ(Length(PipeNamePrefix)), MaxInt)
else
FDisplayName := APipeName;
end;
end.