home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
DOS/V Power Report 1997 August
/
VPR9708A.ISO
/
D3TRIAL
/
INSTALL
/
DATA.Z
/
IPCTHRD.PAS
< prev
next >
Wrap
Pascal/Delphi Source File
|
1997-04-24
|
31KB
|
1,138 lines
unit IPCThrd;
{ Inter-Process Communication Thread Classes }
{$DEFINE DEBUG}
interface
uses
SysUtils, Classes, Windows;
{$MINENUMSIZE 4} { DWORD sized enums to keep TEventInfo DWORD aligned }
type
{ WIN32 Helper Classes }
{ THandledObject }
{ This is a generic class for all encapsulated WinAPI's which need to call
CloseHandle when no longer needed. This code eliminates the need for
3 identical destructors in the TEvent, TMutex, and TSharedMem classes
which are descended from this class. }
THandledObject = class(TObject)
protected
FHandle: THandle;
public
destructor Destroy; override;
property Handle: THandle read FHandle;
end;
{ TEvent }
{ This class encapsulates the concept of a Win32 event (not to be
confused with Delphi events), see "CreateEvent" in the Win32
reference for more information }
TEvent = class(THandledObject)
public
constructor Create(const Name: string; Manual: Boolean);
procedure Signal;
procedure Reset;
function Wait(TimeOut: Integer): Boolean;
end;
{ TMutex }
{ This class encapsulates the concept of a Win32 mutex. See "CreateMutex"
in the Win32 reference for more information }
TMutex = class(THandledObject)
public
constructor Create(const Name: string);
function Get(TimeOut: Integer): Boolean;
function Release: Boolean;
end;
{ TSharedMem }
{ This class simplifies the process of creating a region of shared memory.
In Win32, this is accomplished by using the CreateFileMapping and
MapViewOfFile functions. }
TSharedMem = class(THandledObject)
private
FName: string;
FSize: Integer;
FCreated: Boolean;
FFileView: Pointer;
public
constructor Create(const Name: string; Size: Integer);
destructor Destroy; override;
property Name: string read FName;
property Size: Integer read FSize;
property Buffer: Pointer read FFileView;
property Created: Boolean read FCreated;
end;
{$IFDEF DEBUG}
{ Debug Tracing }
{ The IPCTracer class was used to create and debug the IPC classes which
follow. When developing a multi-process, multi-threaded application, it
is difficult to debug effectively using ordinary debuggers. The trace
data is displayed in a Window when you click on a speed button in the
monitor program. }
const
TRACE_BUF_SIZE = 200 * 1024;
TRACE_BUFFER = 'TRACE_BUFFER';
TRACE_MUTEX = 'TRACE_MUTEX';
type
PTraceEntry = ^TTraceEntry;
TTraceEntry = record
Size: Integer;
Time: Integer;
Msg: array[0..0] of Char;
end;
TIPCTracer = class(TObject)
private
FIDName: string[10];
FSharedMem: TSharedMem;
FMutex: TMutex;
function MakePtr(Ofs: Integer): PTraceEntry;
function FirstEntry: PTraceEntry;
function NextEntry: PTraceEntry;
public
constructor Create(ID: string);
destructor Destroy; override;
procedure Add(AMsg: PChar);
procedure GetList(List: TStrings);
procedure Clear;
end;
{$ENDIF}
{ IPC Classes }
{ These are the classes used by the Monitor and Client to perform the
inter-process communication }
const
MAX_CLIENTS = 6;
TIMEOUT = 2000;
BUFFER_NAME = 'BUFFER_NAME';
BUFFER_MUTEX_NAME = 'BUFFER_MUTEX';
MONITOR_EVENT_NAME = 'MONITOR_EVENT';
CLIENT_EVENT_NAME = 'CLIENT_EVENT';
CONNECT_EVENT_NAME = 'CONNECT_EVENT';
CLIENT_DIR_NAME = 'CLIENT_DIRECTORY';
CLIENT_DIR_MUTEX = 'DIRECTORY_MUTEX';
type
EMonitorActive = class(Exception);
TIPCThread = class;
{ TIPCEvent }
{ Win32 events are very basic. They are either signaled or non-signaled.
The TIPCEvent class creates a "typed" TEvent, by using a block of shared
memory to hold an "EventKind" property. The shared memory is also used
to hold an ID, which is important when running multiple clients, and
a Data area for communicating data along with the event }
TEventKind = (
evMonitorAttach, // Notify client that monitor is attaching
evMonitorDetach, // Notify client that monitor is detaching
evMonitorSignal, // Monitor signaling client
evMonitorExit, // Monitor is exiting
evClientStart, // Notify monitor a client has started
evClientStop, // Notify monitor a client has stopped
evClientAttach, // Notify monitor a client is attaching
evClientDetach, // Notify monitor a client is detaching
evClientSwitch, // Notify monitor to switch to a new client
evClientSignal, // Client signaling monitor
evClientExit // Client is exiting
);
TClientFlag = (cfError, cfMouseMove, cfMouseDown, cfResize, cfAttach);
TClientFlags = set of TClientFlag;
PEventData = ^TEventData;
TEventData = packed record
X: SmallInt;
Y: SmallInt;
Flag: TClientFlag;
Flags: TClientFlags;
end;
TConnectEvent = procedure (Sender: TIPCThread; Connecting: Boolean) of Object;
TDirUpdateEvent = procedure (Sender: TIPCThread) of Object;
TIPCNotifyEvent = procedure (Sender: TIPCThread; Data: TEventData) of Object;
PIPCEventInfo = ^TIPCEventInfo;
TIPCEventInfo = record
FID: Integer;
FKind: TEventKind;
FData: TEventData;
end;
TIPCEvent = class(TEvent)
private
FOwner: TIPCThread;
FOwnerID: Integer;
FSharedMem: TSharedMem;
FEventInfo: PIPCEventInfo;
function GetID: Integer;
procedure SetID(Value: Integer);
function GetKind: TEventKind;
procedure SetKind(Value: TEventKind);
function GetData: TEventData;
procedure SetData(Value: TEventData);
public
constructor Create(AOwner: TIPCThread; const Name: string; Manual: Boolean);
destructor Destroy; override;
procedure Signal(Kind: TEventKind);
procedure SignalID(Kind: TEventKind; ID: Integer);
procedure SignalData(Kind: TEventKind; ID: Integer; Data: TEventData);
function WaitFor(TimeOut, ID: Integer; Kind: TEventKind): Boolean;
property ID: Integer read GetID write SetID;
property Kind: TEventKind read GetKind write SetKind;
property Data: TEventData read GetData write SetData;
property OwnerID: Integer read FOwnerID write FOwnerID;
end;
{ TClientDirectory }
{ The client directory is a block of shared memory where the list of all
active clients is maintained }
TClientDirEntry = packed record
ID: Integer;
Name: Array[0..58] of Char;
end;
TClientDirRecords = array[1..MAX_CLIENTS] of TClientDirEntry;
PClientDirRecords = ^TClientDirRecords;
TClientDirectory = class
private
FClientCount: PInteger;
FMonitorID: PInteger;
FMaxClients: Integer;
FMutex: TMutex;
FSharedMem: TSharedMem;
FDirBuffer: PClientDirRecords;
function GetCount: Integer;
function GetClientName(ClientID: Integer): string;
function GetClientRec(Index: Integer): TClientDirEntry;
function IndexOf(ClientID: Integer): Integer;
function GetMonitorID: Integer;
procedure SetMonitorID(MonitorID: Integer);
public
constructor Create(MaxClients: Integer);
destructor Destroy; override;
function AddClient(ClientID: Integer; const AName: string): Integer;
function Last: Integer;
function RemoveClient(ClientID: Integer): Boolean;
property Count: Integer read GetCount;
property ClientRec[Index: Integer]: TClientDirEntry read GetClientRec;
property MonitorID: Integer read GetMonitorID write SetMonitorID;
property Name[ClientID: Integer]: string read GetClientName;
end;
{ TIPCThread }
{ The TIPCThread class implements the functionality which is common between
the monitor and client thread classes. }
TState = (stInActive, stDisconnected, stConnected);
TIPCThread = class(TThread)
protected
{$IFDEF DEBUG}
FTracer: TIPCTracer;
{$ENDIF}
FID: Integer;
FName: string;
FState: TState;
FClientEvent: TIPCEvent;
FMonitorEvent: TIPCEvent;
FConnectEvent: TIPCEvent;
FClientDirectory: TClientDirectory;
FOnSignal: TIPCNotifyEvent;
FOnConnect: TConnectEvent;
public
constructor Create(AID: Integer; const AName: string);
destructor Destroy; override;
procedure Activate; virtual; abstract;
procedure DeActivate; virtual; abstract;
procedure DbgStr(const S: string);
property State: TState read FState;
published
property OnConnect: TConnectEvent read FOnConnect write FOnConnect;
property OnSignal: TIPCNotifyEvent read FOnSignal write FOnSignal;
end;
{ TIPCMonitor }
TIPCMonitor = class(TIPCThread)
private
FClientID: Integer;
FAutoSwitch: Boolean;
FOnDirUpdate: TDirUpdateEvent;
protected
procedure ConnectToClient(ID: Integer);
procedure DisconnectFromClient(Wait: Boolean);
procedure DoOnSignal;
function GetClientName: string;
procedure Execute; override;
procedure SetCurrentClient(ID: Integer);
procedure DoOnDirUpdate;
public
constructor Create(AID: Integer; const AName: string);
procedure Activate; override;
procedure DeActivate; override;
procedure SignalClient(const Value: TClientFlags);
procedure GetClientNames(List: TStrings);
procedure GetDebugInfo(List: TStrings);
procedure SaveDebugInfo(const FileName: string);
procedure ClearDebugInfo;
property AutoSwitch: Boolean read FAutoSwitch write FAutoSwitch;
property ClientName: string read GetClientName;
property ClientID: Integer read FClientID write SetCurrentClient;
property OnDirectoryUpdate: TDirUpdateEvent read FOnDirUpdate write FOnDirUpdate;
end;
{ TIPCClient }
TIPCClient = class(TIPCThread)
private
FWaitEvent: TIPCEvent;
procedure ConnectToMonitor;
procedure DisconnectFromMonitor(Wait: Boolean);
protected
procedure Execute; override;
public
procedure Activate; override;
procedure DeActivate; override;
function ClientCount: Integer;
procedure SignalMonitor(Data: TEventData);
procedure MakeCurrent;
end;
function IsMonitorRunning(var Hndl: THandle): Boolean;
implementation
uses TypInfo;
{ Utility Routines }
procedure Error(const Msg: string);
begin
raise Exception.Create(Msg);
end;
function EventName(Event: TEventKind): string;
begin
Result := GetEnumName(TypeInfo(TEventKind), ord(Event));
end;
{ Utility function used by the monitor to determine if another monitor is
already running. This is needed to make the monitor a single instance .EXE.
This function relies on the fact that the first 4 bytes of the client
directory always contain the Application handle of the monitor, or zero if
no monitor is running. This function is used in Monitor.dpr. }
function IsMonitorRunning(var Hndl: THandle): Boolean;
var
SharedMem: TSharedMem;
begin
SharedMem := TSharedMem.Create(CLIENT_DIR_NAME, 4);
Hndl := PHandle(SharedMem.Buffer)^;
Result := Hndl <> 0;
SharedMem.Free;
end;
{ THandledObject }
destructor THandledObject.Destroy;
begin
if FHandle <> 0 then
CloseHandle(FHandle);
end;
{ TEvent }
constructor TEvent.Create(const Name: string; Manual: Boolean);
begin
FHandle := CreateEvent(nil, Manual, False, PChar(Name));
if FHandle = 0 then abort;
end;
procedure TEvent.Reset;
begin
ResetEvent(FHandle);
end;
procedure TEvent.Signal;
begin
SetEvent(FHandle);
end;
function TEvent.Wait(TimeOut: Integer): Boolean;
begin
Result := WaitForSingleObject(FHandle, TimeOut) = WAIT_OBJECT_0;
end;
{ TMutex }
constructor TMutex.Create(const Name: string);
begin
FHandle := CreateMutex(nil, False, PChar(Name));
if FHandle = 0 then abort;
end;
function TMutex.Get(TimeOut: Integer): Boolean;
begin
Result := WaitForSingleObject(FHandle, TimeOut) = WAIT_OBJECT_0;
end;
function TMutex.Release: Boolean;
begin
Result := ReleaseMutex(FHandle);
end;
{ TSharedMem }
constructor TSharedMem.Create(const Name: string; Size: Integer);
begin
try
FName := Name;
FSize := Size;
{ CreateFileMapping, when called with $FFFFFFFF for the hanlde value,
creates a region of shared memory }
FHandle := CreateFileMapping($FFFFFFFF, nil, PAGE_READWRITE, 0,
Size, PChar(Name));
if FHandle = 0 then abort;
FCreated := GetLastError = 0;
{ We still need to map a pointer to the handle of the shared memory region }
FFileView := MapViewOfFile(FHandle, FILE_MAP_WRITE, 0, 0, Size);
if FFileView = nil then abort;
except
Error(Format('Error creating shared memory %s (%d)', [Name, GetLastError]));
end;
end;
destructor TSharedMem.Destroy;
begin
if FFileView <> nil then
UnmapViewOfFile(FFileView);
inherited Destroy;
end;
{ IPC Classes }
{$IFDEF DEBUG}
{ TIPCTracer }
constructor TIPCTracer.Create(ID: string);
begin
FIDName := ID;
FSharedMem := TSharedMem.Create(TRACE_BUFFER, TRACE_BUF_SIZE);
FMutex := TMutex.Create(TRACE_MUTEX);
if Integer(FSharedMem.Buffer^) = 0 then
Integer(FSharedMem.Buffer^) := SizeOf(PTraceEntry);
end;
destructor TIPCTracer.Destroy;
begin
FMutex.Free;
FSharedMem.Free;
end;
function TIPCTracer.MakePtr(Ofs: Integer): PTraceEntry;
begin
Result := PTraceEntry(Integer(FSharedMem.Buffer) + Ofs);
end;
function TIPCTracer.FirstEntry: PTraceEntry;
begin
Result := MakePtr(SizeOf(PTraceEntry));
end;
function TIPCTracer.NextEntry: PTraceEntry;
begin
Result := MakePtr(Integer(FSharedMem.Buffer^));
end;
procedure TIPCTracer.Add(AMsg: PChar);
var
TraceEntry: PTraceEntry;
EntrySize: Integer;
TempTime: TLargeInteger;
begin
FMutex.Get(INFINITE);
TraceEntry := NextEntry;
EntrySize := StrLen(AMsg) + SizeOf(TTraceEntry) + 16;
{ If we hit the end of the buffer, just wrap around }
if EntrySize + Integer(FSharedMem.Buffer^) > FSharedMem.Size then
TraceEntry := FirstEntry;
with TraceEntry^ do
begin
QueryPerformanceCounter(TempTime);
Time := TempTime.LowPart;
Size := EntrySize;
FormatBuf(Msg, Size, '%10S: %S', 10, [FIDName, AMsg]);
Integer(FSharedMem.Buffer^) := Integer(FSharedMem.Buffer^) + Size;
end;
FMutex.Release;
end;
procedure TIPCTracer.GetList(List: TStrings);
var
LastEntry, TraceEntry: PTraceEntry;
Dif: Integer;
LastTime: Integer;
begin
List.BeginUpdate;
try
LastEntry := NextEntry;
TraceEntry := FirstEntry;
LastTime := TraceEntry.Time;
List.Clear;
while TraceEntry <> LastEntry do
begin
Dif := TraceEntry.Time - LastTime;
List.Add(format('%x %10d %s', [TraceEntry.Time, Dif, PChar(@TraceEntry.Msg)]));
LastTime := TraceEntry.Time;
Integer(TraceEntry) := Integer(TraceEntry) + TraceEntry.Size;
end;
finally
List.EndUpdate;
end;
end;
procedure TIPCTracer.Clear;
begin
FMutex.Get(INFINITE);
Integer(FSharedMem.Buffer^) := SizeOf(PTraceEntry);
FMutex.Release;
end;
{$ENDIF}
{ TIPCEvent }
constructor TIPCEvent.Create(AOwner: TIPCThread; const Name: string;
Manual: Boolean);
begin
inherited Create(Name, Manual);
FOwner := AOwner;
FSharedMem := TSharedMem.Create(Format('%s.Data', [Name]), SizeOf(TIPCEventInfo));
FEventInfo := FSharedMem.Buffer;
end;
destructor TIPCEvent.Destroy;
begin
FSharedMem.Free;
inherited Destroy;
end;
function TIPCEvent.GetID: Integer;
begin
Result := FEventInfo.FID;
end;
procedure TIPCEvent.SetID(Value: Integer);
begin
FEventInfo.FID := Value;
end;
function TIPCEvent.GetKind: TEventKind;
begin
Result := FEventInfo.FKind;
end;
procedure TIPCEvent.SetKind(Value: TEventKind);
begin
FEventInfo.FKind := Value;
end;
function TIPCEvent.GetData: TEventData;
begin
Result := FEventInfo.FData;
end;
procedure TIPCEvent.SetData(Value: TEventData);
begin
FEventInfo.FData := Value;
end;
procedure TIPCEvent.Signal(Kind: TEventKind);
begin
FEventInfo.FID := FOwnerID;
FEventInfo.FKind := Kind;
inherited Signal;
end;
procedure TIPCEvent.SignalID(Kind: TEventKind; ID: Integer);
begin
FEventInfo.FID := ID;
FEventInfo.FKind := Kind;
inherited Signal;
end;
procedure TIPCEvent.SignalData(Kind: TEventKind; ID: Integer; Data: TEventData);
begin
FEventInfo.FID := ID;
FEventInfo.FData := Data;
FEventInfo.FKind := Kind;
inherited Signal;
end;
function TIPCEvent.WaitFor(TimeOut, ID: Integer; Kind: TEventKind): Boolean;
begin
Result := Wait(TimeOut);
if Result then
Result := (ID = FEventInfo.FID) and (Kind = FEventInfo.FKind);
if not Result then
FOwner.DbgStr(Format('Wait Failed %s Kind: %s ID: %x' ,
[FOwner.ClassName, EventName(Kind), ID]));
end;
{ TClientDirectory }
constructor TClientDirectory.Create(MaxClients: Integer);
begin
FMaxClients := MaxClients;
FMutex := TMutex.Create(CLIENT_DIR_MUTEX);
FSharedMem := TSharedMem.Create(CLIENT_DIR_NAME,
FMaxClients * SizeOf(TClientDirEntry) + 8);
FMonitorID := FSharedMem.Buffer;
Integer(FClientCount) := Integer(FMonitorID) + SizeOf(FMonitorID);
Integer(FDirBuffer) := Integer(FClientCount) + SizeOf(FClientCount);
end;
destructor TClientDirectory.Destroy;
begin
FSharedMem.Free;
end;
function TClientDirectory.AddClient(ClientID: Integer; const AName: string): Integer;
begin
Result := -1;
if Count = FMaxClients then
Error(Format('Maximum of %d clients allowed', [FMaxClients]));
if IndexOf(ClientID) > -1 then
Error('Duplicate client ID');
if FMutex.Get(TIMEOUT) then
try
with FDirBuffer[Count+1] do
begin
ID := ClientID;
StrPLCopy(Name, PChar(AName), SizeOf(Name)-1);
Inc(FClientCount^);
Result := Count;
end;
finally
FMutex.Release;
end;
end;
function TClientDirectory.GetCount: Integer;
begin
Result := FClientCount^;
end;
function TClientDirectory.GetClientRec(Index: Integer): TClientDirEntry;
begin
if (Index > 0) and (Index <= Count) then
Result := FDirBuffer[Index]
else
Error('Invalid client list index');
end;
function TClientDirectory.GetClientName(ClientID: Integer): string;
var
Index: Integer;
begin
Index := IndexOf(ClientID);
if Index > 0 then
Result := FDirBuffer[Index].Name
else
Result := '';
end;
function TClientDirectory.IndexOf(ClientID: Integer): Integer;
var
I: Integer;
begin
for I := 1 to Count do
if FDirBuffer[I].ID = ClientID then
begin
Result := I;
Exit;
end;
Result := -1;
end;
function TClientDirectory.Last: Integer;
begin
if Count > 0 then
Result := FDirBuffer[Count].ID else
Result := 0;
end;
function TClientDirectory.RemoveClient(ClientID: Integer): Boolean;
var
Index: Integer;
begin
Index := IndexOf(ClientID);
if (Index > -1) and FMutex.Get(TIMEOUT) then
try
if (Index > 0) and (Index < Count) then
Move(FDirBuffer[Index+1], FDirBuffer[Index],
(Count - Index) * SizeOf(TClientDirEntry));
Dec(FClientCount^);
Result := True;
finally
FMutex.Release;
end
else
Result := False;
end;
function TClientDirectory.GetMonitorID: Integer;
begin
Result := FMonitorID^;
end;
procedure TClientDirectory.SetMonitorID(MonitorID: Integer);
begin
FMonitorID^ := MonitorID;
end;
{ TIPCThread }
constructor TIPCThread.Create(AID: Integer; const AName: string);
begin
inherited Create(True);
FID := AID;
FName := AName;
{$IFDEF DEBUG}
if Self is TIPCMonitor then
FTracer := TIPCTracer.Create(FName)
else
FTracer := TIPCTracer.Create(IntToHex(FID, 8));
{$ENDIF}
FMonitorEvent := TIPCEvent.Create(Self, MONITOR_EVENT_NAME, False);
FClientEvent := TIPCEvent.Create(Self, CLIENT_EVENT_NAME, False);
FConnectEvent := TIPCEvent.Create(Self, CONNECT_EVENT_NAME, True);
FClientDirectory := TClientDirectory.Create(MAX_CLIENTS);
end;
destructor TIPCThread.Destroy;
begin
DeActivate;
inherited Destroy;
FClientDirectory.Free;
FClientEvent.Free;
FMonitorEvent.Free;
FState := stInActive;
{$IFDEF DEBUG}
FTracer.Free;
{$ENDIF}
end;
{ This procedure is called all over the place to keep track of what is
going on }
procedure TIPCThread.DbgStr(const S: string);
begin
{$IFDEF DEBUG}
FTracer.Add(PChar(S));
{$ENDIF}
end;
{ TIPCMonitor }
constructor TIPCMonitor.Create(AID: Integer; const AName: string);
begin
inherited Create(AID, AName);
FAutoSwitch := True;
end;
procedure TIPCMonitor.Activate;
begin
if FState = stInActive then
begin
{ Put the monitor handle into the client directory so we can use it to
prevent multiple monitors from running }
if FClientDirectory.MonitorID = 0 then
FClientDirectory.MonitorID := FID
else
raise EMonitorActive.Create('');
FState := stDisconnected;
Resume;
end;
end;
procedure TIPCMonitor.DeActivate;
begin
if (State <> stInActive) and not Suspended then
begin
FClientDirectory.MonitorID := 0;
FMonitorEvent.Signal(evMonitorExit);
if WaitForSingleObject(Handle, TIMEOUT) <> WAIT_OBJECT_0 then
TerminateThread(Handle, 0);
end;
end;
{ This method, and the TIPCClient.Execute method represent the meat of this
program. These two thread handlers are responsible for communcation with
each other through the IPC event classes }
procedure TIPCMonitor.Execute;
var
WaitResult: Integer;
begin
DbgStr(FName + ' Activated');
if FClientDirectory.Count > 0 then
FMonitorEvent.SignalID(evClientStart, FClientDirectory.Last);
while True do
try
WaitResult := WaitForSingleObject(FMonitorEvent.Handle, INFINITE);
if WaitResult >= WAIT_ABANDONED then { Something went wrong }
DisconnectFromClient(False) else
if WaitResult = WAIT_OBJECT_0 then { Monitor Event }
begin
DbgStr('Event Signaled: '+EventName(FMonitorEvent.Kind));
case FMonitorEvent.Kind of
evClientSignal:
DoOnSignal;
evClientStart:
begin
if AutoSwitch or (FClientID = 0) then
ConnectToClient(FMonitorEvent.ID);
DoOnDirUpdate;
end;
evClientStop:
DoOnDirUpdate;
evClientDetach:
begin
DisconnectFromClient(False);
Sleep(100);
if AutoSwitch then
ConnectToClient(FClientDirectory.Last);
end;
evClientSwitch:
ConnectToClient(FMonitorEvent.ID);
evMonitorExit:
begin
DisconnectFromClient(False);
break;
end;
end;
end
else
DbgStr(Format('Unexpected Wait Return Code: %d', [WaitResult]));
except
on E:Exception do
DbgStr(Format('Exception raised in Thread Handler: %s at %X', [E.Message, ExceptAddr]));
end;
FState := stInActive;
DbgStr('Thread Handler Exited');
end;
procedure TIPCMonitor.ConnectToClient(ID: Integer);
begin
if ID = FClientID then Exit;
if FState = stConnected then
DisconnectFromClient(True);
if ID = 0 then Exit;
DbgStr(Format('Sending evMonitorAttach: %X', [ID]));
{ Tell a client we want to attach to them }
FConnectEvent.SignalID(evMonitorAttach, ID);
{ Wait for the client to say "OK" }
if FMonitorEvent.WaitFor(TIMEOUT, ID, evClientAttach) and
(FMonitorEvent.Data.Flag = cfAttach) then
begin
FClientID := ID;
FState := stConnected;
if Assigned(FOnConnect) then FOnConnect(Self, True);
DbgStr('ConnectToClient Successful');
end
else
DbgStr('ConnectToClient Failed: '+EventName(FMonitorEvent.Kind));
end;
{ If Wait is true ... }
procedure TIPCMonitor.DisconnectFromClient(Wait: Boolean);
begin
if FState = stConnected then
begin
DbgStr(Format('Sending evMonitorDetach: %x', [FClientID]));
{ Tell the client we are detaching }
FClientEvent.SignalID(evMonitorDetach, FClientID);
{ If we (the monitor) initiated the detach process, then wait around
for the client to acknowledge the detach, otherwise, just continue on }
if Wait then
if not FMonitorEvent.WaitFor(TIMEOUT, FClientID, evClientDetach) then
begin
DbgStr(Format('Error waiting for client to detach: %x', [FClientID]));
FClientDirectory.RemoveClient(FClientID);
end;
FClientID := 0;
FState := stDisconnected;
if Assigned(FOnConnect) then FOnConnect(Self, False);
if not Wait and Assigned(FOnDirUpdate) then
DoOnDirUpdate;
end;
end;
{ This method is called when the client has new data for us }
procedure TIPCMonitor.DoOnSignal;
begin
if Assigned(FOnSignal) and (FMonitorEvent.ID = FClientID) then
FOnSignal(Self, FMonitorEvent.Data);
end;
{ Tell the client we have new flags for it }
procedure TIPCMonitor.SignalClient(const Value: TClientFlags);
begin
if FState = stConnected then
begin
FClientEvent.FEventInfo.FData.Flags := Value;
DbgStr('Signaling Client');
FClientEvent.SignalData(evMonitorSignal, FClientID, FClientEvent.Data);
end;
end;
function TIPCMonitor.GetClientName: string;
begin
Result := FClientDirectory.Name[FClientID];
end;
procedure TIPCMonitor.GetClientNames(List: TStrings);
var
I: Integer;
S: string;
DupCnt: Integer;
begin
List.BeginUpdate;
try
List.Clear;
for I := 1 to FClientDirectory.Count do
with FClientDirectory.ClientRec[I] do
begin
S := Name;
DupCnt := 1;
{ Number duplicate names so we can distinguish them in the client menu }
while(List.IndexOf(S) > -1) do
begin
Inc(DupCnt);
S := Format('%s (%d)', [Name, DupCnt]);
end;
List.AddObject(S, TObject(ID));
end;
finally
List.EndUpdate;
end;
end;
procedure TIPCMonitor.SetCurrentClient(ID: Integer);
begin
if ID = 0 then ID := FClientDirectory.Last;
if ID <> 0 then
FMonitorEvent.SignalID(evClientSwitch, ID);
end;
procedure TIPCMonitor.ClearDebugInfo;
begin
{$IFDEF DEBUG}
FTracer.Clear;
{$ENDIF}
end;
procedure TIPCMonitor.GetDebugInfo(List: TStrings);
begin
{$IFDEF DEBUG}
FTracer.GetList(List);
{$ELSE}
List.Add('Debug Tracing Disabled');
{$ENDIF}
end;
procedure TIPCMonitor.SaveDebugInfo(const FileName: string);
{$IFDEF DEBUG}
var
List: TStringList;
begin
List := TStringList.Create;
try
GetDebugInfo(List);
List.SaveToFile(FileName);
finally
List.Free;
end;
{$ELSE}
begin
{$ENDIF}
end;
procedure TIPCMonitor.DoOnDirUpdate;
begin
if Assigned(FOnDirUpdate) then FOnDirUpdate(Self);
end;
{ TIPCClient }
procedure TIPCClient.Activate;
begin
if FState = stInActive then
begin
FWaitEvent := FConnectEvent;
FMonitorEvent.OwnerID := FID;
FConnectEvent.OwnerID := FID;
FClientEvent.OwnerID := FID;
FClientDirectory.AddClient(FID, FName);
FState := stDisconnected;
Resume;
end
end;
procedure TIPCClient.DeActivate;
begin
if Assigned(FClientDirectory) then
FClientDirectory.RemoveClient(FID);
if (FState <> stInActive) and not Suspended then
begin
FWaitEvent.Signal(evClientExit);
if WaitForSingleObject(Handle, TIMEOUT) <> WAIT_OBJECT_0 then
TerminateThread(Handle, 0);
end;
end;
procedure TIPCClient.Execute;
begin
DbgStr(FName + ' Activated');
if FClientDirectory.MonitorID <> 0 then
FMonitorEvent.SignalID(evClientStart, FID);
while True do
try
if WaitForSingleObject(FWaitEvent.Handle, INFINITE) <> WAIT_OBJECT_0 then Break;
if FWaitEvent.ID <> FID then
begin
Sleep(200);
continue;
end;
DbgStr('Client Event Signaled: '+EventName(FWaitEvent.Kind));
case FWaitEvent.Kind of
evMonitorSignal: if Assigned(FOnSignal) then FOnSignal(Self, FWaitEvent.Data);
evMonitorAttach: ConnectToMonitor;
evMonitorDetach:
begin
DisconnectFromMonitor(False);
Sleep(200);
end;
evClientExit:
begin
if FClientDirectory.MonitorID <> 0 then
begin
if FState = stConnected then
DisconnectFromMonitor(True)
else
FMonitorEvent.Signal(evClientStop);
end;
break;
end;
end;
except
on E:Exception do
DbgStr(Format('Exception raised in Thread Handler: %s at %X', [E.Message, ExceptAddr]));
end;
FState := stInActive;
DbgStr('Thread Handler Exited');
end;
procedure TIPCClient.ConnectToMonitor;
var
Data: TEventData;
begin
DbgStr('ConnectToMonitor Begin');
FConnectEvent.Reset;
try
FState := stConnected;
FWaitEvent := FClientEvent;
Data.Flag := cfAttach;
FMonitorEvent.SignalData(evClientAttach, FID, Data);
if Assigned(FOnConnect) then FOnConnect(Self, True);
except
DbgStr('Exception in ConnectToMonitor: '+Exception(ExceptObject).Message);
Data.Flag := cfError;
FMonitorEvent.SignalData(evClientAttach, FID, Data);
end;
DbgStr('ConnectToMonitor End');
end;
procedure TIPCClient.DisconnectFromMonitor(Wait: Boolean);
begin
DbgStr('DisconnectFromMonitor Begin');
if FState = stConnected then
begin
if Wait then
begin
DbgStr('Sending evClientDetach');
FMonitorEvent.Signal(evClientDetach);
if FClientEvent.WaitFor(TIMEOUT, FID, evMonitorDetach) then
DbgStr('Got evMonitorDetach') else
DbgStr('Error waiting for evMonitorDetach');
end;
FState := stDisconnected;
FWaitEvent := FConnectEvent;
if not Wait then
begin
DbgStr('DisconnectFromMonitor sending evClientDetach');
FMonitorEvent.Signal(evClientDetach);
end;
if Assigned(FOnConnect) then FOnConnect(Self, False);
end;
DbgStr('DisconnectFromMonitor End');
end;
procedure TIPCClient.SignalMonitor(Data: TEventData);
begin
if FState = stConnected then
begin
DbgStr('Signaling Monitor');
FMonitorEvent.SignalData(evClientSignal, FID, Data);
end;
end;
function TIPCClient.ClientCount: Integer;
begin
Result := FClientDirectory.Count;
end;
procedure TIPCClient.MakeCurrent;
begin
FMonitorEvent.SignalID(evClientStart, FID);
end;
end.