home *** CD-ROM | disk | FTP | other *** search
/ Delphi 5 for Professionals / DELPHI5.iso / Runimage / Delphi50 / Demos / Ipcdemos / ipcthrd.pas < prev    next >
Encoding:
Pascal/Delphi Source File  |  1999-08-11  |  30.0 KB  |  1,140 lines

  1. unit IPCThrd;
  2.  
  3. { Inter-Process Communication Thread Classes }
  4.  
  5. {$DEFINE DEBUG}
  6.  
  7. interface
  8.  
  9. uses
  10.   SysUtils, Classes, Windows;
  11.  
  12. {$MINENUMSIZE 4}  { DWORD sized enums to keep TEventInfo DWORD aligned }  
  13.  
  14. type
  15.  
  16. { WIN32 Helper Classes }
  17.  
  18. { THandledObject }
  19.  
  20. { This is a generic class for all encapsulated WinAPI's which need to call
  21.   CloseHandle when no longer needed.  This code eliminates the need for
  22.   3 identical destructors in the TEvent, TMutex, and TSharedMem classes
  23.   which are descended from this class. }
  24.  
  25.   THandledObject = class(TObject)
  26.   protected
  27.     FHandle: THandle;
  28.   public
  29.     destructor Destroy; override;
  30.     property Handle: THandle read FHandle;
  31.   end;
  32.  
  33. { TEvent }
  34.  
  35. { This class encapsulates the concept of a Win32 event (not to be
  36.   confused with Delphi events), see "CreateEvent" in the Win32
  37.   reference for more information }
  38.  
  39.   TEvent = class(THandledObject)
  40.   public
  41.     constructor Create(const Name: string; Manual: Boolean);
  42.     procedure Signal;
  43.     procedure Reset;
  44.     function Wait(TimeOut: Integer): Boolean;
  45.   end;
  46.  
  47. { TMutex }
  48.  
  49. { This class encapsulates the concept of a Win32 mutex.  See "CreateMutex"
  50.   in the Win32 reference for more information }
  51.  
  52.   TMutex = class(THandledObject)
  53.   public
  54.     constructor Create(const Name: string);
  55.     function Get(TimeOut: Integer): Boolean;
  56.     function Release: Boolean;
  57.   end;
  58.  
  59. { TSharedMem }
  60.  
  61. { This class simplifies the process of creating a region of shared memory.
  62.   In Win32, this is accomplished by using the CreateFileMapping and
  63.   MapViewOfFile functions. }
  64.  
  65.   TSharedMem = class(THandledObject)
  66.   private
  67.     FName: string;
  68.     FSize: Integer;
  69.     FCreated: Boolean;
  70.     FFileView: Pointer;
  71.   public
  72.     constructor Create(const Name: string; Size: Integer);
  73.     destructor Destroy; override;
  74.     property Name: string read FName;
  75.     property Size: Integer read FSize;
  76.     property Buffer: Pointer read FFileView;
  77.     property Created: Boolean read FCreated;
  78.   end;
  79.  
  80. {$IFDEF DEBUG}
  81.  
  82. { Debug Tracing }
  83.  
  84. { The IPCTracer class was used to create and debug the IPC classes which
  85.   follow.  When developing a multi-process, multi-threaded application, it
  86.   is difficult to debug effectively using ordinary debuggers.  The trace
  87.   data is displayed in a Window when you click on a speed button in the
  88.   monitor program. }
  89.  
  90. const
  91.   TRACE_BUF_SIZE = 200 * 1024;
  92.   TRACE_BUFFER   = 'TRACE_BUFFER';
  93.   TRACE_MUTEX    = 'TRACE_MUTEX';
  94.  
  95. type
  96.  
  97.   PTraceEntry = ^TTraceEntry;
  98.   TTraceEntry = record
  99.     Size: Integer;
  100.     Time: Integer;
  101.     Msg: array[0..0] of Char;
  102.   end;
  103.  
  104.   TIPCTracer = class(TObject)
  105.   private
  106.     FIDName: string[10];
  107.     FSharedMem: TSharedMem;
  108.     FMutex: TMutex;
  109.     function MakePtr(Ofs: Integer): PTraceEntry;
  110.     function FirstEntry: PTraceEntry;
  111.     function NextEntry: PTraceEntry;
  112.   public
  113.     constructor Create(ID: string);
  114.     destructor Destroy; override;
  115.     procedure Add(AMsg: PChar);
  116.     procedure GetList(List: TStrings);
  117.     procedure Clear;
  118.   end;
  119.  
  120. {$ENDIF}
  121.  
  122. { IPC Classes }
  123.  
  124. { These are the classes used by the Monitor and Client to perform the
  125.   inter-process communication }
  126.  
  127. const
  128.   MAX_CLIENTS        = 6;
  129.   TIMEOUT            = 2000;
  130.   BUFFER_NAME        = 'BUFFER_NAME';
  131.   BUFFER_MUTEX_NAME  = 'BUFFER_MUTEX';
  132.   MONITOR_EVENT_NAME = 'MONITOR_EVENT';
  133.   CLIENT_EVENT_NAME  = 'CLIENT_EVENT';
  134.   CONNECT_EVENT_NAME = 'CONNECT_EVENT';
  135.   CLIENT_DIR_NAME    = 'CLIENT_DIRECTORY';
  136.   CLIENT_DIR_MUTEX   = 'DIRECTORY_MUTEX';
  137.  
  138. type
  139.  
  140.   EMonitorActive = class(Exception);
  141.  
  142.   TIPCThread = class;
  143.  
  144.  
  145. { TIPCEvent }
  146.  
  147. { Win32 events are very basic.  They are either signaled or non-signaled.
  148.   The TIPCEvent class creates a "typed" TEvent, by using a block of shared
  149.   memory to hold an "EventKind" property.  The shared memory is also used
  150.   to hold an ID, which is important when running multiple clients, and
  151.   a Data area for communicating data along with the event }
  152.  
  153.   TEventKind = (
  154.     evMonitorAttach,    // Notify client that monitor is attaching
  155.     evMonitorDetach,    // Notify client that monitor is detaching
  156.     evMonitorSignal,    // Monitor signaling client
  157.     evMonitorExit,      // Monitor is exiting
  158.     evClientStart,      // Notify monitor a client has started
  159.     evClientStop,       // Notify monitor a client has stopped
  160.     evClientAttach,     // Notify monitor a client is attaching
  161.     evClientDetach,     // Notify monitor a client is detaching
  162.     evClientSwitch,     // Notify monitor to switch to a new client
  163.     evClientSignal,     // Client signaling monitor
  164.     evClientExit        // Client is exiting
  165.   );
  166.  
  167.   TClientFlag = (cfError, cfMouseMove, cfMouseDown, cfResize, cfAttach);
  168.   TClientFlags = set of TClientFlag;
  169.  
  170.   PEventData = ^TEventData;
  171.   TEventData = packed record
  172.     X: SmallInt;
  173.     Y: SmallInt;
  174.     Flag: TClientFlag;
  175.     Flags: TClientFlags;
  176.   end;
  177.  
  178.   TConnectEvent = procedure (Sender: TIPCThread; Connecting: Boolean) of Object;
  179.   TDirUpdateEvent = procedure (Sender: TIPCThread) of Object;
  180.   TIPCNotifyEvent = procedure (Sender: TIPCThread; Data: TEventData) of Object;
  181.  
  182.   PIPCEventInfo = ^TIPCEventInfo;
  183.   TIPCEventInfo = record
  184.     FID: Integer;
  185.     FKind: TEventKind;
  186.     FData: TEventData;
  187.   end;
  188.  
  189.   TIPCEvent = class(TEvent)
  190.   private
  191.     FOwner: TIPCThread;
  192.     FOwnerID: Integer;
  193.     FSharedMem: TSharedMem;
  194.     FEventInfo: PIPCEventInfo;
  195.     function GetID: Integer;
  196.     procedure SetID(Value: Integer);
  197.     function GetKind: TEventKind;
  198.     procedure SetKind(Value: TEventKind);
  199.     function GetData: TEventData;
  200.     procedure SetData(Value: TEventData);
  201.   public
  202.     constructor Create(AOwner: TIPCThread; const Name: string; Manual: Boolean);
  203.     destructor Destroy; override;
  204.     procedure Signal(Kind: TEventKind);
  205.     procedure SignalID(Kind: TEventKind; ID: Integer);
  206.     procedure SignalData(Kind: TEventKind; ID: Integer; Data: TEventData);
  207.     function WaitFor(TimeOut, ID: Integer; Kind: TEventKind): Boolean;
  208.     property ID: Integer read GetID write SetID;
  209.     property Kind: TEventKind read GetKind write SetKind;
  210.     property Data: TEventData read GetData write SetData;
  211.     property OwnerID: Integer read FOwnerID write FOwnerID;
  212.   end;
  213.  
  214. { TClientDirectory }
  215.  
  216. { The client directory is a block of shared memory where the list of all
  217.   active clients is maintained }
  218.  
  219.   TClientDirEntry = packed record
  220.     ID: Integer;
  221.     Name: Array[0..58] of Char;
  222.   end;
  223.  
  224.   TClientDirRecords = array[1..MAX_CLIENTS] of TClientDirEntry;
  225.   PClientDirRecords = ^TClientDirRecords;
  226.  
  227.   TClientDirectory = class
  228.   private
  229.     FClientCount: PInteger;
  230.     FMonitorID: PInteger;
  231.     FMaxClients: Integer;
  232.     FMutex: TMutex;
  233.     FSharedMem: TSharedMem;
  234.     FDirBuffer: PClientDirRecords;
  235.     function GetCount: Integer;
  236.     function GetClientName(ClientID: Integer): string;
  237.     function GetClientRec(Index: Integer): TClientDirEntry;
  238.     function IndexOf(ClientID: Integer): Integer;
  239.     function GetMonitorID: Integer;
  240.     procedure SetMonitorID(MonitorID: Integer);
  241.   public
  242.     constructor Create(MaxClients: Integer);
  243.     destructor Destroy; override;
  244.     function AddClient(ClientID: Integer; const AName: string): Integer;
  245.     function Last: Integer;
  246.     function RemoveClient(ClientID: Integer): Boolean;
  247.     property Count: Integer read GetCount;
  248.     property ClientRec[Index: Integer]: TClientDirEntry read GetClientRec;
  249.     property MonitorID: Integer read GetMonitorID write SetMonitorID;
  250.     property Name[ClientID: Integer]: string read GetClientName;
  251.   end;
  252.  
  253. { TIPCThread }
  254.  
  255. { The TIPCThread class implements the functionality which is common between
  256.   the monitor and client thread classes. }
  257.  
  258.   TState = (stInActive, stDisconnected, stConnected);
  259.  
  260.   TIPCThread = class(TThread)
  261.   protected
  262. {$IFDEF DEBUG}
  263.     FTracer: TIPCTracer;
  264. {$ENDIF}
  265.     FID: Integer;
  266.     FName: string;
  267.     FState: TState;
  268.     FClientEvent: TIPCEvent;
  269.     FMonitorEvent: TIPCEvent;
  270.     FConnectEvent: TIPCEvent;
  271.     FClientDirectory: TClientDirectory;
  272.     FOnSignal: TIPCNotifyEvent;
  273.     FOnConnect: TConnectEvent;
  274.   public
  275.     constructor Create(AID: Integer; const AName: string);
  276.     destructor Destroy; override;
  277.     procedure Activate; virtual; abstract;
  278.     procedure DeActivate; virtual; abstract;
  279.     procedure DbgStr(const S: string);
  280.     property State: TState read FState;
  281.   published
  282.     property OnConnect: TConnectEvent read FOnConnect write FOnConnect;
  283.     property OnSignal: TIPCNotifyEvent read FOnSignal write FOnSignal;
  284.   end;
  285.  
  286. { TIPCMonitor }
  287.  
  288.   TIPCMonitor = class(TIPCThread)
  289.   private
  290.     FClientID: Integer;
  291.     FAutoSwitch: Boolean;
  292.     FOnDirUpdate: TDirUpdateEvent;
  293.   protected
  294.     procedure ConnectToClient(ID: Integer);
  295.     procedure DisconnectFromClient(Wait: Boolean);
  296.     procedure DoOnSignal;
  297.     function GetClientName: string;
  298.     procedure Execute; override;
  299.     procedure SetCurrentClient(ID: Integer);
  300.     procedure DoOnDirUpdate;
  301.   public
  302.     constructor Create(AID: Integer; const AName: string);
  303.     procedure Activate; override;
  304.     procedure DeActivate; override;
  305.     procedure SignalClient(const Value: TClientFlags);
  306.     procedure GetClientNames(List: TStrings);
  307.     procedure GetDebugInfo(List: TStrings);
  308.     procedure SaveDebugInfo(const FileName: string);
  309.     procedure ClearDebugInfo;
  310.     property AutoSwitch: Boolean read FAutoSwitch write FAutoSwitch;
  311.     property ClientName: string read GetClientName;
  312.     property ClientID: Integer read FClientID write SetCurrentClient;
  313.     property OnDirectoryUpdate: TDirUpdateEvent read FOnDirUpdate write FOnDirUpdate;
  314.   end;
  315.  
  316. { TIPCClient }
  317.  
  318.   TIPCClient = class(TIPCThread)
  319.   private
  320.     FWaitEvent: TIPCEvent;
  321.     procedure ConnectToMonitor;
  322.     procedure DisconnectFromMonitor(Wait: Boolean);
  323.   protected
  324.     procedure Execute; override;
  325.   public
  326.     procedure Activate; override;
  327.     procedure DeActivate; override;
  328.     function ClientCount: Integer;
  329.     procedure SignalMonitor(Data: TEventData);
  330.     procedure MakeCurrent;
  331.   end;
  332.  
  333. function IsMonitorRunning(var Hndl: THandle): Boolean;
  334.  
  335. implementation
  336.  
  337. uses TypInfo;
  338.  
  339. { Utility Routines }
  340.  
  341. procedure Error(const Msg: string);
  342. begin
  343.   raise Exception.Create(Msg);
  344. end;
  345.  
  346. function EventName(Event: TEventKind): string;
  347. begin
  348.   Result := GetEnumName(TypeInfo(TEventKind), ord(Event));
  349. end;
  350.  
  351. { Utility function used by the monitor to determine if another monitor is
  352.   already running.  This is needed to make the monitor a single instance .EXE.
  353.   This function relies on the fact that the first 4 bytes of the client
  354.   directory always contain the Application handle of the monitor, or zero if
  355.   no monitor is running.  This function is used in Monitor.dpr. }
  356.  
  357. function IsMonitorRunning(var Hndl: THandle): Boolean;
  358. var
  359.   SharedMem: TSharedMem;
  360. begin
  361.   SharedMem := TSharedMem.Create(CLIENT_DIR_NAME, 4);
  362.   Hndl := PHandle(SharedMem.Buffer)^;
  363.   Result := Hndl <> 0;
  364.   SharedMem.Free;
  365. end;
  366.  
  367. { THandledObject }
  368.  
  369. destructor THandledObject.Destroy;
  370. begin
  371.   if FHandle <> 0 then
  372.     CloseHandle(FHandle);
  373. end;
  374.  
  375. { TEvent }
  376.  
  377. constructor TEvent.Create(const Name: string; Manual: Boolean);
  378. begin
  379.   FHandle := CreateEvent(nil, Manual, False, PChar(Name));
  380.   if FHandle = 0 then abort;
  381. end;
  382.  
  383. procedure TEvent.Reset;
  384. begin
  385.   ResetEvent(FHandle);
  386. end;
  387.  
  388. procedure TEvent.Signal;
  389. begin
  390.   SetEvent(FHandle);
  391. end;
  392.  
  393. function TEvent.Wait(TimeOut: Integer): Boolean;
  394. begin
  395.   Result := WaitForSingleObject(FHandle, TimeOut) = WAIT_OBJECT_0;
  396. end;
  397.  
  398. { TMutex }
  399.  
  400. constructor TMutex.Create(const Name: string);
  401. begin
  402.   FHandle := CreateMutex(nil, False, PChar(Name));
  403.   if FHandle = 0 then abort;
  404. end;
  405.  
  406. function TMutex.Get(TimeOut: Integer): Boolean;
  407. begin
  408.   Result := WaitForSingleObject(FHandle, TimeOut) = WAIT_OBJECT_0;
  409. end;
  410.  
  411. function TMutex.Release: Boolean;
  412. begin
  413.   Result := ReleaseMutex(FHandle);
  414. end;
  415.  
  416. { TSharedMem }
  417.  
  418. constructor TSharedMem.Create(const Name: string; Size: Integer);
  419. begin
  420.   try
  421.     FName := Name;
  422.     FSize := Size;
  423.     { CreateFileMapping, when called with $FFFFFFFF for the hanlde value,
  424.       creates a region of shared memory }
  425.     FHandle := CreateFileMapping($FFFFFFFF, nil, PAGE_READWRITE, 0,
  426.         Size, PChar(Name));
  427.     if FHandle = 0 then abort;
  428.     FCreated := GetLastError = 0;
  429.     { We still need to map a pointer to the handle of the shared memory region }
  430.     FFileView := MapViewOfFile(FHandle, FILE_MAP_WRITE, 0, 0, Size);
  431.     if FFileView = nil then abort;
  432.   except
  433.     Error(Format('Error creating shared memory %s (%d)', [Name, GetLastError]));
  434.   end;
  435. end;
  436.  
  437. destructor TSharedMem.Destroy;
  438. begin
  439.   if FFileView <> nil then
  440.     UnmapViewOfFile(FFileView);
  441.   inherited Destroy;
  442. end;
  443.  
  444. { IPC Classes }
  445.  
  446. {$IFDEF DEBUG}
  447.  
  448. { TIPCTracer }
  449.  
  450. constructor TIPCTracer.Create(ID: string);
  451. begin
  452.   FIDName := ID;
  453.   FSharedMem := TSharedMem.Create(TRACE_BUFFER, TRACE_BUF_SIZE);
  454.   FMutex := TMutex.Create(TRACE_MUTEX);
  455.   if Integer(FSharedMem.Buffer^) = 0 then
  456.     Integer(FSharedMem.Buffer^) := SizeOf(PTraceEntry);
  457. end;
  458.  
  459. destructor TIPCTracer.Destroy;
  460. begin
  461.   FMutex.Free;
  462.   FSharedMem.Free;
  463. end;
  464.  
  465. function TIPCTracer.MakePtr(Ofs: Integer): PTraceEntry;
  466. begin
  467.   Result := PTraceEntry(Integer(FSharedMem.Buffer) + Ofs);
  468. end;
  469.  
  470. function TIPCTracer.FirstEntry: PTraceEntry;
  471. begin
  472.   Result := MakePtr(SizeOf(PTraceEntry));
  473. end;
  474.  
  475. function TIPCTracer.NextEntry: PTraceEntry;
  476. begin
  477.   Result := MakePtr(Integer(FSharedMem.Buffer^));
  478. end;
  479.  
  480. procedure TIPCTracer.Add(AMsg: PChar);
  481. var
  482.   TraceEntry: PTraceEntry;
  483.   EntrySize: Integer;
  484.   TempTime: Int64;
  485. begin
  486.   FMutex.Get(LongInt(INFINITE));
  487.   TraceEntry := NextEntry;
  488.   EntrySize := StrLen(AMsg) + SizeOf(TTraceEntry) + 16;
  489.   { If we hit the end of the buffer, just wrap around }
  490.   if EntrySize + Integer(FSharedMem.Buffer^) > FSharedMem.Size then
  491.     TraceEntry := FirstEntry;
  492.   with TraceEntry^ do
  493.   begin
  494.     QueryPerformanceCounter(TempTime);
  495.     Time := TempTime;
  496.     Size := EntrySize;
  497.     FormatBuf(Msg, Size, '%10S: %S', 10, [FIDName, AMsg]);
  498.     Integer(FSharedMem.Buffer^) := Integer(FSharedMem.Buffer^) + Size;
  499.   end;
  500.   FMutex.Release;
  501. end;
  502.  
  503. procedure TIPCTracer.GetList(List: TStrings);
  504. var
  505.   LastEntry, TraceEntry: PTraceEntry;
  506.   Dif: Integer;
  507.   LastTime: Integer;
  508. begin
  509.   List.BeginUpdate;
  510.   try
  511.     LastEntry := NextEntry;
  512.     TraceEntry := FirstEntry;
  513.     LastTime := TraceEntry.Time;
  514.     List.Clear;
  515.     while TraceEntry <> LastEntry  do
  516.     begin
  517.       Dif := TraceEntry.Time - LastTime;
  518.       List.Add(format('%x %10d %s', [TraceEntry.Time, Dif, PChar(@TraceEntry.Msg)]));
  519.       LastTime := TraceEntry.Time;
  520.       Integer(TraceEntry) := Integer(TraceEntry) + TraceEntry.Size;
  521.     end;
  522.   finally
  523.     List.EndUpdate;
  524.   end;
  525. end;
  526.  
  527. procedure TIPCTracer.Clear;
  528. begin
  529.   FMutex.Get(LongInt(INFINITE));
  530.   Integer(FSharedMem.Buffer^) := SizeOf(PTraceEntry);
  531.   FMutex.Release;
  532. end;
  533.  
  534. {$ENDIF}
  535.  
  536. { TIPCEvent }
  537.  
  538. constructor TIPCEvent.Create(AOwner: TIPCThread; const Name: string;
  539.   Manual: Boolean);
  540. begin
  541.   inherited Create(Name, Manual);
  542.   FOwner := AOwner;
  543.   FSharedMem := TSharedMem.Create(Format('%s.Data', [Name]), SizeOf(TIPCEventInfo));
  544.   FEventInfo := FSharedMem.Buffer;
  545. end;
  546.  
  547. destructor TIPCEvent.Destroy;
  548. begin
  549.   FSharedMem.Free;
  550.   inherited Destroy;
  551. end;
  552.  
  553. function TIPCEvent.GetID: Integer;
  554. begin
  555.   Result := FEventInfo.FID;
  556. end;
  557.  
  558. procedure TIPCEvent.SetID(Value: Integer);
  559. begin
  560.   FEventInfo.FID := Value;
  561. end;
  562.  
  563. function TIPCEvent.GetKind: TEventKind;
  564. begin
  565.   Result := FEventInfo.FKind;
  566. end;
  567.  
  568. procedure TIPCEvent.SetKind(Value: TEventKind);
  569. begin
  570.   FEventInfo.FKind := Value;
  571. end;
  572.  
  573. function TIPCEvent.GetData: TEventData;
  574. begin
  575.   Result := FEventInfo.FData;
  576. end;
  577.  
  578. procedure TIPCEvent.SetData(Value: TEventData);
  579. begin
  580.   FEventInfo.FData := Value;
  581. end;
  582.  
  583. procedure TIPCEvent.Signal(Kind: TEventKind);
  584. begin
  585.   FEventInfo.FID := FOwnerID;
  586.   FEventInfo.FKind := Kind;
  587.   inherited Signal;
  588. end;
  589.  
  590. procedure TIPCEvent.SignalID(Kind: TEventKind; ID: Integer);
  591. begin
  592.   FEventInfo.FID := ID;
  593.   FEventInfo.FKind := Kind;
  594.   inherited Signal;
  595. end;
  596.  
  597. procedure TIPCEvent.SignalData(Kind: TEventKind; ID: Integer; Data: TEventData);
  598. begin
  599.   FEventInfo.FID := ID;
  600.   FEventInfo.FData := Data;
  601.   FEventInfo.FKind := Kind;
  602.   inherited Signal;
  603. end;
  604.  
  605. function TIPCEvent.WaitFor(TimeOut, ID: Integer; Kind: TEventKind): Boolean;
  606. begin
  607.   Result := Wait(TimeOut);
  608.   if Result then
  609.     Result := (ID = FEventInfo.FID) and (Kind = FEventInfo.FKind);
  610.   if not Result then
  611.     FOwner.DbgStr(Format('Wait Failed %s Kind: %s ID: %x' ,
  612.       [FOwner.ClassName, EventName(Kind), ID]));
  613. end;
  614.  
  615. { TClientDirectory }
  616.  
  617. constructor TClientDirectory.Create(MaxClients: Integer);
  618. begin
  619.   FMaxClients := MaxClients;
  620.   FMutex := TMutex.Create(CLIENT_DIR_MUTEX);
  621.   FSharedMem := TSharedMem.Create(CLIENT_DIR_NAME,
  622.     FMaxClients * SizeOf(TClientDirEntry) + 8);
  623.   FMonitorID := FSharedMem.Buffer;
  624.   Integer(FClientCount) := Integer(FMonitorID) + SizeOf(FMonitorID);
  625.   Integer(FDirBuffer) := Integer(FClientCount) + SizeOf(FClientCount);
  626. end;
  627.  
  628. destructor TClientDirectory.Destroy;
  629. begin
  630.   FSharedMem.Free;
  631.   FMutex.Free;
  632. end;
  633.  
  634. function TClientDirectory.AddClient(ClientID: Integer; const AName: string): Integer;
  635. begin
  636.   Result := -1;
  637.   if Count = FMaxClients then
  638.     Error(Format('Maximum of %d clients allowed', [FMaxClients]));
  639.   if IndexOf(ClientID) > -1 then
  640.     Error('Duplicate client ID');
  641.   if FMutex.Get(TIMEOUT) then
  642.   try
  643.     with FDirBuffer[Count+1] do
  644.     begin
  645.       ID := ClientID;
  646.       StrPLCopy(Name, PChar(AName), SizeOf(Name)-1);
  647.       Inc(FClientCount^);
  648.       Result := Count;
  649.     end;
  650.   finally
  651.     FMutex.Release;
  652.   end;
  653. end;
  654.  
  655. function TClientDirectory.GetCount: Integer;
  656. begin
  657.   Result := FClientCount^;
  658. end;
  659.  
  660. function TClientDirectory.GetClientRec(Index: Integer): TClientDirEntry;
  661. begin
  662.   if (Index > 0) and (Index <= Count) then
  663.     Result := FDirBuffer[Index]
  664.   else
  665.     Error('Invalid client list index');
  666. end;
  667.  
  668. function TClientDirectory.GetClientName(ClientID: Integer): string;
  669. var
  670.   Index: Integer;
  671. begin
  672.   Index := IndexOf(ClientID);
  673.   if Index > 0 then
  674.     Result := FDirBuffer[Index].Name
  675.   else
  676.     Result := '';
  677. end;
  678.  
  679. function TClientDirectory.IndexOf(ClientID: Integer): Integer;
  680. var
  681.   I: Integer;
  682. begin
  683.   for I := 1 to Count do
  684.     if FDirBuffer[I].ID = ClientID then
  685.     begin
  686.       Result := I;
  687.       Exit;
  688.     end;
  689.   Result := -1;
  690. end;
  691.  
  692. function TClientDirectory.Last: Integer;
  693. begin
  694.   if Count > 0 then
  695.     Result := FDirBuffer[Count].ID else
  696.     Result := 0;
  697. end;
  698.  
  699. function TClientDirectory.RemoveClient(ClientID: Integer): Boolean;
  700. var
  701.   Index: Integer;
  702. begin
  703.   Index := IndexOf(ClientID);
  704.   if (Index > -1) and FMutex.Get(TIMEOUT) then
  705.   try
  706.     if (Index > 0) and (Index < Count) then
  707.       Move(FDirBuffer[Index+1], FDirBuffer[Index],
  708.         (Count - Index) * SizeOf(TClientDirEntry));
  709.     Dec(FClientCount^);
  710.     Result := True;
  711.   finally
  712.     FMutex.Release;
  713.   end
  714.   else
  715.     Result := False;
  716. end;
  717.  
  718. function TClientDirectory.GetMonitorID: Integer;
  719. begin
  720.   Result := FMonitorID^;
  721. end;
  722.  
  723. procedure TClientDirectory.SetMonitorID(MonitorID: Integer);
  724. begin
  725.   FMonitorID^ := MonitorID;
  726. end;
  727.  
  728. { TIPCThread }
  729.  
  730. constructor TIPCThread.Create(AID: Integer; const AName: string);
  731. begin
  732.   inherited Create(True);
  733.   FID := AID;
  734.   FName := AName;
  735. {$IFDEF DEBUG}
  736.   if Self is TIPCMonitor then
  737.     FTracer := TIPCTracer.Create(FName)
  738.   else
  739.     FTracer := TIPCTracer.Create(IntToHex(FID, 8));
  740. {$ENDIF}
  741.   FMonitorEvent := TIPCEvent.Create(Self, MONITOR_EVENT_NAME, False);
  742.   FClientEvent := TIPCEvent.Create(Self, CLIENT_EVENT_NAME, False);
  743.   FConnectEvent := TIPCEvent.Create(Self, CONNECT_EVENT_NAME, True);
  744.   FClientDirectory := TClientDirectory.Create(MAX_CLIENTS);
  745. end;
  746.  
  747. destructor TIPCThread.Destroy;
  748. begin
  749.   DeActivate;
  750.   inherited Destroy;
  751.   FClientDirectory.Free;
  752.   FClientEvent.Free;
  753.   FConnectEvent.Free;
  754.   FMonitorEvent.Free;
  755.   FState := stInActive;
  756. {$IFDEF DEBUG}
  757.   FTracer.Free;
  758. {$ENDIF}
  759. end;
  760.  
  761. { This procedure is called all over the place to keep track of what is
  762.   going on }
  763.  
  764. procedure TIPCThread.DbgStr(const S: string);
  765. begin
  766. {$IFDEF DEBUG}
  767.   FTracer.Add(PChar(S));
  768. {$ENDIF}
  769. end;
  770.  
  771. { TIPCMonitor }
  772.  
  773. constructor TIPCMonitor.Create(AID: Integer; const AName: string);
  774. begin
  775.   inherited Create(AID, AName);
  776.   FAutoSwitch := True;
  777. end;
  778.  
  779. procedure TIPCMonitor.Activate;
  780. begin
  781.   if FState = stInActive then
  782.   begin
  783.     { Put the monitor handle into the client directory so we can use it to
  784.       prevent multiple monitors from running }
  785.     if FClientDirectory.MonitorID = 0 then
  786.       FClientDirectory.MonitorID := FID
  787.     else
  788.       raise EMonitorActive.Create('');
  789.     FState := stDisconnected;
  790.     Resume;
  791.   end;
  792. end;
  793.  
  794. procedure TIPCMonitor.DeActivate;
  795. begin
  796.   if (State <> stInActive) and not Suspended then
  797.   begin
  798.     FClientDirectory.MonitorID := 0;
  799.     FMonitorEvent.Signal(evMonitorExit);
  800.     if WaitForSingleObject(Handle, TIMEOUT) <> WAIT_OBJECT_0 then
  801.       TerminateThread(Handle, 0);
  802.   end;
  803. end;
  804.  
  805. { This method, and the TIPCClient.Execute method represent the meat of this
  806.   program.  These two thread handlers are responsible for communcation with
  807.   each other through the IPC event classes }
  808.  
  809. procedure TIPCMonitor.Execute;
  810. var
  811.   WaitResult: Integer;
  812. begin
  813.   DbgStr(FName + ' Activated');
  814.   if FClientDirectory.Count > 0 then
  815.     FMonitorEvent.SignalID(evClientStart, FClientDirectory.Last);
  816.   while True do
  817.   try
  818.     WaitResult := WaitForSingleObject(FMonitorEvent.Handle, INFINITE);
  819.     if WaitResult >= WAIT_ABANDONED then        { Something went wrong }
  820.       DisconnectFromClient(False) else
  821.     if WaitResult = WAIT_OBJECT_0 then          { Monitor Event }
  822.     begin
  823.       DbgStr('Event Signaled: '+EventName(FMonitorEvent.Kind));
  824.       case FMonitorEvent.Kind of
  825.         evClientSignal:
  826.           DoOnSignal;
  827.         evClientStart:
  828.           begin
  829.             if AutoSwitch or (FClientID = 0) then
  830.               ConnectToClient(FMonitorEvent.ID);
  831.             DoOnDirUpdate;
  832.           end;
  833.         evClientStop:
  834.           DoOnDirUpdate;
  835.         evClientDetach:
  836.           begin
  837.             DisconnectFromClient(False);
  838.             Sleep(100);
  839.             if AutoSwitch then
  840.               ConnectToClient(FClientDirectory.Last);
  841.           end;
  842.         evClientSwitch:
  843.           ConnectToClient(FMonitorEvent.ID);
  844.         evMonitorExit:
  845.           begin
  846.             DisconnectFromClient(False);
  847.             break;
  848.           end;
  849.       end;
  850.     end
  851.     else
  852.       DbgStr(Format('Unexpected Wait Return Code: %d', [WaitResult]));
  853.   except
  854.     on E:Exception do
  855.       DbgStr(Format('Exception raised in Thread Handler: %s at %X', [E.Message, ExceptAddr]));
  856.   end;
  857.   FState := stInActive;
  858.   DbgStr('Thread Handler Exited');
  859. end;
  860.  
  861. procedure TIPCMonitor.ConnectToClient(ID: Integer);
  862. begin
  863.   if ID = FClientID then Exit;
  864.   if FState = stConnected then
  865.     DisconnectFromClient(True);
  866.   if ID = 0 then Exit;
  867.   DbgStr(Format('Sending evMonitorAttach: %X', [ID]));
  868.   { Tell a client we want to attach to them }
  869.   FConnectEvent.SignalID(evMonitorAttach, ID);
  870.   { Wait for the client to say "OK" }
  871.   if FMonitorEvent.WaitFor(TIMEOUT, ID, evClientAttach) and
  872.     (FMonitorEvent.Data.Flag = cfAttach) then
  873.   begin
  874.     FClientID := ID;
  875.     FState := stConnected;
  876.     if Assigned(FOnConnect) then FOnConnect(Self, True);
  877.     DbgStr('ConnectToClient Successful');
  878.   end
  879.   else
  880.     DbgStr('ConnectToClient Failed: '+EventName(FMonitorEvent.Kind));
  881. end;
  882.  
  883. { If Wait is true ... }
  884.  
  885. procedure TIPCMonitor.DisconnectFromClient(Wait: Boolean);
  886. begin
  887.   if FState = stConnected then
  888.   begin
  889.     DbgStr(Format('Sending evMonitorDetach: %x', [FClientID]));
  890.     { Tell the client we are detaching }
  891.     FClientEvent.SignalID(evMonitorDetach, FClientID);
  892.     { If we (the monitor) initiated the detach process, then wait around
  893.       for the client to acknowledge the detach, otherwise, just continue on }
  894.     if Wait then
  895.       if not FMonitorEvent.WaitFor(TIMEOUT, FClientID, evClientDetach) then
  896.       begin
  897.         DbgStr(Format('Error waiting for client to detach: %x', [FClientID]));
  898.         FClientDirectory.RemoveClient(FClientID);
  899.       end;
  900.     FClientID := 0;
  901.     FState := stDisconnected;
  902.     if Assigned(FOnConnect) then FOnConnect(Self, False);
  903.     if not Wait and Assigned(FOnDirUpdate) then
  904.       DoOnDirUpdate;
  905.   end;
  906. end;
  907.  
  908. { This method is called when the client has new data for us }
  909.  
  910. procedure TIPCMonitor.DoOnSignal;
  911. begin
  912.   if Assigned(FOnSignal) and (FMonitorEvent.ID = FClientID) then
  913.     FOnSignal(Self, FMonitorEvent.Data);
  914. end;
  915.  
  916. { Tell the client we have new flags for it }
  917.  
  918. procedure TIPCMonitor.SignalClient(const Value: TClientFlags);
  919. begin
  920.   if FState = stConnected then
  921.   begin
  922.     FClientEvent.FEventInfo.FData.Flags := Value;
  923.     DbgStr('Signaling Client');
  924.     FClientEvent.SignalData(evMonitorSignal, FClientID, FClientEvent.Data);
  925.   end;
  926. end;
  927.  
  928. function TIPCMonitor.GetClientName: string;
  929. begin
  930.   Result := FClientDirectory.Name[FClientID];
  931. end;
  932.  
  933. procedure TIPCMonitor.GetClientNames(List: TStrings);
  934. var
  935.   I: Integer;
  936.   S: string;
  937.   DupCnt: Integer;
  938. begin
  939.   List.BeginUpdate;
  940.   try
  941.     List.Clear;
  942.     for I := 1 to FClientDirectory.Count do
  943.       with FClientDirectory.ClientRec[I] do
  944.       begin
  945.         S := Name;
  946.         DupCnt := 1;
  947.         { Number duplicate names so we can distinguish them in the client menu }
  948.         while(List.IndexOf(S) > -1) do
  949.         begin
  950.           Inc(DupCnt);
  951.           S := Format('%s (%d)', [Name, DupCnt]);
  952.         end;
  953.         List.AddObject(S, TObject(ID));
  954.      end;
  955.   finally
  956.     List.EndUpdate;
  957.   end;
  958. end;
  959.  
  960. procedure TIPCMonitor.SetCurrentClient(ID: Integer);
  961. begin
  962.   if ID = 0 then ID := FClientDirectory.Last;
  963.   if ID <> 0 then
  964.     FMonitorEvent.SignalID(evClientSwitch, ID);
  965. end;
  966.  
  967. procedure TIPCMonitor.ClearDebugInfo;
  968. begin
  969. {$IFDEF DEBUG}
  970.   FTracer.Clear;
  971. {$ENDIF}
  972. end;
  973.  
  974. procedure TIPCMonitor.GetDebugInfo(List: TStrings);
  975. begin
  976. {$IFDEF DEBUG}
  977.   FTracer.GetList(List);
  978. {$ELSE}
  979.   List.Add('Debug Tracing Disabled');
  980. {$ENDIF}
  981. end;
  982.  
  983. procedure TIPCMonitor.SaveDebugInfo(const FileName: string);
  984. {$IFDEF DEBUG}
  985. var
  986.   List: TStringList;
  987. begin
  988.   List := TStringList.Create;
  989.   try
  990.     GetDebugInfo(List);
  991.     List.SaveToFile(FileName);
  992.   finally
  993.     List.Free;
  994.   end;
  995. {$ELSE}
  996. begin
  997. {$ENDIF}
  998. end;
  999.  
  1000. procedure TIPCMonitor.DoOnDirUpdate;
  1001. begin
  1002.   if Assigned(FOnDirUpdate) then FOnDirUpdate(Self);
  1003. end;
  1004.  
  1005. { TIPCClient }
  1006.  
  1007. procedure TIPCClient.Activate;
  1008. begin
  1009.   if FState = stInActive then
  1010.   begin
  1011.     FWaitEvent := FConnectEvent;
  1012.     FMonitorEvent.OwnerID := FID;
  1013.     FConnectEvent.OwnerID := FID;
  1014.     FClientEvent.OwnerID := FID;
  1015.     FClientDirectory.AddClient(FID, FName);
  1016.     FState := stDisconnected;
  1017.     Resume;
  1018.   end
  1019. end;
  1020.  
  1021. procedure TIPCClient.DeActivate;
  1022. begin
  1023.   if Assigned(FClientDirectory) then
  1024.     FClientDirectory.RemoveClient(FID);
  1025.   if (FState <> stInActive) and not Suspended then
  1026.   begin
  1027.     FWaitEvent.Signal(evClientExit);
  1028.     if WaitForSingleObject(Handle, TIMEOUT) <> WAIT_OBJECT_0 then
  1029.       TerminateThread(Handle, 0);
  1030.   end;
  1031. end;
  1032.  
  1033. procedure TIPCClient.Execute;
  1034. begin
  1035.   DbgStr(FName + ' Activated');
  1036.   if FClientDirectory.MonitorID <> 0 then
  1037.     FMonitorEvent.SignalID(evClientStart, FID);
  1038.   while True do
  1039.   try
  1040.     if WaitForSingleObject(FWaitEvent.Handle, INFINITE) <> WAIT_OBJECT_0 then Break;
  1041.     if FWaitEvent.ID <> FID then
  1042.     begin
  1043.       Sleep(200);
  1044.       continue;
  1045.     end;
  1046.     DbgStr('Client Event Signaled: '+EventName(FWaitEvent.Kind));
  1047.     case FWaitEvent.Kind of
  1048.       evMonitorSignal: if Assigned(FOnSignal) then FOnSignal(Self, FWaitEvent.Data);
  1049.       evMonitorAttach: ConnectToMonitor;
  1050.       evMonitorDetach:
  1051.         begin
  1052.           DisconnectFromMonitor(False);
  1053.           Sleep(200);
  1054.         end;
  1055.       evClientExit:
  1056.         begin
  1057.           if FClientDirectory.MonitorID <> 0 then
  1058.           begin
  1059.             if FState = stConnected then
  1060.               DisconnectFromMonitor(True)
  1061.             else
  1062.               FMonitorEvent.Signal(evClientStop);
  1063.           end;
  1064.           break;
  1065.         end;
  1066.     end;
  1067.   except
  1068.     on E:Exception do
  1069.       DbgStr(Format('Exception raised in Thread Handler: %s at %X', [E.Message, ExceptAddr]));
  1070.   end;
  1071.   FState := stInActive;
  1072.   DbgStr('Thread Handler Exited');
  1073. end;
  1074.  
  1075. procedure TIPCClient.ConnectToMonitor;
  1076. var
  1077.   Data: TEventData;
  1078. begin
  1079.   DbgStr('ConnectToMonitor Begin');
  1080.   FConnectEvent.Reset;
  1081.   try
  1082.     FState := stConnected;
  1083.     FWaitEvent := FClientEvent;
  1084.     Data.Flag := cfAttach;
  1085.     FMonitorEvent.SignalData(evClientAttach, FID, Data);
  1086.     if Assigned(FOnConnect) then FOnConnect(Self, True);
  1087.   except
  1088.     DbgStr('Exception in ConnectToMonitor: '+Exception(ExceptObject).Message);
  1089.     Data.Flag := cfError;
  1090.     FMonitorEvent.SignalData(evClientAttach, FID, Data);
  1091.   end;
  1092.   DbgStr('ConnectToMonitor End');
  1093. end;
  1094.  
  1095. procedure TIPCClient.DisconnectFromMonitor(Wait: Boolean);
  1096. begin
  1097.   DbgStr('DisconnectFromMonitor Begin');
  1098.   if FState = stConnected then
  1099.   begin
  1100.     if Wait then
  1101.     begin
  1102.       DbgStr('Sending evClientDetach');
  1103.       FMonitorEvent.Signal(evClientDetach);
  1104.       if FClientEvent.WaitFor(TIMEOUT, FID, evMonitorDetach) then
  1105.         DbgStr('Got evMonitorDetach') else
  1106.         DbgStr('Error waiting for evMonitorDetach');
  1107.     end;
  1108.     FState := stDisconnected;
  1109.     FWaitEvent := FConnectEvent;
  1110.     if not Wait then
  1111.     begin
  1112.       DbgStr('DisconnectFromMonitor sending evClientDetach');
  1113.       FMonitorEvent.Signal(evClientDetach);
  1114.     end;
  1115.     if Assigned(FOnConnect) then FOnConnect(Self, False);
  1116.   end;
  1117.   DbgStr('DisconnectFromMonitor End');
  1118. end;
  1119.  
  1120. procedure TIPCClient.SignalMonitor(Data: TEventData);
  1121. begin
  1122.   if FState = stConnected then
  1123.   begin
  1124.     DbgStr('Signaling Monitor');
  1125.     FMonitorEvent.SignalData(evClientSignal, FID, Data);
  1126.   end;
  1127. end;
  1128.  
  1129. function TIPCClient.ClientCount: Integer;
  1130. begin
  1131.   Result := FClientDirectory.Count;
  1132. end;
  1133.  
  1134. procedure TIPCClient.MakeCurrent;
  1135. begin
  1136.   FMonitorEvent.SignalID(evClientStart, FID);
  1137. end;
  1138.  
  1139. end.
  1140.