simpleipc.pp 29 KB

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