1
0
mirror of synced 2024-09-16 17:06:08 +00:00

Fixed: FS#4 - Binary details are lost with the Named Pipe observer

Fixed: FS#5 - Reconnecting can cause pipe to be busy
Fixed: FS#6 - Process hangs when closing after named pipe client has disconnected
Fixed: themed drawing of toolbar in MonitorForm
Fixed: various memory leaks
Changed: NamedPipeClient test application uses MonitorForm observer to display log
This commit is contained in:
Mark van Renswoude 2014-05-31 20:10:10 +00:00
parent a3fd535be7
commit a9cf1b75f6
17 changed files with 418 additions and 159 deletions

View File

@ -1,17 +1,30 @@
program X2LogNamedPipeClient; program X2LogNamedPipeClient;
uses uses
// FastMM4,
Vcl.Forms, Vcl.Forms,
MainFrm in 'source\MainFrm.pas' {MainForm}; X2Log.Intf,
X2Log.Client.NamedPipe,
X2Log.Observer.MonitorForm;
{$R *.res} {$R *.res}
var var
MainForm: TMainForm; client: IX2LogObservable;
observerForm: TX2LogObserverMonitorForm;
begin begin
ReportMemoryLeaksOnShutdown := True;
Application.Initialize; Application.Initialize;
Application.MainFormOnTaskbar := True; Application.MainFormOnTaskbar := True;
Application.CreateForm(TMainForm, MainForm); Application.Title := 'X²Log Named Pipe Client';
Application.Run;
client := TX2LogNamedPipeClient.Create('X2LogTest');
try
observerForm := TX2LogObserverMonitorForm.Instance(client);
observerForm.ShowModal;
finally
client := nil;
end;
end. end.

View File

@ -84,10 +84,6 @@
<DelphiCompile Include="$(MainSource)"> <DelphiCompile Include="$(MainSource)">
<MainSource>MainSource</MainSource> <MainSource>MainSource</MainSource>
</DelphiCompile> </DelphiCompile>
<DCCReference Include="source\MainFrm.pas">
<Form>MainForm</Form>
<FormType>dfm</FormType>
</DCCReference>
<BuildConfiguration Include="Release"> <BuildConfiguration Include="Release">
<Key>Cfg_2</Key> <Key>Cfg_2</Key>
<CfgParent>Base</CfgParent> <CfgParent>Base</CfgParent>

View File

@ -1,27 +0,0 @@
object MainForm: TMainForm
Left = 0
Top = 0
Caption = 'X'#178'Log Named Pipe Client'
ClientHeight = 443
ClientWidth = 552
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
OnCreate = FormCreate
PixelsPerInch = 96
TextHeight = 13
object mmoLog: TMemo
Left = 0
Top = 0
Width = 552
Height = 443
Align = alClient
ReadOnly = True
ScrollBars = ssVertical
TabOrder = 0
end
end

View File

@ -1,52 +0,0 @@
unit MainFrm;
interface
uses
System.Classes,
Vcl.Controls,
Vcl.Forms,
Vcl.StdCtrls,
X2Log.Intf;
type
TMainForm = class(TForm)
mmoLog: TMemo;
procedure FormCreate(Sender: TObject);
private
FClient: IX2LogObservable;
FObserver: IX2LogObserver;
protected
procedure DoLog(Sender: TObject; Level: TX2LogLevel; const Msg, Details: string);
end;
implementation
uses
System.SysUtils,
X2Log.Constants,
X2Log.Client.NamedPipe,
X2Log.Observer.Event;
{$R *.dfm}
{ TMainForm }
procedure TMainForm.FormCreate(Sender: TObject);
begin
FClient := TX2LogNamedPipeClient.Create('X2LogTest');
FObserver := TX2LogEventObserver.Create(DoLog);
FClient.Attach(FObserver);
end;
procedure TMainForm.DoLog(Sender: TObject; Level: TX2LogLevel; const Msg, Details: string);
begin
mmoLog.Lines.Add(DateTimeToStr(Now) + ' ' + GetLogLevelText(Level) + ': ' + Msg + ' (' + Details + ')');
end;
end.

View File

@ -1,6 +1,7 @@
program X2LogTest; program X2LogTest;
uses uses
// FastMM4,
Forms, Forms,
MainFrm in 'source\MainFrm.pas' {MainForm}, MainFrm in 'source\MainFrm.pas' {MainForm},
X2Log.Intf in '..\X2Log.Intf.pas', X2Log.Intf in '..\X2Log.Intf.pas',
@ -17,7 +18,8 @@ uses
X2Log.Global in '..\X2Log.Global.pas', X2Log.Global in '..\X2Log.Global.pas',
X2Log.Client.NamedPipe in '..\X2Log.Client.NamedPipe.pas', X2Log.Client.NamedPipe in '..\X2Log.Client.NamedPipe.pas',
X2Log.Client.Base in '..\X2Log.Client.Base.pas', X2Log.Client.Base in '..\X2Log.Client.Base.pas',
X2Log.Details.Default in '..\X2Log.Details.Default.pas'; X2Log.Details.Default in '..\X2Log.Details.Default.pas',
X2Log.Details.Registry in '..\X2Log.Details.Registry.pas';
{$R *.res} {$R *.res}

View File

@ -186,12 +186,12 @@
<DCCReference Include="..\X2Log.Observer.CustomThreaded.pas"/> <DCCReference Include="..\X2Log.Observer.CustomThreaded.pas"/>
<DCCReference Include="..\X2Log.Observer.MonitorForm.pas"> <DCCReference Include="..\X2Log.Observer.MonitorForm.pas">
<Form>X2LogObserverMonitorForm</Form> <Form>X2LogObserverMonitorForm</Form>
<FormType>dfm</FormType>
</DCCReference> </DCCReference>
<DCCReference Include="..\X2Log.Global.pas"/> <DCCReference Include="..\X2Log.Global.pas"/>
<DCCReference Include="..\X2Log.Client.NamedPipe.pas"/> <DCCReference Include="..\X2Log.Client.NamedPipe.pas"/>
<DCCReference Include="..\X2Log.Client.Base.pas"/> <DCCReference Include="..\X2Log.Client.Base.pas"/>
<DCCReference Include="..\X2Log.Details.Default.pas"/> <DCCReference Include="..\X2Log.Details.Default.pas"/>
<DCCReference Include="..\X2Log.Details.Registry.pas"/>
<BuildConfiguration Include="Debug"> <BuildConfiguration Include="Debug">
<Key>Cfg_2</Key> <Key>Cfg_2</Key>
<CfgParent>Base</CfgParent> <CfgParent>Base</CfgParent>

View File

@ -349,7 +349,7 @@ object MainForm: TMainForm
Shape = bsTopLine Shape = bsTopLine
end end
end end
object Panel1: TPanel object pnlObservers: TPanel
AlignWithMargins = True AlignWithMargins = True
Left = 8 Left = 8
Top = 144 Top = 144
@ -370,7 +370,7 @@ object MainForm: TMainForm
Font.Style = [fsBold] Font.Style = [fsBold]
ParentFont = False ParentFont = False
TabOrder = 4 TabOrder = 4
object Bevel1: TBevel object bvlObservers: TBevel
Left = 80 Left = 80
Top = 12 Top = 12
Width = 513 Width = 513
@ -384,7 +384,7 @@ object MainForm: TMainForm
Left = 552 Left = 552
Top = 176 Top = 176
Bitmap = { Bitmap = {
494C01010200140034000C000C00FFFFFFFFFF10FFFFFFFFFFFFFFFF424D3600 494C01010200140038000C000C00FFFFFFFFFF10FFFFFFFFFFFFFFFF424D3600
0000000000003600000028000000300000000C00000001002000000000000009 0000000000003600000028000000300000000C00000001002000000000000009
0000000000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000000

View File

@ -52,8 +52,8 @@ type
tsBinary: TTabSheet; tsBinary: TTabSheet;
pnlDispatch: TPanel; pnlDispatch: TPanel;
bvlDispatch: TBevel; bvlDispatch: TBevel;
Panel1: TPanel; pnlObservers: TPanel;
Bevel1: TBevel; bvlObservers: TBevel;
procedure FormCreate(Sender: TObject); procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject); procedure FormDestroy(Sender: TObject);

View File

@ -17,6 +17,7 @@ type
property WorkerThread: TThread read FWorkerThread; property WorkerThread: TThread read FWorkerThread;
public public
constructor Create(const APipeName: string); constructor Create(const APipeName: string);
destructor Destroy; override;
end; end;
@ -24,18 +25,21 @@ implementation
uses uses
System.SyncObjs, System.SyncObjs,
System.SysUtils, System.SysUtils,
System.Types,
Winapi.Windows, Winapi.Windows,
X2Log.Details.Default; X2Log.Details.Default,
X2Log.Details.Registry;
type type
TX2LogNamedPipeClientWorkerThread = class(TThread) TX2LogNamedPipeClientWorkerThread = class(TThread)
private private
FLog: IX2LogBase; FClient: TX2LogNamedPipeClient;
FPipeName: string; FPipeName: string;
FTerminateEvent: TEvent; FTerminateEvent: TEvent;
FReadEvent: TEvent;
FPipeHandle: THandle; FPipeHandle: THandle;
FOverlappedRead: TOverlapped; FOverlappedRead: TOverlapped;
FReadBuffer: array[0..4095] of Byte; FReadBuffer: array[0..4095] of Byte;
@ -51,14 +55,16 @@ type
procedure ReadMessage; procedure ReadMessage;
procedure HandleMessage; procedure HandleMessage;
property Log: IX2LogBase read FLog; property Client: TX2LogNamedPipeClient read FClient;
property PipeName: string read FPipeName; property PipeName: string read FPipeName;
property ReadEvent: TEvent read FReadEvent;
property TerminateEvent: TEvent read FTerminateEvent; property TerminateEvent: TEvent read FTerminateEvent;
property PipeHandle: THandle read FPipeHandle; property PipeHandle: THandle read FPipeHandle;
property MessageData: TMemoryStream read FMessageData; property MessageData: TMemoryStream read FMessageData;
public public
constructor Create(ALog: IX2LogBase; const APipeName: string); constructor Create(AClient: TX2LogNamedPipeClient; const APipeName: string);
destructor Destroy; override;
end; end;
@ -80,19 +86,38 @@ begin
end; end;
destructor TX2LogNamedPipeClient.Destroy;
begin
FreeAndNil(FWorkerThread);
inherited Destroy;
end;
{ TX2LogNamedPipeClientWorkerThread } { TX2LogNamedPipeClientWorkerThread }
constructor TX2LogNamedPipeClientWorkerThread.Create(ALog: IX2LogBase; const APipeName: string); constructor TX2LogNamedPipeClientWorkerThread.Create(AClient: TX2LogNamedPipeClient; const APipeName: string);
begin begin
FTerminateEvent := TEvent.Create(nil, True, False, ''); FTerminateEvent := TEvent.Create(nil, True, False, '');
FReadEvent := TEvent.Create(nil, True, False, '');
FMessageData := TMemoryStream.Create; FMessageData := TMemoryStream.Create;
FLog := ALog; FClient := AClient;
FPipeName := APipeName; FPipeName := APipeName;
inherited Create(False); inherited Create(False);
end; end;
destructor TX2LogNamedPipeClientWorkerThread.Destroy;
begin
inherited Destroy;
FreeAndNil(FMessageData);
FreeAndNil(FReadEvent);
FreeAndNil(FTerminateEvent);
end;
procedure TX2LogNamedPipeClientWorkerThread.Execute; procedure TX2LogNamedPipeClientWorkerThread.Execute;
begin begin
FPipeHandle := INVALID_HANDLE_VALUE; FPipeHandle := INVALID_HANDLE_VALUE;
@ -159,15 +184,13 @@ end;
procedure TX2LogNamedPipeClientWorkerThread.ReadPipe; procedure TX2LogNamedPipeClientWorkerThread.ReadPipe;
var var
readEvent: TEvent;
events: array[0..1] of THandle; events: array[0..1] of THandle;
waitResult: Cardinal; waitResult: Cardinal;
bytesTransferred: Cardinal; bytesTransferred: Cardinal;
begin begin
readEvent := TEvent.Create(nil, False, False, '');
events[0] := TerminateEvent.Handle; events[0] := TerminateEvent.Handle;
events[1] := readEvent.Handle; events[1] := ReadEvent.Handle;
FOverlappedRead.hEvent := readEvent.Handle; FOverlappedRead.hEvent := readEvent.Handle;
ReadMessage; ReadMessage;
@ -268,12 +291,17 @@ var
header: TX2LogMessageHeaderV1; header: TX2LogMessageHeaderV1;
headerDiff: Integer; headerDiff: Integer;
msg: string; msg: string;
details: string; details: IX2LogDetails;
serializerIID: TGUID;
detailsSize: Cardinal;
detailsStream: TMemoryStream;
serializer: IX2LogDetailsSerializer;
begin begin
if MessageData.Size > 0 then if MessageData.Size > 0 then
begin begin
try try
{ Header }
MessageData.Position := 0; MessageData.Position := 0;
MessageData.ReadBuffer(header, SizeOf(header)); MessageData.ReadBuffer(header, SizeOf(header));
@ -288,11 +316,35 @@ begin
end else if headerDiff < 0 then end else if headerDiff < 0 then
raise EReadError.Create('Header too small'); raise EReadError.Create('Header too small');
{ Message }
msg := ReadString; msg := ReadString;
details := ReadString;
// #ToDo1 named pipe support for non-string details { Details }
Log.Log(header.Level, msg, TX2LogStringDetails.CreateIfNotEmpty(details)); details := nil;
MessageData.ReadBuffer(serializerIID, SizeOf(TGUID));
if serializerIID <> GUID_NULL then
begin
MessageData.ReadBuffer(detailsSize, SizeOf(Cardinal));
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.Seek(detailsSize, soFromCurrent);
end;
end;
Client.Log(header.Level, msg, details);
except except
on E:EReadError do on E:EReadError do
ClosePipe; ClosePipe;

View File

@ -46,10 +46,10 @@ resourcestring
{ Status messages } { Status messages }
LogMonitorFormStatusPaused = 'Paused: %d log message(s) skipped'; LogMonitorFormStatusPaused = 'Paused: %d log message(s) skipped';
{ Filter for Save details buttons }
LogMonitorFormSaveDetailsFilter = 'All files (*.*)|*.*'; LogMonitorFormSaveDetailsFilter = 'All files (*.*)|*.*';
function GetLogLevelText(ALogLevel: TX2LogLevel): string; function GetLogLevelText(ALogLevel: TX2LogLevel): string;
function GetLogResourceString(AResourceString: Pointer): string; function GetLogResourceString(AResourceString: Pointer): string;
@ -62,8 +62,11 @@ uses
System.SysUtils; System.SysUtils;
type
TResourceStringDictionary = TDictionary<Pointer,string>;
var var
LogResourceStringMap: TDictionary<Pointer,string>; LogResourceStringMap: TResourceStringDictionary;
@ -104,7 +107,7 @@ end;
initialization initialization
LogResourceStringMap := TDictionary<Pointer,string>.Create; LogResourceStringMap := TResourceStringDictionary.Create;
finalization finalization
FreeAndNil(LogResourceStringMap); FreeAndNil(LogResourceStringMap);

View File

@ -17,6 +17,9 @@ type
constructor Create(const AText: string); constructor Create(const AText: string);
{ IX2LogDetails }
function GetSerializerIID: TGUID;
{ IX2LogDetailsText } { IX2LogDetailsText }
function GetAsString: string; function GetAsString: string;
@ -36,10 +39,14 @@ type
protected protected
property Data: TStream read FData; property Data: TStream read FData;
public public
constructor Create(ACopyFrom: TStream); overload; constructor Create; overload;
constructor Create(ACopyFrom: TStream; ACount: Integer = 0); overload;
constructor Create(AData: RawByteString); overload; constructor Create(AData: RawByteString); overload;
destructor Destroy; override; destructor Destroy; override;
{ IX2LogDetails }
function GetSerializerIID: TGUID;
{ IX2LogDetailsBinary } { IX2LogDetailsBinary }
function GetAsStream: TStream; function GetAsStream: TStream;
@ -51,7 +58,33 @@ type
implementation implementation
uses uses
System.SysUtils, System.SysUtils,
Vcl.ClipBrd; Vcl.ClipBrd,
X2Log.Constants,
X2Log.Details.Registry;
const
StringDetailsSerializerIID: TGUID = '{4223C30E-6E80-4D66-9EDC-F8688A7413D2}';
BinaryDetailsSerializerIID: TGUID = '{05F6E8BD-118E-41B3-B626-1F190CC2A7D3}';
type
TX2LogStringDetailsSerializer = class(TInterfacedObject, IX2LogDetailsSerializer)
public
{ IX2LogDetailsSerializer }
procedure Serialize(ADetails: IX2LogDetails; AStream: TStream);
function Deserialize(AStream: TStream): IX2LogDetails;
end;
TX2LogBinaryDetailsSerializer = class(TInterfacedObject, IX2LogDetailsSerializer)
public
{ IX2LogDetailsSerializer }
procedure Serialize(ADetails: IX2LogDetails; AStream: TStream);
function Deserialize(AStream: TStream): IX2LogDetails;
end;
{ TX2LogStringDetails } { TX2LogStringDetails }
@ -72,6 +105,12 @@ begin
end; end;
function TX2LogStringDetails.GetSerializerIID: TGUID;
begin
Result := StringDetailsSerializerIID;
end;
function TX2LogStringDetails.GetAsString: string; function TX2LogStringDetails.GetAsString: string;
begin begin
Result := FText; Result := FText;
@ -86,25 +125,33 @@ end;
procedure TX2LogStringDetails.SaveToStream(AStream: TStream); procedure TX2LogStringDetails.SaveToStream(AStream: TStream);
var var
textStream: TStringStream; writer: TStreamWriter;
begin begin
textStream := TStringStream.Create(GetAsString, TEncoding.ANSI, False); writer := TStreamWriter.Create(AStream, TEncoding.UTF8);
try try
AStream.CopyFrom(textStream, 0); writer.Write(GetAsString);
finally finally
FreeAndNil(textStream); FreeAndNil(writer);
end; end;
end; end;
{ TX2LogBinaryDetails } { TX2LogBinaryDetails }
constructor TX2LogBinaryDetails.Create(ACopyFrom: TStream); constructor TX2LogBinaryDetails.Create;
begin begin
inherited Create; inherited Create;
FData := TMemoryStream.Create; FData := TMemoryStream.Create;
FData.CopyFrom(ACopyFrom, ACopyFrom.Size - ACopyFrom.Position); end;
constructor TX2LogBinaryDetails.Create(ACopyFrom: TStream; ACount: Integer);
begin
inherited Create;
FData := TMemoryStream.Create;
FData.CopyFrom(ACopyFrom, ACount);
end; end;
@ -124,6 +171,12 @@ begin
end; end;
function TX2LogBinaryDetails.GetSerializerIID: TGUID;
begin
Result := BinaryDetailsSerializerIID;
end;
function TX2LogBinaryDetails.GetAsStream: TStream; function TX2LogBinaryDetails.GetAsStream: TStream;
begin begin
Data.Position := 0; Data.Position := 0;
@ -136,4 +189,77 @@ begin
AStream.CopyFrom(Data, 0); AStream.CopyFrom(Data, 0);
end; end;
{ TX2LogStringDetailsSerializer }
procedure TX2LogStringDetailsSerializer.Serialize(ADetails: IX2LogDetails; AStream: TStream);
var
bytes: TBytes;
bytesLength: Cardinal;
begin
bytes := TEncoding.UTF8.GetBytes((ADetails as IX2LogDetailsText).AsString);
bytesLength := Length(bytes);
AStream.WriteBuffer(bytesLength, SizeOf(Cardinal));
if bytesLength > 0 then
AStream.WriteBuffer(bytes[0], bytesLength);
end;
function TX2LogStringDetailsSerializer.Deserialize(AStream: TStream): IX2LogDetails;
var
bytes: TBytes;
bytesLength: Cardinal;
begin
AStream.ReadBuffer(bytesLength, SizeOf(Cardinal));
if bytesLength > 0 then
begin
SetLength(bytes, bytesLength);
AStream.ReadBuffer(bytes[0], bytesLength);
Result := TX2LogStringDetails.Create(TEncoding.UTF8.GetString(bytes));
end else
{ Do not return nil; the fact that Deserialize is called means an
empty Details was serialized. }
Result := TX2LogStringDetails.Create('');
end;
{ TX2LogBinaryDetailsSerializer }
procedure TX2LogBinaryDetailsSerializer.Serialize(ADetails: IX2LogDetails; AStream: TStream);
var
stream: TStream;
streamSize: Cardinal;
begin
stream := (ADetails as IX2LogDetailsBinary).AsStream;
streamSize := stream.Size;
AStream.WriteBuffer(streamSize, SizeOf(Cardinal));
if streamSize > 0 then
AStream.CopyFrom(stream, streamSize);
end;
function TX2LogBinaryDetailsSerializer.Deserialize(AStream: TStream): IX2LogDetails;
var
streamSize: Cardinal;
begin
AStream.ReadBuffer(streamSize, SizeOf(Cardinal));
if streamSize > 0 then
Result := TX2LogBinaryDetails.Create(AStream, streamSize)
else
{ Do not return nil; the fact that Deserialize is called means an
empty Details was serialized. }
Result := TX2LogBinaryDetails.Create;
end;
initialization
TX2LogDetailsRegistry.Register(StringDetailsSerializerIID, TX2LogStringDetailsSerializer.Create);
TX2LogDetailsRegistry.Register(BinaryDetailsSerializerIID, TX2LogBinaryDetailsSerializer.Create);
end. end.

View File

@ -0,0 +1,81 @@
unit X2Log.Details.Registry;
interface
uses
System.Generics.Collections,
X2Log.Intf;
type
TSerializerDictionary = TDictionary<TGUID, IX2LogDetailsSerializer>;
TX2LogDetailsRegistry = class(TObject)
private
class var FSerializers: TSerializerDictionary;
protected
class function Serializers: TSerializerDictionary;
class procedure CleanupSerializers;
public
class procedure Register(AIID: TGUID; ASerializer: IX2LogDetailsSerializer);
class procedure Unregister(AIID: TGUID);
class function GetSerializer(ASerializerIID: TGUID; out ASerializer: IX2LogDetailsSerializer): Boolean; overload;
class function GetSerializer(ADetails: IX2LogDetails; out ASerializer: IX2LogDetailsSerializer): Boolean; overload;
end;
implementation
uses
System.SysUtils,
System.Types;
{ TX2LogDetailsRegistry }
class procedure TX2LogDetailsRegistry.Register(AIID: TGUID; ASerializer: IX2LogDetailsSerializer);
begin
Serializers.Add(AIID, ASerializer);
end;
class function TX2LogDetailsRegistry.GetSerializer(ADetails: IX2LogDetails; out ASerializer: IX2LogDetailsSerializer): Boolean;
begin
Result := Assigned(ADetails) and
(ADetails.SerializerIID <> GUID_NULL) and
GetSerializer(ADetails.SerializerIID, ASerializer);
end;
class procedure TX2LogDetailsRegistry.Unregister(AIID: TGUID);
begin
Serializers.Remove(AIID);
end;
class function TX2LogDetailsRegistry.GetSerializer(ASerializerIID: TGUID; out ASerializer: IX2LogDetailsSerializer): Boolean;
begin
Result := Serializers.TryGetValue(ASerializerIID, ASerializer);
end;
class function TX2LogDetailsRegistry.Serializers: TSerializerDictionary;
begin
if not Assigned(FSerializers) then
FSerializers := TSerializerDictionary.Create;
Result := FSerializers;
end;
class procedure TX2LogDetailsRegistry.CleanupSerializers;
begin
FreeAndNil(FSerializers);
end;
initialization
finalization
TX2LogDetailsRegistry.CleanupSerializers;
end.

View File

@ -10,8 +10,8 @@ uses
type type
TX2GlobalLog = class(TObject) TX2GlobalLog = class(TObject)
private class var private
FInstance: IX2Log; class var FInstance: IX2Log;
protected protected
class procedure CleanupInstance; class procedure CleanupInstance;
public public

View File

@ -19,6 +19,16 @@ type
{ Details } { Details }
IX2LogDetails = interface IX2LogDetails = interface
['{86F24F52-CE1F-4A79-936F-A5805D84E18A}'] ['{86F24F52-CE1F-4A79-936F-A5805D84E18A}']
function GetSerializerIID: TGUID;
property SerializerIID: TGUID read GetSerializerIID;
end;
IX2LogDetailsSerializer = interface
['{E75F6F4E-A671-4622-878C-F59C64FB1320}']
procedure Serialize(ADetails: IX2LogDetails; AStream: TStream);
function Deserialize(AStream: TStream): IX2LogDetails;
end; end;

View File

@ -46,6 +46,7 @@ object X2LogObserverMonitorForm: TX2LogObserverMonitorForm
List = True List = True
ShowCaptions = True ShowCaptions = True
TabOrder = 0 TabOrder = 0
OnCustomDraw = ToolbarCustomDraw
object tbCopyDetails: TToolButton object tbCopyDetails: TToolButton
Left = 0 Left = 0
Top = 0 Top = 0
@ -163,6 +164,7 @@ object X2LogObserverMonitorForm: TX2LogObserverMonitorForm
List = True List = True
ShowCaptions = True ShowCaptions = True
TabOrder = 1 TabOrder = 1
OnCustomDraw = ToolbarCustomDraw
object tbClear: TToolButton object tbClear: TToolButton
Left = 0 Left = 0
Top = 0 Top = 0
@ -194,7 +196,7 @@ object X2LogObserverMonitorForm: TX2LogObserverMonitorForm
Left = 448 Left = 448
Top = 48 Top = 48
Bitmap = { Bitmap = {
494C010109004000940010001000FFFFFFFFFF10FFFFFFFFFFFFFFFF424D3600 494C0101090040009C0010001000FFFFFFFFFF10FFFFFFFFFFFFFFFF424D3600
0000000000003600000028000000400000003000000001002000000000000030 0000000000003600000028000000400000003000000001002000000000000030
0000000000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000000

View File

@ -4,6 +4,7 @@ interface
uses uses
System.Classes, System.Classes,
System.Generics.Collections, System.Generics.Collections,
System.Types,
Vcl.ActnList, Vcl.ActnList,
Vcl.ComCtrls, Vcl.ComCtrls,
Vcl.Controls, Vcl.Controls,
@ -24,6 +25,10 @@ const
type type
TX2LogObserverMonitorForm = class;
TMonitorFormDictionary = TObjectDictionary<IX2LogObservable,TX2LogObserverMonitorForm>;
TX2LogObserverMonitorForm = class(TForm, IX2LogObserver) TX2LogObserverMonitorForm = class(TForm, IX2LogObserver)
vstLog: TVirtualStringTree; vstLog: TVirtualStringTree;
ilsLog: TImageList; ilsLog: TImageList;
@ -59,18 +64,19 @@ type
procedure actCopyDetailsExecute(Sender: TObject); procedure actCopyDetailsExecute(Sender: TObject);
procedure actSaveDetailsExecute(Sender: TObject); procedure actSaveDetailsExecute(Sender: TObject);
procedure actPauseExecute(Sender: TObject); procedure actPauseExecute(Sender: TObject);
procedure ToolbarCustomDraw(Sender: TToolBar; const ARect: TRect; var DefaultDraw: Boolean);
private class var private class var
FInstances: TObjectDictionary<IX2Log,TX2LogObserverMonitorForm>; FInstances: TMonitorFormDictionary;
private private
FFreeOnClose: Boolean; FFreeOnClose: Boolean;
FLogToAttach: IX2Log; FLogObservable: IX2LogObservable;
FLogAttached: Boolean; FLogAttached: Boolean;
FPausedLogCount: Integer; FPausedLogCount: Integer;
FDetails: IX2LogDetails; FDetails: IX2LogDetails;
function GetPaused: Boolean; function GetPaused: Boolean;
protected protected
class function GetInstance(ALog: IX2Log; out AForm: TX2LogObserverMonitorForm): Boolean; class function GetInstance(ALog: IX2LogObservable; out AForm: TX2LogObserverMonitorForm): Boolean;
class procedure RemoveInstance(AForm: TX2LogObserverMonitorForm); class procedure RemoveInstance(AForm: TX2LogObserverMonitorForm);
class procedure CleanupInstances; class procedure CleanupInstances;
@ -86,17 +92,17 @@ type
procedure SetBinaryDetails(ADetails: IX2LogDetailsBinary); procedure SetBinaryDetails(ADetails: IX2LogDetailsBinary);
property Details: IX2LogDetails read FDetails; property Details: IX2LogDetails read FDetails;
property LogToAttach: IX2Log read FLogToAttach; property LogObservable: IX2LogObservable read FLogObservable;
property LogAttached: Boolean read FLogAttached; property LogAttached: Boolean read FLogAttached;
property Paused: Boolean read GetPaused; property Paused: Boolean read GetPaused;
property PausedLogCount: Integer read FPausedLogCount write FPausedLogCount; property PausedLogCount: Integer read FPausedLogCount write FPausedLogCount;
public public
class function Instance(ALog: IX2Log): TX2LogObserverMonitorForm; class function Instance(ALog: IX2LogObservable): TX2LogObserverMonitorForm;
class procedure ShowInstance(ALog: IX2Log); class procedure ShowInstance(ALog: IX2LogObservable);
class procedure CloseInstance(ALog: IX2Log); class procedure CloseInstance(ALog: IX2LogObservable);
constructor Create(AOwner: TComponent; ALogToAttach: IX2Log = nil); reintroduce; constructor Create(AOwner: TComponent; ALogObservable: IX2LogObservable = nil); reintroduce;
destructor Destroy; override; destructor Destroy; override;
{ IX2LogObserver } { IX2LogObserver }
@ -112,6 +118,7 @@ uses
System.Math, System.Math,
System.SysUtils, System.SysUtils,
Vcl.Clipbrd, Vcl.Clipbrd,
Vcl.Themes,
Winapi.Windows, Winapi.Windows,
X2Log.Constants; X2Log.Constants;
@ -150,17 +157,17 @@ end;
{ TX2LogObserverMonitorForm } { TX2LogObserverMonitorForm }
class function TX2LogObserverMonitorForm.Instance(ALog: IX2Log): TX2LogObserverMonitorForm; class function TX2LogObserverMonitorForm.Instance(ALog: IX2LogObservable): TX2LogObserverMonitorForm;
var var
log: IX2Log; log: IX2LogObservable;
begin begin
{ Explicit cast ensures we're getting the same pointer every time if, for example, { Explicit cast ensures we're getting the same pointer every time if, for example,
the implementing interface is a descendant of IX2Log } the implementing interface is a descendant of IX2Log }
log := (ALog as IX2Log); log := (ALog as IX2LogObservable);
if not Assigned(FInstances) then if not Assigned(FInstances) then
FInstances := TObjectDictionary<IX2Log,TX2LogObserverMonitorForm>.Create([doOwnsValues]); FInstances := TMonitorFormDictionary.Create([doOwnsValues]);
if not FInstances.TryGetValue(log, Result) then if not FInstances.TryGetValue(log, Result) then
begin begin
@ -172,13 +179,13 @@ begin
end; end;
class procedure TX2LogObserverMonitorForm.ShowInstance(ALog: IX2Log); class procedure TX2LogObserverMonitorForm.ShowInstance(ALog: IX2LogObservable);
begin begin
Instance(ALog).Show; Instance(ALog).Show;
end; end;
class procedure TX2LogObserverMonitorForm.CloseInstance(ALog: IX2Log); class procedure TX2LogObserverMonitorForm.CloseInstance(ALog: IX2LogObservable);
var var
monitorForm: TX2LogObserverMonitorForm; monitorForm: TX2LogObserverMonitorForm;
@ -188,7 +195,7 @@ begin
end; end;
class function TX2LogObserverMonitorForm.GetInstance(ALog: IX2Log; out AForm: TX2LogObserverMonitorForm): Boolean; class function TX2LogObserverMonitorForm.GetInstance(ALog: IX2LogObservable; out AForm: TX2LogObserverMonitorForm): Boolean;
begin begin
Result := False; Result := False;
@ -199,7 +206,7 @@ end;
class procedure TX2LogObserverMonitorForm.RemoveInstance(AForm: TX2LogObserverMonitorForm); class procedure TX2LogObserverMonitorForm.RemoveInstance(AForm: TX2LogObserverMonitorForm);
var var
log: IX2Log; log: IX2LogObservable;
begin begin
if Assigned(FInstances) then if Assigned(FInstances) then
@ -223,14 +230,14 @@ begin
end; end;
constructor TX2LogObserverMonitorForm.Create(AOwner: TComponent; ALogToAttach: IX2Log); constructor TX2LogObserverMonitorForm.Create(AOwner: TComponent; ALogObservable: IX2LogObservable);
var var
captionFormat: string; captionFormat: string;
begin begin
inherited Create(AOwner); inherited Create(AOwner);
FLogToAttach := ALogToAttach; FLogObservable := ALogObservable;
captionFormat := GetLogResourceString(@LogMonitorFormCaption); captionFormat := GetLogResourceString(@LogMonitorFormCaption);
if Pos('%s', captionFormat) > 0 then if Pos('%s', captionFormat) > 0 then
@ -262,8 +269,8 @@ end;
destructor TX2LogObserverMonitorForm.Destroy; destructor TX2LogObserverMonitorForm.Destroy;
begin begin
if Assigned(FLogToAttach) and FLogAttached then if Assigned(FLogObservable) and FLogAttached then
FLogToAttach.Detach(Self); FLogObservable.Detach(Self);
RemoveInstance(Self); RemoveInstance(Self);
@ -273,9 +280,9 @@ end;
procedure TX2LogObserverMonitorForm.FormShow(Sender: TObject); procedure TX2LogObserverMonitorForm.FormShow(Sender: TObject);
begin begin
if Assigned(FLogToAttach) and (not FLogAttached) then if Assigned(FLogObservable) and (not FLogAttached) then
begin begin
FLogToAttach.Attach(Self); FLogObservable.Attach(Self);
FLogAttached := True; FLogAttached := True;
end; end;
end; end;
@ -283,9 +290,9 @@ end;
procedure TX2LogObserverMonitorForm.FormClose(Sender: TObject; var Action: TCloseAction); procedure TX2LogObserverMonitorForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin begin
if Assigned(FLogToAttach) and FLogAttached then if Assigned(FLogObservable) and FLogAttached then
begin begin
FLogToAttach.Detach(Self); FLogObservable.Detach(Self);
FLogAttached := False; FLogAttached := False;
end; end;
@ -628,6 +635,24 @@ begin
end; end;
procedure TX2LogObserverMonitorForm.ToolbarCustomDraw(Sender: TToolBar; const ARect: TRect; var DefaultDraw: Boolean);
var
element: TThemedElementDetails;
rect: TRect;
begin
if StyleServices.Enabled then
begin
rect := Sender.ClientRect;
if Assigned(Self.Menu) then
Dec(rect.Top, GetSystemMetrics(SM_CYMENU));
element := StyleServices.GetElementDetails(trRebarRoot);
StyleServices.DrawElement(Sender.Canvas.Handle, element, rect);
end;
end;
initialization initialization
finalization finalization
TX2LogObserverMonitorForm.CleanupInstances; TX2LogObserverMonitorForm.CleanupInstances;

View File

@ -24,7 +24,10 @@ uses
System.Generics.Collections, System.Generics.Collections,
System.SyncObjs, System.SyncObjs,
System.SysUtils, System.SysUtils,
Winapi.Windows; System.Types,
Winapi.Windows,
X2Log.Details.Registry;
type type
@ -128,11 +131,9 @@ end;
destructor TX2LogNamedPipeClient.Destroy; destructor TX2LogNamedPipeClient.Destroy;
begin begin
Disconnect;
FreeAndNil(FOverlappedEvent); FreeAndNil(FOverlappedEvent);
if PipeHandle <> INVALID_HANDLE_VALUE then
DisconnectNamedPipe(PipeHandle);
ClearWriteBuffer; ClearWriteBuffer;
inherited Destroy; inherited Destroy;
@ -185,6 +186,7 @@ begin
begin begin
CancelIo(PipeHandle); CancelIo(PipeHandle);
DisconnectNamedPipe(PipeHandle); DisconnectNamedPipe(PipeHandle);
CloseHandle(PipeHandle);
FPipeHandle := INVALID_HANDLE_VALUE; FPipeHandle := INVALID_HANDLE_VALUE;
end; end;
@ -208,26 +210,49 @@ var
header: TX2LogMessageHeader; header: TX2LogMessageHeader;
bytesWritten: Cardinal; bytesWritten: Cardinal;
lastError: Cardinal; lastError: Cardinal;
logDetailsText: IX2LogDetailsText; detailsSize: Cardinal;
detailsStream: TMemoryStream;
serializerIID: TGUID;
serializer: IX2LogDetailsSerializer;
begin begin
ClearWriteBuffer; ClearWriteBuffer;
FWriteBuffer := TMemoryStream.Create; FWriteBuffer := TMemoryStream.Create;
{ Header }
header.ID := X2LogMessageHeader; header.ID := X2LogMessageHeader;
header.Version := X2LogMessageVersion; header.Version := X2LogMessageVersion;
header.Size := SizeOf(header); header.Size := SizeOf(header);
header.Level := AEntry.Level; header.Level := AEntry.Level;
WriteBuffer.WriteBuffer(header, SizeOf(header)); WriteBuffer.WriteBuffer(header, SizeOf(header));
{ Message }
WriteString(AEntry.Message); WriteString(AEntry.Message);
// #ToDo1 support for non-string details { Details }
if Supports(AEntry.Details, IX2LogDetailsText, logDetailsText) then if TX2LogDetailsRegistry.GetSerializer(AEntry.Details, serializer) then
WriteString(logDetailsText.AsString) begin
else detailsStream := TMemoryStream.Create;
WriteString(''); try
serializer.Serialize(AEntry.Details, detailsStream);
serializerIID := AEntry.Details.SerializerIID;
WriteBuffer.WriteBuffer(serializerIID, SizeOf(TGUID));
detailsSize := detailsStream.Size;
WriteBuffer.WriteBuffer(detailsSize, SizeOf(Cardinal));
WriteBuffer.CopyFrom(detailsStream, 0);
finally
FreeAndNil(detailsStream);
end;
end else
begin
serializerIID := GUID_NULL;
WriteBuffer.WriteBuffer(serializerIID, SizeOf(TGUID));
end;
Result := WriteFile(Pipe, WriteBuffer.Memory^, WriteBuffer.Size, bytesWritten, @Overlapped); Result := WriteFile(Pipe, WriteBuffer.Memory^, WriteBuffer.Size, bytesWritten, @Overlapped);
if not Result then if not Result then
@ -412,18 +437,19 @@ begin
{ Thanks to: http://www.osronline.com/showthread.cfm?link=204207 { Thanks to: http://www.osronline.com/showthread.cfm?link=204207
and: http://www.netid.washington.edu/documentation/domains/sddl.aspx and: http://www.netid.washington.edu/documentation/domains/sddl.aspx
0x12018d = 0x12019f =
0x00100000 - SYNCHRONIZE 0x00100000 - SYNCHRONIZE
0x00020000 - READ_CONTROL 0x00020000 - READ_CONTROL
0x00000100 - FILE_WRITE_ATTRIBUTES 0x00000100 - FILE_WRITE_ATTRIBUTES
0x00000080 - FILE_READ_ATTRIBUTES 0x00000080 - FILE_READ_ATTRIBUTES
0x00000010 - FILE_WRITE_EA
0x00000008 - FILE_READ_EA 0x00000008 - FILE_READ_EA
0x00000004 - FILE_CREATE_PIPE_INSTANCE 0x00000004 - FILE_CREATE_PIPE_INSTANCE
0x00000002 - FILE_WRITE_DATA
0x00000001 - FILE_READ_DATA } 0x00000001 - FILE_READ_DATA }
if ConvertStringSecurityDescriptorToSecurityDescriptorW('D:' + // Discretionary ACL if ConvertStringSecurityDescriptorToSecurityDescriptorW('D:' + // Discretionary ACL
'(D;;FA;;;NU)' + // Deny file all access (FA) to network user access (NU) '(D;;FA;;;NU)' + // Deny file all access (FA) to network user access (NU)
'(A;;0x12018d;;;WD)' + // Allow specific permissions for everyone (WD) '(A;;0x12019f;;;WD)', // Allow permissions for everyone (WD)
'(A;;0x12018d;;;CO)', // Allow specific permissions for creator owner (CO)
SDDL_REVISION_1, SDDL_REVISION_1,
@security.lpSecurityDescriptor, @security.lpSecurityDescriptor,
nil) then nil) then
@ -452,6 +478,8 @@ begin
begin begin
client.State := Connected; client.State := Connected;
Clients.Add(client); Clients.Add(client);
AddListener;
end; end;
else else
{ Error occured } { Error occured }