home *** CD-ROM | disk | FTP | other *** search
/ DOS/V Power Report 1997 August / VPR9708A.ISO / D3TRIAL / INSTALL / DATA.Z / IPCTHRD.PAS < prev    next >
Pascal/Delphi Source File  |  1997-04-24  |  31KB  |  1,138 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: TLargeInteger;
  485. begin
  486.   FMutex.Get(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.LowPart;
  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(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. end;
  632.  
  633. function TClientDirectory.AddClient(ClientID: Integer; const AName: string): Integer;
  634. begin
  635.   Result := -1;
  636.   if Count = FMaxClients then
  637.     Error(Format('Maximum of %d clients allowed', [FMaxClients]));
  638.   if IndexOf(ClientID) > -1 then
  639.     Error('Duplicate client ID');
  640.   if FMutex.Get(TIMEOUT) then
  641.   try
  642.     with FDirBuffer[Count+1] do
  643.     begin
  644.       ID := ClientID;
  645.       StrPLCopy(Name, PChar(AName), SizeOf(Name)-1);
  646.       Inc(FClientCount^);
  647.       Result := Count;
  648.     end;
  649.   finally
  650.     FMutex.Release;
  651.   end;
  652. end;
  653.  
  654. function TClientDirectory.GetCount: Integer;
  655. begin
  656.   Result := FClientCount^;
  657. end;
  658.  
  659. function TClientDirectory.GetClientRec(Index: Integer): TClientDirEntry;
  660. begin
  661.   if (Index > 0) and (Index <= Count) then
  662.     Result := FDirBuffer[Index]
  663.   else
  664.     Error('Invalid client list index');
  665. end;
  666.  
  667. function TClientDirectory.GetClientName(ClientID: Integer): string;
  668. var
  669.   Index: Integer;
  670. begin
  671.   Index := IndexOf(ClientID);
  672.   if Index > 0 then
  673.     Result := FDirBuffer[Index].Name
  674.   else
  675.     Result := '';
  676. end;
  677.  
  678. function TClientDirectory.IndexOf(ClientID: Integer): Integer;
  679. var
  680.   I: Integer;
  681. begin
  682.   for I := 1 to Count do
  683.     if FDirBuffer[I].ID = ClientID then
  684.     begin
  685.       Result := I;
  686.       Exit;
  687.     end;
  688.   Result := -1;
  689. end;
  690.  
  691. function TClientDirectory.Last: Integer;
  692. begin
  693.   if Count > 0 then
  694.     Result := FDirBuffer[Count].ID else
  695.     Result := 0;
  696. end;
  697.  
  698. function TClientDirectory.RemoveClient(ClientID: Integer): Boolean;
  699. var
  700.   Index: Integer;
  701. begin
  702.   Index := IndexOf(ClientID);
  703.   if (Index > -1) and FMutex.Get(TIMEOUT) then
  704.   try
  705.     if (Index > 0) and (Index < Count) then
  706.       Move(FDirBuffer[Index+1], FDirBuffer[Index],
  707.         (Count - Index) * SizeOf(TClientDirEntry));
  708.     Dec(FClientCount^);
  709.     Result := True;
  710.   finally
  711.     FMutex.Release;
  712.   end
  713.   else
  714.     Result := False;
  715. end;
  716.  
  717. function TClientDirectory.GetMonitorID: Integer;
  718. begin
  719.   Result := FMonitorID^;
  720. end;
  721.  
  722. procedure TClientDirectory.SetMonitorID(MonitorID: Integer);
  723. begin
  724.   FMonitorID^ := MonitorID;
  725. end;
  726.  
  727. { TIPCThread }
  728.  
  729. constructor TIPCThread.Create(AID: Integer; const AName: string);
  730. begin
  731.   inherited Create(True);
  732.   FID := AID;
  733.   FName := AName;
  734. {$IFDEF DEBUG}
  735.   if Self is TIPCMonitor then
  736.     FTracer := TIPCTracer.Create(FName)
  737.   else
  738.     FTracer := TIPCTracer.Create(IntToHex(FID, 8));
  739. {$ENDIF}
  740.   FMonitorEvent := TIPCEvent.Create(Self, MONITOR_EVENT_NAME, False);
  741.   FClientEvent := TIPCEvent.Create(Self, CLIENT_EVENT_NAME, False);
  742.   FConnectEvent := TIPCEvent.Create(Self, CONNECT_EVENT_NAME, True);
  743.   FClientDirectory := TClientDirectory.Create(MAX_CLIENTS);
  744. end;
  745.  
  746. destructor TIPCThread.Destroy;
  747. begin
  748.   DeActivate;
  749.   inherited Destroy;
  750.   FClientDirectory.Free;
  751.   FClientEvent.Free;
  752.   FMonitorEvent.Free;
  753.   FState := stInActive;
  754. {$IFDEF DEBUG}
  755.   FTracer.Free;
  756. {$ENDIF}
  757. end;
  758.  
  759. { This procedure is called all over the place to keep track of what is
  760.   going on }
  761.  
  762. procedure TIPCThread.DbgStr(const S: string);
  763. begin
  764. {$IFDEF DEBUG}
  765.   FTracer.Add(PChar(S));
  766. {$ENDIF}
  767. end;
  768.  
  769. { TIPCMonitor }
  770.  
  771. constructor TIPCMonitor.Create(AID: Integer; const AName: string);
  772. begin
  773.   inherited Create(AID, AName);
  774.   FAutoSwitch := True;
  775. end;
  776.  
  777. procedure TIPCMonitor.Activate;
  778. begin
  779.   if FState = stInActive then
  780.   begin
  781.     { Put the monitor handle into the client directory so we can use it to
  782.       prevent multiple monitors from running }
  783.     if FClientDirectory.MonitorID = 0 then
  784.       FClientDirectory.MonitorID := FID
  785.     else
  786.       raise EMonitorActive.Create('');
  787.     FState := stDisconnected;
  788.     Resume;
  789.   end;
  790. end;
  791.  
  792. procedure TIPCMonitor.DeActivate;
  793. begin
  794.   if (State <> stInActive) and not Suspended then
  795.   begin
  796.     FClientDirectory.MonitorID := 0;
  797.     FMonitorEvent.Signal(evMonitorExit);
  798.     if WaitForSingleObject(Handle, TIMEOUT) <> WAIT_OBJECT_0 then
  799.       TerminateThread(Handle, 0);
  800.   end;
  801. end;
  802.  
  803. { This method, and the TIPCClient.Execute method represent the meat of this
  804.   program.  These two thread handlers are responsible for communcation with
  805.   each other through the IPC event classes }
  806.  
  807. procedure TIPCMonitor.Execute;
  808. var
  809.   WaitResult: Integer;
  810. begin
  811.   DbgStr(FName + ' Activated');
  812.   if FClientDirectory.Count > 0 then
  813.     FMonitorEvent.SignalID(evClientStart, FClientDirectory.Last);
  814.   while True do
  815.   try
  816.     WaitResult := WaitForSingleObject(FMonitorEvent.Handle, INFINITE);
  817.     if WaitResult >= WAIT_ABANDONED then        { Something went wrong }
  818.       DisconnectFromClient(False) else
  819.     if WaitResult = WAIT_OBJECT_0 then          { Monitor Event }
  820.     begin
  821.       DbgStr('Event Signaled: '+EventName(FMonitorEvent.Kind));
  822.       case FMonitorEvent.Kind of
  823.         evClientSignal:
  824.           DoOnSignal;
  825.         evClientStart:
  826.           begin
  827.             if AutoSwitch or (FClientID = 0) then
  828.               ConnectToClient(FMonitorEvent.ID);
  829.             DoOnDirUpdate;
  830.           end;
  831.         evClientStop:
  832.           DoOnDirUpdate;
  833.         evClientDetach:
  834.           begin
  835.             DisconnectFromClient(False);
  836.             Sleep(100);
  837.             if AutoSwitch then
  838.               ConnectToClient(FClientDirectory.Last);
  839.           end;
  840.         evClientSwitch:
  841.           ConnectToClient(FMonitorEvent.ID);
  842.         evMonitorExit:
  843.           begin
  844.             DisconnectFromClient(False);
  845.             break;
  846.           end;
  847.       end;
  848.     end
  849.     else
  850.       DbgStr(Format('Unexpected Wait Return Code: %d', [WaitResult]));
  851.   except
  852.     on E:Exception do
  853.       DbgStr(Format('Exception raised in Thread Handler: %s at %X', [E.Message, ExceptAddr]));
  854.   end;
  855.   FState := stInActive;
  856.   DbgStr('Thread Handler Exited');
  857. end;
  858.  
  859. procedure TIPCMonitor.ConnectToClient(ID: Integer);
  860. begin
  861.   if ID = FClientID then Exit;
  862.   if FState = stConnected then
  863.     DisconnectFromClient(True);
  864.   if ID = 0 then Exit;
  865.   DbgStr(Format('Sending evMonitorAttach: %X', [ID]));
  866.   { Tell a client we want to attach to them }
  867.   FConnectEvent.SignalID(evMonitorAttach, ID);
  868.   { Wait for the client to say "OK" }
  869.   if FMonitorEvent.WaitFor(TIMEOUT, ID, evClientAttach) and
  870.     (FMonitorEvent.Data.Flag = cfAttach) then
  871.   begin
  872.     FClientID := ID;
  873.     FState := stConnected;
  874.     if Assigned(FOnConnect) then FOnConnect(Self, True);
  875.     DbgStr('ConnectToClient Successful');
  876.   end
  877.   else
  878.     DbgStr('ConnectToClient Failed: '+EventName(FMonitorEvent.Kind));
  879. end;
  880.  
  881. { If Wait is true ... }
  882.  
  883. procedure TIPCMonitor.DisconnectFromClient(Wait: Boolean);
  884. begin
  885.   if FState = stConnected then
  886.   begin
  887.     DbgStr(Format('Sending evMonitorDetach: %x', [FClientID]));
  888.     { Tell the client we are detaching }
  889.     FClientEvent.SignalID(evMonitorDetach, FClientID);
  890.     { If we (the monitor) initiated the detach process, then wait around
  891.       for the client to acknowledge the detach, otherwise, just continue on }
  892.     if Wait then
  893.       if not FMonitorEvent.WaitFor(TIMEOUT, FClientID, evClientDetach) then
  894.       begin
  895.         DbgStr(Format('Error waiting for client to detach: %x', [FClientID]));
  896.         FClientDirectory.RemoveClient(FClientID);
  897.       end;
  898.     FClientID := 0;
  899.     FState := stDisconnected;
  900.     if Assigned(FOnConnect) then FOnConnect(Self, False);
  901.     if not Wait and Assigned(FOnDirUpdate) then
  902.       DoOnDirUpdate;
  903.   end;
  904. end;
  905.  
  906. { This method is called when the client has new data for us }
  907.  
  908. procedure TIPCMonitor.DoOnSignal;
  909. begin
  910.   if Assigned(FOnSignal) and (FMonitorEvent.ID = FClientID) then
  911.     FOnSignal(Self, FMonitorEvent.Data);
  912. end;
  913.  
  914. { Tell the client we have new flags for it }
  915.  
  916. procedure TIPCMonitor.SignalClient(const Value: TClientFlags);
  917. begin
  918.   if FState = stConnected then
  919.   begin
  920.     FClientEvent.FEventInfo.FData.Flags := Value;
  921.     DbgStr('Signaling Client');
  922.     FClientEvent.SignalData(evMonitorSignal, FClientID, FClientEvent.Data);
  923.   end;
  924. end;
  925.  
  926. function TIPCMonitor.GetClientName: string;
  927. begin
  928.   Result := FClientDirectory.Name[FClientID];
  929. end;
  930.  
  931. procedure TIPCMonitor.GetClientNames(List: TStrings);
  932. var
  933.   I: Integer;
  934.   S: string;
  935.   DupCnt: Integer;
  936. begin
  937.   List.BeginUpdate;
  938.   try
  939.     List.Clear;
  940.     for I := 1 to FClientDirectory.Count do
  941.       with FClientDirectory.ClientRec[I] do
  942.       begin
  943.         S := Name;
  944.         DupCnt := 1;
  945.         { Number duplicate names so we can distinguish them in the client menu }
  946.         while(List.IndexOf(S) > -1) do
  947.         begin
  948.           Inc(DupCnt);
  949.           S := Format('%s (%d)', [Name, DupCnt]);
  950.         end;
  951.         List.AddObject(S, TObject(ID));
  952.      end;
  953.   finally
  954.     List.EndUpdate;
  955.   end;
  956. end;
  957.  
  958. procedure TIPCMonitor.SetCurrentClient(ID: Integer);
  959. begin
  960.   if ID = 0 then ID := FClientDirectory.Last;
  961.   if ID <> 0 then
  962.     FMonitorEvent.SignalID(evClientSwitch, ID);
  963. end;
  964.  
  965. procedure TIPCMonitor.ClearDebugInfo;
  966. begin
  967. {$IFDEF DEBUG}
  968.   FTracer.Clear;
  969. {$ENDIF}
  970. end;
  971.  
  972. procedure TIPCMonitor.GetDebugInfo(List: TStrings);
  973. begin
  974. {$IFDEF DEBUG}
  975.   FTracer.GetList(List);
  976. {$ELSE}
  977.   List.Add('Debug Tracing Disabled');
  978. {$ENDIF}
  979. end;
  980.  
  981. procedure TIPCMonitor.SaveDebugInfo(const FileName: string);
  982. {$IFDEF DEBUG}
  983. var
  984.   List: TStringList;
  985. begin
  986.   List := TStringList.Create;
  987.   try
  988.     GetDebugInfo(List);
  989.     List.SaveToFile(FileName);
  990.   finally
  991.     List.Free;
  992.   end;
  993. {$ELSE}
  994. begin
  995. {$ENDIF}
  996. end;
  997.  
  998. procedure TIPCMonitor.DoOnDirUpdate;
  999. begin
  1000.   if Assigned(FOnDirUpdate) then FOnDirUpdate(Self);
  1001. end;
  1002.  
  1003. { TIPCClient }
  1004.  
  1005. procedure TIPCClient.Activate;
  1006. begin
  1007.   if FState = stInActive then
  1008.   begin
  1009.     FWaitEvent := FConnectEvent;
  1010.     FMonitorEvent.OwnerID := FID;
  1011.     FConnectEvent.OwnerID := FID;
  1012.     FClientEvent.OwnerID := FID;
  1013.     FClientDirectory.AddClient(FID, FName);
  1014.     FState := stDisconnected;
  1015.     Resume;
  1016.   end
  1017. end;
  1018.  
  1019. procedure TIPCClient.DeActivate;
  1020. begin
  1021.   if Assigned(FClientDirectory) then
  1022.     FClientDirectory.RemoveClient(FID);
  1023.   if (FState <> stInActive) and not Suspended then
  1024.   begin
  1025.     FWaitEvent.Signal(evClientExit);
  1026.     if WaitForSingleObject(Handle, TIMEOUT) <> WAIT_OBJECT_0 then
  1027.       TerminateThread(Handle, 0);
  1028.   end;
  1029. end;
  1030.  
  1031. procedure TIPCClient.Execute;
  1032. begin
  1033.   DbgStr(FName + ' Activated');
  1034.   if FClientDirectory.MonitorID <> 0 then
  1035.     FMonitorEvent.SignalID(evClientStart, FID);
  1036.   while True do
  1037.   try
  1038.     if WaitForSingleObject(FWaitEvent.Handle, INFINITE) <> WAIT_OBJECT_0 then Break;
  1039.     if FWaitEvent.ID <> FID then
  1040.     begin
  1041.       Sleep(200);
  1042.       continue;
  1043.     end;
  1044.     DbgStr('Client Event Signaled: '+EventName(FWaitEvent.Kind));
  1045.     case FWaitEvent.Kind of
  1046.       evMonitorSignal: if Assigned(FOnSignal) then FOnSignal(Self, FWaitEvent.Data);
  1047.       evMonitorAttach: ConnectToMonitor;
  1048.       evMonitorDetach:
  1049.         begin
  1050.           DisconnectFromMonitor(False);
  1051.           Sleep(200);
  1052.         end;
  1053.       evClientExit:
  1054.         begin
  1055.           if FClientDirectory.MonitorID <> 0 then
  1056.           begin
  1057.             if FState = stConnected then
  1058.               DisconnectFromMonitor(True)
  1059.             else
  1060.               FMonitorEvent.Signal(evClientStop);
  1061.           end;
  1062.           break;
  1063.         end;
  1064.     end;
  1065.   except
  1066.     on E:Exception do
  1067.       DbgStr(Format('Exception raised in Thread Handler: %s at %X', [E.Message, ExceptAddr]));
  1068.   end;
  1069.   FState := stInActive;
  1070.   DbgStr('Thread Handler Exited');
  1071. end;
  1072.  
  1073. procedure TIPCClient.ConnectToMonitor;
  1074. var
  1075.   Data: TEventData;
  1076. begin
  1077.   DbgStr('ConnectToMonitor Begin');
  1078.   FConnectEvent.Reset;
  1079.   try
  1080.     FState := stConnected;
  1081.     FWaitEvent := FClientEvent;
  1082.     Data.Flag := cfAttach;
  1083.     FMonitorEvent.SignalData(evClientAttach, FID, Data);
  1084.     if Assigned(FOnConnect) then FOnConnect(Self, True);
  1085.   except
  1086.     DbgStr('Exception in ConnectToMonitor: '+Exception(ExceptObject).Message);
  1087.     Data.Flag := cfError;
  1088.     FMonitorEvent.SignalData(evClientAttach, FID, Data);
  1089.   end;
  1090.   DbgStr('ConnectToMonitor End');
  1091. end;
  1092.  
  1093. procedure TIPCClient.DisconnectFromMonitor(Wait: Boolean);
  1094. begin
  1095.   DbgStr('DisconnectFromMonitor Begin');
  1096.   if FState = stConnected then
  1097.   begin
  1098.     if Wait then
  1099.     begin
  1100.       DbgStr('Sending evClientDetach');
  1101.       FMonitorEvent.Signal(evClientDetach);
  1102.       if FClientEvent.WaitFor(TIMEOUT, FID, evMonitorDetach) then
  1103.         DbgStr('Got evMonitorDetach') else
  1104.         DbgStr('Error waiting for evMonitorDetach');
  1105.     end;
  1106.     FState := stDisconnected;
  1107.     FWaitEvent := FConnectEvent;
  1108.     if not Wait then
  1109.     begin
  1110.       DbgStr('DisconnectFromMonitor sending evClientDetach');
  1111.       FMonitorEvent.Signal(evClientDetach);
  1112.     end;
  1113.     if Assigned(FOnConnect) then FOnConnect(Self, False);
  1114.   end;
  1115.   DbgStr('DisconnectFromMonitor End');
  1116. end;
  1117.  
  1118. procedure TIPCClient.SignalMonitor(Data: TEventData);
  1119. begin
  1120.   if FState = stConnected then
  1121.   begin
  1122.     DbgStr('Signaling Monitor');
  1123.     FMonitorEvent.SignalData(evClientSignal, FID, Data);
  1124.   end;
  1125. end;
  1126.  
  1127. function TIPCClient.ClientCount: Integer;
  1128. begin
  1129.   Result := FClientDirectory.Count;
  1130. end;
  1131.  
  1132. procedure TIPCClient.MakeCurrent;
  1133. begin
  1134.   FMonitorEvent.SignalID(evClientStart, FID);
  1135. end;
  1136.  
  1137. end.
  1138.