simpleipc.pp 29 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165
  1. {
  2. This file is part of the Free Component library.
  3. Copyright (c) 2005 by Michael Van Canneyt, member of
  4. the Free Pascal development team
  5. Unit implementing one-way IPC between 2 processes
  6. See the file COPYING.FPC, included in this distribution,
  7. for details about the copyright.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  11. **********************************************************************}
  12. unit simpleipc;
  13. {$mode objfpc}{$H+}
  14. interface
  15. uses
  16. Contnrs, SyncObjs, Classes, SysUtils;
  17. const
  18. MsgVersion = 1;
  19. { IPC message types }
  20. mtUnknown = 0;
  21. mtString = 1;
  22. type
  23. TIPCMessageOverflowAction = (ipcmoaNone, ipcmoaDiscardOld, ipcmoaDiscardNew, ipcmoaError);
  24. TMessageType = LongInt;
  25. TMsgHeader = Packed record
  26. Version : Byte;
  27. MsgType : TMessageType;
  28. MsgLen : Integer;
  29. end;
  30. TSimpleIPCServer = class;
  31. TSimpleIPCClient = class;
  32. { TIPCServerMsg }
  33. TIPCServerMsg = class
  34. private type
  35. TStreamClass = class of TStream;
  36. private const
  37. // TMemoryStream uses an effecient grow algorithm.
  38. DefaultStreamClass: TStreamClass = TMemoryStream;
  39. strict private
  40. FStream: TStream;
  41. FOwnsStream: Boolean;
  42. FMsgType: TMessageType;
  43. function GetStringMessage: String;
  44. public
  45. constructor Create;
  46. constructor Create(AStream: TStream; AOwnsStream: Boolean = True);
  47. destructor Destroy; override;
  48. property Stream: TStream read FStream;
  49. property MsgType: TMessageType read FMsgType write FMsgType;
  50. property OwnsStream: Boolean read FOwnsStream write FOwnsStream;
  51. property StringMessage: String read GetStringMessage;
  52. end;
  53. { TIPCServerMsgQueue }
  54. TIPCServerMsgQueue = class
  55. strict private
  56. FList: TFPObjectList;
  57. FMaxCount: Integer;
  58. FMaxAction: TIPCMessageOverflowAction;
  59. function GetCount: Integer;
  60. procedure DeleteAndFree(Index: Integer);
  61. function PrepareToPush: Boolean;
  62. public
  63. constructor Create;
  64. destructor Destroy; override;
  65. procedure Clear;
  66. procedure Push(AItem: TIPCServerMsg);
  67. function Pop: TIPCServerMsg;
  68. property Count: Integer read GetCount;
  69. property MaxCount: Integer read FMaxCount write FMaxCount;
  70. property MaxAction: TIPCMessageOverflowAction read FMaxAction write FMaxAction;
  71. end;
  72. { TIPCServerComm }
  73. TIPCServerComm = Class(TObject)
  74. Private
  75. FOwner : TSimpleIPCServer;
  76. Protected
  77. Function GetInstanceID : String; virtual; abstract;
  78. Procedure DoError(const Msg : String; const Args : Array of const);
  79. Procedure PushMessage(Const Hdr : TMsgHeader; AStream : TStream);
  80. Procedure PushMessage(Msg : TIPCServerMsg);
  81. Public
  82. Constructor Create(AOwner : TSimpleIPCServer); virtual;
  83. Property Owner : TSimpleIPCServer read FOwner;
  84. Procedure StartServer; virtual; Abstract;
  85. Procedure StopServer;virtual; Abstract;
  86. // Check for new messages, may read and push messages to the queue.
  87. Function PeekMessage(Timeout: Integer): Boolean; virtual; Abstract;
  88. // Read and push new message to the queue, if not done by PeekMessage.
  89. Procedure ReadMessage; virtual; Abstract;
  90. Property InstanceID : String read GetInstanceID;
  91. end;
  92. TIPCServerCommClass = Class of TIPCServerComm;
  93. { TSimpleIPC }
  94. TSimpleIPC = Class(TComponent)
  95. Private
  96. procedure SetActive(const AValue: Boolean);
  97. procedure SetServerID(const AValue: String);
  98. Protected
  99. FBusy: Boolean;
  100. FActive : Boolean;
  101. FServerID : String;
  102. procedure PrepareServerID;
  103. Procedure DoError(const Msg: String; const Args: array of const);
  104. Procedure CheckInactive;
  105. Procedure CheckActive;
  106. Procedure Activate; virtual; abstract;
  107. Procedure Deactivate; virtual; abstract;
  108. Procedure Loaded; override;
  109. Property Busy : Boolean Read FBusy;
  110. Published
  111. Property Active : Boolean Read FActive Write SetActive;
  112. Property ServerID : String Read FServerID Write SetServerID;
  113. end;
  114. TMessageQueueEvent = Procedure(Sender: TObject; Msg: TIPCServerMsg) of object;
  115. { TSimpleIPCServer }
  116. TSimpleIPCServer = Class(TSimpleIPC)
  117. private const
  118. DefaultThreaded = False;
  119. DefaultThreadTimeout = 50;
  120. DefaultSynchronizeEvents = True;
  121. DefaultMaxAction = ipcmoaNone;
  122. DefaultMaxQueue = 0;
  123. private
  124. FOnMessageError: TMessageQueueEvent;
  125. FOnMessageQueued: TNotifyEvent;
  126. FOnMessage: TNotifyEvent;
  127. FOnThreadError: TNotifyEvent;
  128. FQueue: TIPCServerMsgQueue;
  129. FQueueLock: TCriticalSection;
  130. FQueueAddEvent: TSimpleEvent;
  131. FGlobal: Boolean;
  132. // Access to the message is not locked by design!
  133. // In the threaded mode, it must be accessed only during event callbacks.
  134. FMessage: TIPCServerMsg;
  135. FTempMessage: TIPCServerMsg;
  136. FThreaded: Boolean;
  137. FThreadTimeout: Integer;
  138. FThreadError: String;
  139. FThreadExecuting: Boolean;
  140. FThreadReadyEvent: TSimpleEvent;
  141. FThread: TThread;
  142. FSynchronizeEvents: Boolean;
  143. procedure DoOnMessage;
  144. procedure DoOnMessageQueued;
  145. procedure DoOnMessageError(Msg: TIPCServerMsg);
  146. procedure DoOnThreadError;
  147. procedure InternalDoOnMessage;
  148. procedure InternalDoOnMessageQueued;
  149. procedure InternalDoOnMessageError;
  150. procedure InternalDoOnThreadError;
  151. function GetInstanceID: String;
  152. function GetMaxAction: TIPCMessageOverflowAction;
  153. function GetMaxQueue: Integer;
  154. function GetStringMessage: String;
  155. procedure SetGlobal(const AValue: Boolean);
  156. procedure SetMaxAction(AValue: TIPCMessageOverflowAction);
  157. procedure SetMaxQueue(AValue: Integer);
  158. procedure SetThreaded(AValue: Boolean);
  159. procedure SetThreadTimeout(AValue: Integer);
  160. procedure SetSynchronizeEvents(AValue: Boolean);
  161. function WaitForReady(Timeout: Integer = -1): Boolean;
  162. function GetMsgType: TMessageType;
  163. function GetMsgData: TStream;
  164. protected
  165. FIPCComm: TIPCServerComm;
  166. Function CommClass : TIPCServerCommClass; virtual;
  167. Procedure PushMessage(Msg : TIPCServerMsg); virtual;
  168. function PopMessage: Boolean; virtual;
  169. procedure StartComm; virtual;
  170. procedure StopComm; virtual;
  171. function StartThread: Boolean; virtual;
  172. procedure StopThread; virtual;
  173. Procedure Activate; override;
  174. Procedure Deactivate; override;
  175. function ProcessMessage(Timeout: Integer): Boolean;
  176. Property Queue : TIPCServerMsgQueue Read FQueue;
  177. Property Thread : TThread Read FThread;
  178. Public
  179. Constructor Create(AOwner : TComponent); override;
  180. Destructor Destroy; override;
  181. Procedure StartServer;
  182. Procedure StartServer(AThreaded: Boolean);
  183. Procedure StopServer;
  184. Function PeekMessage(Timeout: Integer; DoReadMessage: Boolean): Boolean;
  185. Function ReadMessage: Boolean;
  186. Property StringMessage : String Read GetStringMessage;
  187. Procedure GetMessageData(Stream : TStream);
  188. Property Message: TIPCServerMsg read FMessage;
  189. Property MsgType: TMessageType Read GetMsgType;
  190. Property MsgData: TStream Read GetMsgData;
  191. Property InstanceID : String Read GetInstanceID;
  192. property ThreadExecuting: Boolean read FThreadExecuting;
  193. property ThreadError: String read FThreadError;
  194. Published
  195. Property Global : Boolean Read FGlobal Write SetGlobal;
  196. // Called during ReadMessage
  197. Property OnMessage : TNotifyEvent Read FOnMessage Write FOnMessage;
  198. // Called when a message is pushed on the queue.
  199. Property OnMessageQueued : TNotifyEvent Read FOnMessageQueued Write FOnMessageQueued;
  200. // Called when the queue overflows and MaxAction = ipcmoaError.
  201. Property OnMessageError : TMessageQueueEvent Read FOnMessageError Write FOnMessageError;
  202. // Called when the server thread catches an exception.
  203. property OnThreadError: TNotifyEvent read FOnThreadError write FOnThreadError;
  204. // Maximum number of messages to keep in the queue
  205. property MaxQueue: Integer read GetMaxQueue write SetMaxQueue default DefaultMaxQueue;
  206. // What to do when the queue overflows
  207. property MaxAction: TIPCMessageOverflowAction read GetMaxAction write SetMaxAction default DefaultMaxAction;
  208. // Instruct IPC server to operate in a threaded mode.
  209. property Threaded: Boolean read FThreaded write SetThreaded;
  210. // Amount of time thread waits for a message before checking for termination.
  211. property ThreadTimeout: Integer read FThreadTimeout write SetThreadTimeout default DefaultThreadTimeout;
  212. // Synchronize events with the main thread when in threaded mode.
  213. property SynchronizeEvents: Boolean read FSynchronizeEvents write SetSynchronizeEvents default DefaultSynchronizeEvents;
  214. end;
  215. { TIPCClientComm }
  216. TIPCClientComm = Class(TObject)
  217. private
  218. FOwner: TSimpleIPCClient;
  219. protected
  220. Procedure DoError(const Msg : String; const Args : Array of const);
  221. Public
  222. Constructor Create(AOwner : TSimpleIPCClient); virtual;
  223. Property Owner : TSimpleIPCClient read FOwner;
  224. Procedure Connect; virtual; abstract;
  225. Procedure Disconnect; virtual; abstract;
  226. Function ServerRunning : Boolean; virtual; abstract;
  227. Procedure SendMessage(MsgType : TMessageType; Stream : TStream);virtual;Abstract;
  228. end;
  229. TIPCClientCommClass = Class of TIPCClientComm;
  230. { TSimpleIPCClient }
  231. TSimpleIPCClient = Class(TSimpleIPC)
  232. Private
  233. FServerInstance: String;
  234. procedure SetServerInstance(const AValue: String);
  235. Protected
  236. FIPCComm : TIPCClientComm;
  237. Procedure Activate; override;
  238. Procedure Deactivate; override;
  239. Function CommClass : TIPCClientCommClass; virtual;
  240. Public
  241. Constructor Create(AOwner : TComponent); override;
  242. Destructor Destroy; override;
  243. Procedure Connect;
  244. Procedure Disconnect;
  245. Function ServerRunning : Boolean;
  246. Procedure SendMessage(MsgType : TMessageType; Stream: TStream);
  247. Procedure SendStringMessage(const Msg : String);
  248. Procedure SendStringMessage(MsgType : TMessageType; const Msg : String);
  249. Procedure SendStringMessageFmt(const Msg : String; Args : Array of const);
  250. Procedure SendStringMessageFmt(MsgType : TMessageType; const Msg : String; Args : Array of const);
  251. Property ServerInstance : String Read FServerInstance Write SetServerInstance;
  252. end;
  253. EIPCError = Class(Exception);
  254. var
  255. DefaultIPCServerClass : TIPCServerCommClass = Nil;
  256. DefaultIPCClientClass : TIPCClientCommClass = Nil;
  257. var
  258. DefaultIPCMessageOverflowAction: TIPCMessageOverflowAction = TSimpleIPCServer.DefaultMaxAction;
  259. DefaultIPCMessageQueueLimit: Integer = TSimpleIPCServer.DefaultMaxQueue;
  260. resourcestring
  261. SErrServerNotActive = 'Server with ID %s is not active.';
  262. SErrActive = 'This operation is illegal when the server is active.';
  263. SErrInActive = 'This operation is illegal when the server is inactive.';
  264. SErrThreadContext = 'This operation is illegal outside of IPC thread context.';
  265. SErrThreadFailure = 'IPC thread failure.';
  266. SErrMessageQueueOverflow = 'Message queue overflow (limit %s)';
  267. implementation
  268. { ---------------------------------------------------------------------
  269. Include platform specific implementation.
  270. Should implement the CommClass method of both server and client component,
  271. as well as the communication class itself.
  272. This comes first, to allow the uses clause to be set.
  273. If the include file defines OSNEEDIPCINITDONE then the unit will
  274. call IPCInit and IPCDone in the initialization/finalization code.
  275. --------------------------------------------------------------------- }
  276. {$UNDEF OSNEEDIPCINITDONE}
  277. {$i simpleipc.inc}
  278. // Convert content of any stream type to a string.
  279. function FastStreamToString(Stream: TStream): String;
  280. var
  281. CharCount, CharSize: Integer;
  282. StringStream: TStringStream;
  283. OldPosition: Int64;
  284. begin
  285. // Optimized for TStringStream
  286. if Stream is TStringStream then
  287. begin
  288. Result := TStringStream(Stream).DataString;
  289. end
  290. // Optimized for TCustomMemoryStream
  291. else if Stream is TCustomMemoryStream then
  292. begin
  293. Result := '';
  294. CharSize := StringElementSize(Result);
  295. CharCount := Stream.Size div CharSize;
  296. SetLength(Result, CharCount);
  297. Move(TCustomMemoryStream(Stream).Memory^, Result[1], CharCount * CharSize);
  298. end
  299. // Any other stream type
  300. else
  301. begin
  302. OldPosition := Stream.Position;
  303. try
  304. StringStream := TStringStream.Create('');
  305. try
  306. Stream.Position := 0;
  307. StringStream.CopyFrom(Stream, Stream.Size);
  308. Result := StringStream.DataString;
  309. finally
  310. StringStream.Free;
  311. end;
  312. finally
  313. Stream.Position := OldPosition;
  314. end;
  315. end;
  316. end;
  317. // Timeout values:
  318. // > 0 -- Number of milliseconds to wait
  319. // = 0 -- return immediately
  320. // = -1 -- wait infinitely (converted to INFINITE)
  321. // < -1 -- wait infinitely (converted to INFINITE)
  322. function IPCTimeoutToEventTimeout(Timeout: Integer): Cardinal; inline;
  323. begin
  324. if Timeout >= 0 then
  325. Result := Timeout
  326. else
  327. Result := SyncObjs.INFINITE;
  328. end;
  329. // Timeout values:
  330. // > 0 -- Number of milliseconds to wait
  331. // = 0 -- return immediately
  332. // = -1 -- wait infinitely
  333. // < -1 -- wait infinitely (force to -1)
  334. function IPCTimeoutSanitized(Timeout: Integer): Integer; inline;
  335. begin
  336. if Timeout >= 0 then
  337. Result := Timeout
  338. else
  339. Result := -1;
  340. end;
  341. {$REGION 'TIPCServerMsg'}
  342. constructor TIPCServerMsg.Create;
  343. begin
  344. FMsgType := mtUnknown;
  345. FStream := Self.DefaultStreamClass.Create;
  346. FOwnsStream := True;
  347. end;
  348. constructor TIPCServerMsg.Create(AStream: TStream; AOwnsStream: Boolean);
  349. begin
  350. FMsgType := mtUnknown;
  351. FStream := AStream;
  352. FOwnsStream := AOwnsStream;
  353. end;
  354. destructor TIPCServerMsg.Destroy;
  355. begin
  356. if FOwnsStream then
  357. FreeAndNil(FStream);
  358. end;
  359. function TIPCServerMsg.GetStringMessage: String;
  360. begin
  361. Result := FastStreamToString(FStream);
  362. end;
  363. {$ENDREGION}
  364. {$REGION 'TIPCServerMsgQueue'}
  365. constructor TIPCServerMsgQueue.Create;
  366. begin
  367. FMaxCount := DefaultIPCMessageQueueLimit;
  368. FMaxAction := DefaultIPCMessageOverflowAction;
  369. FList := TFPObjectList.Create(False); // FreeObjects = False!
  370. end;
  371. destructor TIPCServerMsgQueue.Destroy;
  372. begin
  373. Clear;
  374. FList.Free;
  375. Inherited;
  376. end;
  377. procedure TIPCServerMsgQueue.Clear;
  378. begin
  379. while FList.Count > 0 do
  380. DeleteAndFree(FList.Count - 1);
  381. end;
  382. procedure TIPCServerMsgQueue.DeleteAndFree(Index: Integer);
  383. begin
  384. FList[Index].Free; // Free objects manually!
  385. FList.Delete(Index);
  386. end;
  387. function TIPCServerMsgQueue.GetCount: Integer;
  388. begin
  389. Result := FList.Count;
  390. end;
  391. function TIPCServerMsgQueue.PrepareToPush: Boolean;
  392. begin
  393. Result := True;
  394. case FMaxAction of
  395. ipcmoaDiscardOld:
  396. begin
  397. while (FList.Count >= FMaxCount) do
  398. DeleteAndFree(FList.Count - 1);
  399. end;
  400. ipcmoaDiscardNew:
  401. begin
  402. Result := (FList.Count < FMaxCount);
  403. end;
  404. ipcmoaError:
  405. begin
  406. if (FList.Count >= FMaxCount) then
  407. // Caller is expected to catch this exception, so not using Owner.DoError()
  408. raise EIPCError.CreateFmt(SErrMessageQueueOverflow, [IntToStr(FMaxCount)]);
  409. end;
  410. end;
  411. end;
  412. procedure TIPCServerMsgQueue.Push(AItem: TIPCServerMsg);
  413. begin
  414. // PrepareToPush may throw an exception, e.g. if message queue is full.
  415. if PrepareToPush then
  416. FList.Insert(0, AItem);
  417. end;
  418. function TIPCServerMsgQueue.Pop: TIPCServerMsg;
  419. var
  420. Index: Integer;
  421. begin
  422. Index := FList.Count - 1;
  423. if Index >= 0 then
  424. begin
  425. // Caller is responsible for freeing the object.
  426. Result := TIPCServerMsg(FList[Index]);
  427. FList.Delete(Index);
  428. end
  429. else
  430. Result := nil;
  431. end;
  432. {$ENDREGION}
  433. {$REGION 'TIPCServerComm'}
  434. constructor TIPCServerComm.Create(AOwner: TSimpleIPCServer);
  435. begin
  436. FOwner:=AOWner;
  437. end;
  438. procedure TIPCServerComm.DoError(const Msg: String; const Args: array of const);
  439. begin
  440. FOwner.DoError(Msg,Args);
  441. end;
  442. procedure TIPCServerComm.PushMessage(const Hdr: TMsgHeader; AStream: TStream);
  443. var
  444. M : TIPCServerMsg;
  445. begin
  446. M:=TIPCServerMsg.Create;
  447. try
  448. M.MsgType:=Hdr.MsgType;
  449. if Hdr.MsgLen>0 then
  450. M.Stream.CopyFrom(AStream,Hdr.MsgLen);
  451. except
  452. M.Free;
  453. Raise;
  454. end;
  455. PushMessage(M);
  456. end;
  457. procedure TIPCServerComm.PushMessage(Msg: TIPCServerMsg);
  458. begin
  459. FOwner.PushMessage(Msg);
  460. end;
  461. {$ENDREGION}
  462. {$REGION 'TIPCClientComm'}
  463. constructor TIPCClientComm.Create(AOwner: TSimpleIPCClient);
  464. begin
  465. FOwner:=AOwner;
  466. end;
  467. Procedure TIPCClientComm.DoError(const Msg : String; const Args : Array of const);
  468. begin
  469. FOwner.DoError(Msg,Args);
  470. end;
  471. {$ENDREGION}
  472. {$REGION 'TSimpleIPC'}
  473. Procedure TSimpleIPC.DoError(const Msg: String; const Args: array of const);
  474. var
  475. FullMsg: String;
  476. begin
  477. if Length(Name) > 0
  478. then FullMsg := Name + ': '
  479. else FullMsg := '';
  480. FullMsg := FullMsg + Format(Msg, Args);
  481. raise EIPCError.Create(FullMsg);
  482. end;
  483. procedure TSimpleIPC.CheckInactive;
  484. begin
  485. if not (csLoading in ComponentState) then
  486. If Active then
  487. DoError(SErrActive,[]);
  488. end;
  489. procedure TSimpleIPC.CheckActive;
  490. begin
  491. if not (csLoading in ComponentState) then
  492. If Not Active then
  493. DoError(SErrInActive,[]);
  494. end;
  495. procedure TSimpleIPC.SetActive(const AValue: Boolean);
  496. begin
  497. if (FActive<>AValue) then
  498. begin
  499. if ([]<>([csLoading,csDesigning]*ComponentState)) then
  500. FActive:=AValue
  501. else
  502. If AValue then
  503. Activate
  504. else
  505. Deactivate;
  506. end;
  507. end;
  508. procedure TSimpleIPC.SetServerID(const AValue: String);
  509. begin
  510. if (FServerID<>AValue) then
  511. begin
  512. CheckInactive;
  513. FServerID:=AValue;
  514. end;
  515. end;
  516. procedure TSimpleIPC.PrepareServerID;
  517. begin
  518. if FServerID = '' then
  519. FServerID := ApplicationName;
  520. // Extra precaution for thread-safety
  521. UniqueString(FServerID);
  522. end;
  523. procedure TSimpleIPC.Loaded;
  524. var
  525. B : Boolean;
  526. begin
  527. inherited;
  528. B:=FActive;
  529. if B then
  530. begin
  531. FActive:=False;
  532. Activate;
  533. end;
  534. end;
  535. {$ENDREGION}
  536. {$REGION 'TIPCServerThread'}
  537. type
  538. TIPCServerThread = class(TThread)
  539. private
  540. FServer: TSimpleIPCServer;
  541. protected
  542. procedure Execute; override;
  543. public
  544. constructor Create(AServer: TSimpleIPCServer);
  545. property Server: TSimpleIPCServer read FServer;
  546. end;
  547. constructor TIPCServerThread.Create(AServer: TSimpleIPCServer);
  548. begin
  549. inherited Create(True); // CreateSuspended = True
  550. FServer := AServer;
  551. end;
  552. procedure TIPCServerThread.Execute;
  553. begin
  554. FServer.FThreadExecuting := True;
  555. try
  556. FServer.StartComm;
  557. try
  558. // Notify server that thread has started.
  559. FServer.FThreadReadyEvent.SetEvent;
  560. // Run message loop
  561. while not Terminated do
  562. FServer.ProcessMessage(FServer.ThreadTimeout);
  563. finally
  564. FServer.StopComm;
  565. end;
  566. except on E: Exception do
  567. begin
  568. FServer.FThreadExecuting := False;
  569. FServer.FThreadError := E.Message;
  570. // Trigger event to wake up the caller from potentially indefinite wait.
  571. FServer.FThreadReadyEvent.SetEvent;
  572. FServer.DoOnThreadError;
  573. end;
  574. end;
  575. FServer.FThreadExecuting := False;
  576. end;
  577. {$ENDREGION}
  578. {$REGION 'TSimpleIPCServer'}
  579. constructor TSimpleIPCServer.Create(AOwner: TComponent);
  580. begin
  581. inherited Create(AOwner);
  582. FGlobal:=False;
  583. FActive:=False;
  584. FBusy:=False;
  585. FMessage:=nil;
  586. FQueue:=TIPCServerMsgQueue.Create;
  587. FThreaded:=DefaultThreaded;
  588. FThreadTimeout:=DefaultThreadTimeout;
  589. FSynchronizeEvents:=DefaultSynchronizeEvents;
  590. end;
  591. destructor TSimpleIPCServer.Destroy;
  592. begin
  593. Active:=False;
  594. FreeAndNil(FQueue);
  595. if Assigned(FMessage) then
  596. FreeAndNil(FMessage);
  597. inherited Destroy;
  598. end;
  599. procedure TSimpleIPCServer.SetGlobal(const AValue: Boolean);
  600. begin
  601. CheckInactive;
  602. FGlobal:=AValue;
  603. end;
  604. procedure TSimpleIPCServer.SetThreaded(AValue: Boolean);
  605. begin
  606. CheckInactive;
  607. FThreaded:=AValue;
  608. end;
  609. procedure TSimpleIPCServer.SetThreadTimeout(AValue: Integer);
  610. begin
  611. CheckInactive;
  612. FThreadTimeout:=AValue;
  613. end;
  614. procedure TSimpleIPCServer.SetSynchronizeEvents(AValue: Boolean);
  615. begin
  616. CheckInactive;
  617. FSynchronizeEvents:=AValue;
  618. end;
  619. procedure TSimpleIPCServer.SetMaxAction(AValue: TIPCMessageOverflowAction);
  620. begin
  621. CheckInactive;
  622. FQueue.MaxAction:=AValue;
  623. end;
  624. procedure TSimpleIPCServer.SetMaxQueue(AValue: Integer);
  625. begin
  626. CheckInactive;
  627. FQueue.MaxCount:=AValue;
  628. end;
  629. function TSimpleIPCServer.GetInstanceID: String;
  630. begin
  631. Result:=FIPCComm.InstanceID;
  632. end;
  633. function TSimpleIPCServer.GetMaxAction: TIPCMessageOverflowAction;
  634. begin
  635. Result:=FQueue.MaxAction;
  636. end;
  637. function TSimpleIPCServer.GetMaxQueue: Integer;
  638. begin
  639. Result:=FQueue.MaxCount;
  640. end;
  641. procedure TSimpleIPCServer.StartComm;
  642. begin
  643. if Assigned(FIPCComm) then
  644. FreeAndNil(FIPCComm);
  645. FIPCComm := CommClass.Create(Self);
  646. FIPCComm.StartServer;
  647. end;
  648. procedure TSimpleIPCServer.StopComm;
  649. begin
  650. if Assigned(FIPCComm) then
  651. begin
  652. FIPCComm.StopServer;
  653. FreeAndNil(FIPCComm);
  654. end;
  655. end;
  656. function TSimpleIPCServer.StartThread: Boolean;
  657. begin
  658. FThreadError := '';
  659. FQueueLock := SyncObjs.TCriticalSection.Create;
  660. FQueueAddEvent := SyncObjs.TSimpleEvent.Create;
  661. FThreadReadyEvent := SyncObjs.TSimpleEvent.Create;
  662. FThread := TIPCServerThread.Create(Self);
  663. FThread.Start;
  664. Result := WaitForReady;
  665. end;
  666. procedure TSimpleIPCServer.StopThread;
  667. begin
  668. if Assigned(FThread) then
  669. begin
  670. FThread.Terminate;
  671. FThread.WaitFor;
  672. FreeAndNil(FThread);
  673. end;
  674. if Assigned(FThreadReadyEvent) then
  675. FreeAndNil(FThreadReadyEvent);
  676. if Assigned(FQueueAddEvent) then
  677. FreeAndNil(FQueueAddEvent);
  678. if Assigned(FQueueLock) then
  679. FreeAndNil(FQueueLock);
  680. end;
  681. function TSimpleIPCServer.WaitForReady(Timeout: Integer = -1): Boolean;
  682. begin
  683. if FThreadReadyEvent.WaitFor(IPCTimeoutToEventTimeout(Timeout)) = wrSignaled then
  684. Result := FThreadExecuting
  685. else
  686. Result := False;
  687. end;
  688. procedure TSimpleIPCServer.StartServer;
  689. begin
  690. StartServer(FThreaded);
  691. end;
  692. procedure TSimpleIPCServer.StartServer(AThreaded: Boolean);
  693. begin
  694. CheckInactive;
  695. FActive := True;
  696. try
  697. PrepareServerID;
  698. FThreaded := AThreaded;
  699. if FThreaded then
  700. begin
  701. if not StartThread then
  702. raise EIPCError.Create(SErrThreadFailure);
  703. end
  704. else
  705. StartComm;
  706. except
  707. FActive := False;
  708. raise;
  709. end;
  710. end;
  711. procedure TSimpleIPCServer.StopServer;
  712. begin
  713. StopThread;
  714. StopComm;
  715. FQueue.Clear;
  716. FActive := False;
  717. end;
  718. function TSimpleIPCServer.ProcessMessage(Timeout: Integer): Boolean;
  719. begin
  720. FBusy := True;
  721. try
  722. // Check for new messages (may push several messages to the queue)
  723. Result := FIPCComm.PeekMessage(IPCTimeoutSanitized(Timeout));
  724. // Push new message to the queue (explicitly)
  725. if Result then
  726. FIPCComm.ReadMessage;
  727. finally
  728. FBusy := False;
  729. end;
  730. end;
  731. // Timeout values:
  732. // > 0 -- Number of milliseconds to wait
  733. // = 0 -- return immediately
  734. // = -1 -- wait infinitely
  735. // < -1 -- wait infinitely (force to -1)
  736. function TSimpleIPCServer.PeekMessage(Timeout: Integer; DoReadMessage: Boolean): Boolean;
  737. begin
  738. CheckActive;
  739. if Threaded then
  740. begin
  741. // Check if have messages in the queue
  742. FQueueLock.Acquire;
  743. try
  744. Result:=FQueue.Count>0;
  745. // Reset queue add event
  746. if not Result then
  747. FQueueAddEvent.ResetEvent;
  748. finally
  749. FQueueLock.Release;
  750. end;
  751. // Wait for queue add event
  752. if not Result and (Timeout <> 0) then
  753. Result := FQueueAddEvent.WaitFor(IPCTimeoutToEventTimeout(Timeout)) = wrSignaled;
  754. end
  755. else
  756. begin
  757. // Check if have messages in the queue
  758. Result:=FQueue.Count>0;
  759. // If queue is empty, process new messages via IPC driver
  760. if not Result then
  761. Result := ProcessMessage(Timeout);
  762. end;
  763. // Read message if available (be aware of a race condition in threaded mode)
  764. If Result then
  765. If DoReadMessage then
  766. ReadMessage;
  767. end;
  768. function TSimpleIPCServer.ReadMessage: Boolean;
  769. begin
  770. // Pop a message from the queue
  771. Result := PopMessage;
  772. if Result then
  773. DoOnMessage;
  774. end;
  775. function TSimpleIPCServer.PopMessage: Boolean;
  776. begin
  777. if Threaded then
  778. FQueueLock.Acquire;
  779. try
  780. if Assigned(FMessage) then
  781. FreeAndNil(FMessage);
  782. FMessage := FQueue.Pop;
  783. Result := Assigned(FMessage);
  784. finally
  785. if Threaded then
  786. FQueueLock.Release;
  787. end;
  788. end;
  789. procedure TSimpleIPCServer.PushMessage(Msg: TIPCServerMsg);
  790. var
  791. PushFailed: Boolean;
  792. begin
  793. if Threaded then
  794. FQueueLock.Acquire;
  795. try
  796. PushFailed := False;
  797. try
  798. // Queue.Push may throw an exception, e.g. if message queue is full.
  799. FQueue.Push(Msg);
  800. except
  801. PushFailed := True;
  802. end;
  803. // Notify a waiting PeekMessage in threaded mode
  804. if Threaded and not PushFailed then
  805. FQueueAddEvent.SetEvent;
  806. finally
  807. if Threaded then
  808. FQueueLock.Release;
  809. end;
  810. if PushFailed then
  811. // Handler must free the Msg, because it is not owned by anybody.
  812. DoOnMessageError(Msg)
  813. else
  814. DoOnMessageQueued;
  815. end;
  816. function TSimpleIPCServer.GetMsgType: TMessageType;
  817. begin
  818. // Access to the message is not locked by design!
  819. if Assigned(FMessage) then
  820. Result := FMessage.MsgType
  821. else
  822. Result := mtUnknown;
  823. end;
  824. function TSimpleIPCServer.GetMsgData: TStream;
  825. begin
  826. // Access to the message is not locked by design!
  827. if Assigned(FMessage) then
  828. Result := FMessage.Stream
  829. else
  830. Result := nil;
  831. end;
  832. procedure TSimpleIPCServer.GetMessageData(Stream: TStream);
  833. begin
  834. // Access to the message is not locked by design!
  835. if Assigned(FMessage) then
  836. Stream.CopyFrom(FMessage.Stream, 0);
  837. end;
  838. function TSimpleIPCServer.GetStringMessage: String;
  839. begin
  840. // Access to the message is not locked by design!
  841. if Assigned(FMessage) then
  842. Result := FMessage.StringMessage
  843. else
  844. Result := '';
  845. end;
  846. procedure TSimpleIPCServer.Activate;
  847. begin
  848. StartServer;
  849. end;
  850. procedure TSimpleIPCServer.Deactivate;
  851. begin
  852. StopServer;
  853. end;
  854. procedure TSimpleIPCServer.DoOnMessage;
  855. begin
  856. if Assigned(FOnMessage) then
  857. begin
  858. if FSynchronizeEvents and Assigned(FThread) then
  859. TThread.Synchronize(FThread, @InternalDoOnMessage)
  860. else
  861. InternalDoOnMessage;
  862. end;
  863. end;
  864. procedure TSimpleIPCServer.InternalDoOnMessage;
  865. begin
  866. if Assigned(FOnMessage) then
  867. FOnMessage(Self);
  868. end;
  869. procedure TSimpleIPCServer.DoOnMessageQueued;
  870. begin
  871. if Assigned(FOnMessageQueued) then
  872. begin
  873. if FSynchronizeEvents and Assigned(FThread) then
  874. TThread.Synchronize(FThread, @InternalDoOnMessageQueued)
  875. else
  876. InternalDoOnMessageQueued;
  877. end;
  878. end;
  879. procedure TSimpleIPCServer.InternalDoOnMessageQueued;
  880. begin
  881. if Assigned(FOnMessageQueued) then
  882. FOnMessageQueued(Self);
  883. end;
  884. procedure TSimpleIPCServer.DoOnMessageError(Msg: TIPCServerMsg);
  885. begin
  886. try
  887. if Assigned(FOnMessageError) then
  888. begin
  889. // Temp message (class instance variable) is used to pass
  890. // a parameter to a synchronized thread method.
  891. FTempMessage := Msg;
  892. if FSynchronizeEvents and Assigned(FThread) then
  893. TThread.Synchronize(FThread, @InternalDoOnMessageError)
  894. else
  895. InternalDoOnMessageError;
  896. end;
  897. finally
  898. // Must free the message because it is not owned by anybody.
  899. FTempMessage := nil;
  900. FreeAndNil(Msg);
  901. end;
  902. end;
  903. procedure TSimpleIPCServer.InternalDoOnMessageError;
  904. begin
  905. if Assigned(FOnMessageError) then
  906. FOnMessageError(Self, FTempMessage);
  907. end;
  908. procedure TSimpleIPCServer.DoOnThreadError;
  909. begin
  910. if Assigned(FOnThreadError) then
  911. begin
  912. if FSynchronizeEvents and Assigned(FThread) then
  913. TThread.Synchronize(FThread, @InternalDoOnThreadError)
  914. else
  915. InternalDoOnThreadError;
  916. end;
  917. end;
  918. procedure TSimpleIPCServer.InternalDoOnThreadError;
  919. begin
  920. if Assigned(FOnThreadError) then
  921. FOnThreadError(Self);
  922. end;
  923. {$ENDREGION}
  924. {$REGION 'TSimpleIPCClient'}
  925. procedure TSimpleIPCClient.SetServerInstance(const AValue: String);
  926. begin
  927. CheckInactive;
  928. FServerInstance:=AVAlue;
  929. end;
  930. procedure TSimpleIPCClient.Activate;
  931. begin
  932. Connect;
  933. end;
  934. procedure TSimpleIPCClient.Deactivate;
  935. begin
  936. DisConnect;
  937. end;
  938. constructor TSimpleIPCClient.Create(AOwner: TComponent);
  939. begin
  940. inherited Create(AOwner);
  941. end;
  942. destructor TSimpleIPCClient.Destroy;
  943. begin
  944. Active:=False;
  945. Inherited;
  946. end;
  947. procedure TSimpleIPCClient.Connect;
  948. begin
  949. If Not assigned(FIPCComm) then
  950. begin
  951. PrepareServerID;
  952. FIPCComm:=CommClass.Create(Self);
  953. Try
  954. FIPCComm.Connect;
  955. Except
  956. FreeAndNil(FIPCComm);
  957. Raise;
  958. end;
  959. FActive:=True;
  960. end;
  961. end;
  962. procedure TSimpleIPCClient.Disconnect;
  963. begin
  964. If Assigned(FIPCComm) then
  965. Try
  966. FIPCComm.DisConnect;
  967. Finally
  968. FActive:=False;
  969. FreeAndNil(FIPCComm);
  970. end;
  971. end;
  972. function TSimpleIPCClient.ServerRunning: Boolean;
  973. var
  974. TempComm: TIPCClientComm;
  975. begin
  976. If Assigned(FIPCComm) then
  977. Result:=FIPCComm.ServerRunning
  978. else
  979. begin
  980. PrepareServerID;
  981. TempComm := CommClass.Create(Self);
  982. Try
  983. Result := TempComm.ServerRunning;
  984. finally
  985. TempComm.Free;
  986. end;
  987. end;
  988. end;
  989. procedure TSimpleIPCClient.SendMessage(MsgType : TMessageType; Stream: TStream);
  990. begin
  991. CheckActive;
  992. FBusy:=True;
  993. Try
  994. FIPCComm.SendMessage(MsgType,Stream);
  995. Finally
  996. FBusy:=False;
  997. end;
  998. end;
  999. procedure TSimpleIPCClient.SendStringMessage(const Msg: String);
  1000. begin
  1001. SendStringMessage(mtString,Msg);
  1002. end;
  1003. procedure TSimpleIPCClient.SendStringMessage(MsgType: TMessageType; const Msg: String);
  1004. Var
  1005. S : TStringStream;
  1006. begin
  1007. S:=TStringStream.Create(Msg);
  1008. try
  1009. SendMessage(MsgType,S);
  1010. finally
  1011. S.free;
  1012. end;
  1013. end;
  1014. procedure TSimpleIPCClient.SendStringMessageFmt(const Msg: String;
  1015. Args: array of const);
  1016. begin
  1017. SendStringMessageFmt(mtString,Msg,Args);
  1018. end;
  1019. procedure TSimpleIPCClient.SendStringMessageFmt(MsgType: TMessageType;
  1020. const Msg: String; Args: array of const);
  1021. begin
  1022. SendStringMessage(MsgType, Format(Msg,Args));
  1023. end;
  1024. {$ENDREGION}
  1025. {$IFDEF OSNEEDIPCINITDONE}
  1026. initialization
  1027. IPCInit;
  1028. finalization
  1029. IPCDone;
  1030. {$ENDIF}
  1031. end.