simpleipc.inc 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427
  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. Windows implementation of 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. {$IFDEF FPC_DOTTEDUNITS}
  13. uses WinApi.Windows,WinApi.Messages;
  14. {$ELSE FPC_DOTTEDUNITS}
  15. uses Windows,messages;
  16. {$ENDIF FPC_DOTTEDUNITS}
  17. const
  18. MsgWndClassName: WideString = 'FPCMsgWindowCls';
  19. resourcestring
  20. SErrFailedToRegisterWindowClass = 'Failed to register message window class';
  21. SErrFailedToCreateWindow = 'Failed to create message window %s';
  22. var
  23. MsgWindowClass: TWndClassW = (
  24. style: 0;
  25. lpfnWndProc: nil;
  26. cbClsExtra: 0;
  27. cbWndExtra: 0;
  28. hInstance: 0;
  29. hIcon: 0;
  30. hCursor: 0;
  31. hbrBackground: 0;
  32. lpszMenuName: nil;
  33. lpszClassName: nil);
  34. type
  35. TWinMsgServerComm = Class(TIPCServerComm)
  36. strict private
  37. FHWND : HWND;
  38. FWindowName : String;
  39. FWndProcException: Boolean;
  40. FWndProcExceptionMsg: String;
  41. function AllocateHWnd(const aWindowName: WideString) : HWND;
  42. procedure ProcessMessages;
  43. procedure ProcessMessagesWait(TimeOut: Integer);
  44. procedure HandlePostedMessage(const Msg: TMsg); inline;
  45. function HaveQueuedMessages: Boolean; inline;
  46. function CountQueuedMessages: Integer; inline;
  47. procedure CheckWndProcException; inline;
  48. private
  49. procedure ReadMsgData(var Msg: TMsg);
  50. function TryReadMsgData(var Msg: TMsg; out Error: String): Boolean;
  51. procedure SetWndProcException(const ErrorMsg: String); inline;
  52. public
  53. constructor Create(AOwner : TSimpleIPCServer); override;
  54. destructor Destroy; override;
  55. Procedure StartServer; override;
  56. Procedure StopServer; override;
  57. Function PeekMessage(TimeOut : Integer) : Boolean; override;
  58. Procedure ReadMessage ; override;
  59. Function GetInstanceID : String;override;
  60. Property WindowName : String Read FWindowName;
  61. end;
  62. { ---------------------------------------------------------------------
  63. MsgWndProc
  64. ---------------------------------------------------------------------}
  65. function MsgWndProc(Window: HWND; uMsg: UINT; wParam: WPARAM; lParam: LPARAM): LRESULT; {$ifdef wince}cdecl;{$else}stdcall;{$endif}
  66. Var
  67. Server: TWinMsgServerComm;
  68. Msg: TMsg;
  69. MsgError: String;
  70. begin
  71. Result:=0;
  72. if (uMsg=WM_COPYDATA) then
  73. begin
  74. // Post WM_USER to wake up GetMessage call.
  75. PostMessage(Window, WM_USER, 0, 0);
  76. // Read message data and add to message queue.
  77. Server:=TWinMsgServerComm({$ifdef wince}GetWindowLong{$else}GetWindowLongPtr{$endif}(Window,GWL_USERDATA));
  78. if Assigned(Server) then
  79. begin
  80. Msg.Message:=uMsg;
  81. Msg.wParam:=wParam;
  82. Msg.lParam:=lParam;
  83. // Exceptions thrown inside WindowProc may not propagate back
  84. // to the caller in some circumstances (according to MSDN),
  85. // so capture it and raise it outside of WindowProc!
  86. if Server.TryReadMsgData(Msg, MsgError) then
  87. Result:=1 // True
  88. else
  89. begin
  90. Result:=0; // False
  91. Server.SetWndProcException(MsgError);
  92. end;
  93. end;
  94. end
  95. else
  96. begin
  97. Result:=DefWindowProcW(Window,uMsg,wParam,lParam);
  98. end;
  99. end;
  100. { ---------------------------------------------------------------------
  101. TWinMsgServerComm
  102. ---------------------------------------------------------------------}
  103. function TWinMsgServerComm.AllocateHWnd(const aWindowName: WideString): HWND;
  104. var
  105. cls: TWndClassW;
  106. isreg : Boolean;
  107. begin
  108. MsgWindowClass.lpfnWndProc:=@MsgWndProc;
  109. MsgWindowClass.hInstance := HInstance;
  110. MsgWindowClass.lpszClassName:=PWideChar(MsgWndClassName);
  111. isreg:=GetClassInfoW(HInstance,PWideChar(MsgWndClassName),@cls);
  112. if not isreg then
  113. if ({$IFDEF FPC_DOTTEDUNITS}WinApi.{$ENDIF}Windows.RegisterClassW(MsgWindowClass)=0) then
  114. Owner.DoError(SErrFailedToRegisterWindowClass,[]);
  115. Result:=CreateWindowExW(WS_EX_TOOLWINDOW, PWideChar(MsgWndClassName),
  116. PWideChar(aWindowName), WS_POPUP {!0}, 0, 0, 0, 0, 0, 0, HInstance, nil);
  117. if (Result=0) then
  118. Owner.DoError(SErrFailedToCreateWindow,[aWindowName]);
  119. {$ifdef wince}SetWindowLong{$else}SetWindowLongPtr{$endif}(Result,GWL_USERDATA,PtrInt(Self));
  120. end;
  121. constructor TWinMsgServerComm.Create(AOwner: TSimpleIPCServer);
  122. begin
  123. inherited Create(AOwner);
  124. FWindowName := Owner.ServerID;
  125. If not Owner.Global then
  126. FWindowName := FWindowName+'_'+InstanceID;
  127. FWndProcException := False;
  128. FWndProcExceptionMsg := '';
  129. end;
  130. destructor TWinMsgServerComm.Destroy;
  131. begin
  132. StopServer;
  133. inherited;
  134. end;
  135. procedure TWinMsgServerComm.StartServer;
  136. begin
  137. StopServer;
  138. FHWND := AllocateHWND(WideString(FWindowName));
  139. end;
  140. procedure TWinMsgServerComm.StopServer;
  141. begin
  142. if FHWND <> 0 then
  143. begin
  144. DestroyWindow(FHWND);
  145. FHWND := 0;
  146. end;
  147. end;
  148. procedure TWinMsgServerComm.SetWndProcException(const ErrorMsg: String); inline;
  149. begin
  150. FWndProcException := True;
  151. FWndProcExceptionMsg := ErrorMsg;
  152. end;
  153. procedure TWinMsgServerComm.CheckWndProcException; inline;
  154. var
  155. Msg: String;
  156. begin
  157. if FWndProcException then
  158. begin
  159. Msg := FWndProcExceptionMsg;
  160. FWndProcException := False;
  161. FWndProcExceptionMsg := '';
  162. Owner.DoError(Msg, []);
  163. end;
  164. end;
  165. function TWinMsgServerComm.HaveQueuedMessages: Boolean; inline;
  166. begin
  167. Result := (Owner.Queue.Count > 0);
  168. end;
  169. function TWinMsgServerComm.CountQueuedMessages: Integer; inline;
  170. begin
  171. Result := Owner.Queue.Count;
  172. end;
  173. procedure TWinMsgServerComm.HandlePostedMessage(const Msg: TMsg); inline;
  174. begin
  175. if Msg.message <> WM_USER then
  176. begin
  177. TranslateMessage(Msg);
  178. DispatchMessage(Msg);
  179. end
  180. end;
  181. procedure TWinMsgServerComm.ProcessMessages;
  182. var
  183. Msg: TMsg;
  184. begin
  185. // {$IFDEF FPC_DOTTEDUNITS}WinApi.{$ENDIF}Windows.PeekMessage dispatches incoming sent messages by directly
  186. // calling associated WindowProc, and then checks the thread message queue
  187. // for posted messages and retrieves a message if any available.
  188. // Note: WM_COPYDATA is a sent message, not posted, so it will be processed
  189. // directly via WindowProc inside of {$IFDEF FPC_DOTTEDUNITS}WinApi.{$ENDIF}Windows.PeekMessage call.
  190. while {$IFDEF FPC_DOTTEDUNITS}WinApi.{$ENDIF}Windows.PeekMessage(Msg, FHWND, 0, 0, PM_REMOVE) do
  191. begin
  192. // Empty the message queue by processing all posted messages.
  193. HandlePostedMessage(Msg);
  194. end;
  195. end;
  196. procedure TWinMsgServerComm.ProcessMessagesWait(TimeOut: Integer);
  197. var
  198. Msg: TMsg;
  199. TimerID: UINT_PTR;
  200. GetMessageResult: BOOL;
  201. begin
  202. // Not allowed to wait.
  203. if TimeOut = 0 then
  204. Exit;
  205. // Setup a timer to post WM_TIMER to wake up GetMessage call.
  206. if TimeOut > 0 then
  207. TimerID := SetTimer(FHWND, 0, TimeOut, nil)
  208. else
  209. TimerID := 0;
  210. // Wait until a message arrives.
  211. try
  212. // We either need to wait infinitely or we have a timer.
  213. if (TimeOut < 0) or (TimerID <> 0) then
  214. begin
  215. // {$IFDEF FPC_DOTTEDUNITS}WinApi.{$ENDIF}Windows.GetMessage dispatches incoming sent messages until a posted
  216. // message is available for retrieval. Note: WM_COPYDATA will not actually
  217. // wake up {$IFDEF FPC_DOTTEDUNITS}WinApi.{$ENDIF}Windows.GetMessage, so we must post a dummy message when
  218. // we receive WM_COPYDATA inside of WindowProc.
  219. GetMessageResult := {$IFDEF FPC_DOTTEDUNITS}WinApi.{$ENDIF}Windows.GetMessage(Msg, FHWND, 0, 0);
  220. case LongInt(GetMessageResult) of
  221. -1, 0: ;
  222. else HandlePostedMessage(Msg);
  223. end;
  224. end;
  225. finally
  226. // Destroy timer.
  227. if TimerID <> 0 then
  228. KillTimer(FHWND, TimerID);
  229. end;
  230. end;
  231. function TWinMsgServerComm.PeekMessage(TimeOut: Integer): Boolean;
  232. begin
  233. // Process incoming messages.
  234. ProcessMessages;
  235. // Do we have queued messages?
  236. Result := HaveQueuedMessages;
  237. // Wait for incoming messages.
  238. if (not Result) and (TimeOut <> 0) then
  239. begin
  240. ProcessMessagesWait(TimeOut);
  241. Result := HaveQueuedMessages;
  242. end;
  243. // Check for exception raised inside WindowProc.
  244. CheckWndProcException;
  245. end;
  246. procedure TWinMsgServerComm.ReadMsgData(var Msg: TMsg);
  247. var
  248. CDS: PCopyDataStruct;
  249. MsgItem: TIPCServerMsg;
  250. begin
  251. CDS := PCopyDataStruct(Msg.lParam);
  252. MsgItem := TIPCServerMsg.Create;
  253. try
  254. MsgItem.MsgType := CDS^.dwData;
  255. MsgItem.Stream.WriteBuffer(CDS^.lpData^,CDS^.cbData);
  256. except
  257. FreeAndNil(MsgItem);
  258. // Caller is expected to catch this exception, so not using Owner.DoError()
  259. raise;
  260. end;
  261. PushMessage(MsgItem);
  262. end;
  263. function TWinMsgServerComm.TryReadMsgData(var Msg: TMsg; out Error: String): Boolean;
  264. begin
  265. Result := True;
  266. try
  267. ReadMsgData(Msg);
  268. except on E: Exception do
  269. begin
  270. Result := False;
  271. Error := E.Message;
  272. end;
  273. end;
  274. end;
  275. procedure TWinMsgServerComm.ReadMessage;
  276. begin
  277. // Do nothing, PeekMessages has pushed messages to the queue.
  278. end;
  279. function TWinMsgServerComm.GetInstanceID: String;
  280. begin
  281. Result:=IntToStr(HInstance);
  282. end;
  283. { ---------------------------------------------------------------------
  284. TWinMsgClientComm
  285. ---------------------------------------------------------------------}
  286. Type
  287. TWinMsgClientComm = Class(TIPCClientComm)
  288. Private
  289. FWindowName: String;
  290. FHWND : HWND;
  291. function FindServerWindow: HWND;
  292. function FindServerWindow(const aWindowName: WideString): HWND;
  293. Public
  294. Constructor Create(AOWner : TSimpleIPCClient); override;
  295. Procedure Connect; override;
  296. Procedure Disconnect; override;
  297. Procedure SendMessage(MsgType : TMessageType; Stream : TStream); override;
  298. Function ServerRunning : Boolean; override;
  299. Property WindowName : String Read FWindowName;
  300. end;
  301. constructor TWinMsgClientComm.Create(AOWner: TSimpleIPCClient);
  302. begin
  303. inherited Create(AOWner);
  304. FWindowName:=Owner.ServerID;
  305. If (Owner.ServerInstance<>'') then
  306. FWindowName:=FWindowName+'_'+Owner.ServerInstance;
  307. end;
  308. function TWinMsgClientComm.FindServerWindow: HWND;
  309. begin
  310. Result := FindServerWindow(WideString(FWindowName));
  311. end;
  312. function TWinMsgClientComm.FindServerWindow(const aWindowName: WideString): HWND;
  313. begin
  314. Result := FindWindowW(PWideChar(MsgWndClassName), PWideChar(aWindowName));
  315. end;
  316. procedure TWinMsgClientComm.Connect;
  317. begin
  318. FHWND:=FindServerWindow;
  319. If (FHWND=0) then
  320. Owner.DoError(SErrServerNotActive,[Owner.ServerID]);
  321. end;
  322. procedure TWinMsgClientComm.Disconnect;
  323. begin
  324. FHWND:=0;
  325. end;
  326. procedure TWinMsgClientComm.SendMessage(MsgType: TMessageType; Stream: TStream);
  327. var
  328. CDS : TCopyDataStruct;
  329. Data,FMemstr : TMemorySTream;
  330. begin
  331. if Stream is TMemoryStream then
  332. begin
  333. Data:=TMemoryStream(Stream);
  334. FMemStr:=nil;
  335. end
  336. else
  337. begin
  338. FMemStr:=TMemoryStream.Create;
  339. Data:=FMemstr;
  340. end;
  341. try
  342. if Assigned(FMemStr) then
  343. begin
  344. FMemStr.CopyFrom(Stream,0);
  345. FMemStr.Seek(0,soFromBeginning);
  346. end;
  347. CDS.dwData:=MsgType;
  348. CDS.lpData:=Data.Memory;
  349. CDS.cbData:=Data.Size;
  350. {$IFDEF FPC_DOTTEDUNITS}WinApi.{$ENDIF}Windows.SendMessage(FHWND,WM_COPYDATA,0,PtrInt(@CDS));
  351. finally
  352. FreeAndNil(FMemStr);
  353. end;
  354. end;
  355. function TWinMsgClientComm.ServerRunning: Boolean;
  356. begin
  357. Result:=FindServerWindow<>0;
  358. end;
  359. { ---------------------------------------------------------------------
  360. Set TSimpleIPCClient / TSimpleIPCServer defaults.
  361. ---------------------------------------------------------------------}
  362. Function TSimpleIPCServer.CommClass : TIPCServerCommClass;
  363. begin
  364. if (DefaultIPCServerClass<>Nil) then
  365. Result:=DefaultIPCServerClass
  366. else
  367. Result:=TWinMsgServerComm;
  368. end;
  369. Function TSimpleIPCClient.CommClass : TIPCClientCommClass;
  370. begin
  371. if (DefaultIPCClientClass<>Nil) then
  372. Result:=DefaultIPCClientClass
  373. else
  374. Result:=TWinMsgClientComm;
  375. end;