123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427 |
- {
- This file is part of the Free Component library.
- Copyright (c) 2005 by Michael Van Canneyt, member of
- the Free Pascal development team
- Windows implementation of one-way IPC between 2 processes
- See the file COPYING.FPC, included in this distribution,
- for details about the copyright.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- **********************************************************************}
- {$IFDEF FPC_DOTTEDUNITS}
- uses WinApi.Windows,WinApi.Messages;
- {$ELSE FPC_DOTTEDUNITS}
- uses Windows,messages;
- {$ENDIF FPC_DOTTEDUNITS}
- const
- MsgWndClassName: WideString = 'FPCMsgWindowCls';
- resourcestring
- SErrFailedToRegisterWindowClass = 'Failed to register message window class';
- SErrFailedToCreateWindow = 'Failed to create message window %s';
- var
- MsgWindowClass: TWndClassW = (
- style: 0;
- lpfnWndProc: nil;
- cbClsExtra: 0;
- cbWndExtra: 0;
- hInstance: 0;
- hIcon: 0;
- hCursor: 0;
- hbrBackground: 0;
- lpszMenuName: nil;
- lpszClassName: nil);
- type
- TWinMsgServerComm = Class(TIPCServerComm)
- strict private
- FHWND : HWND;
- FWindowName : String;
- FWndProcException: Boolean;
- FWndProcExceptionMsg: String;
- function AllocateHWnd(const aWindowName: WideString) : HWND;
- procedure ProcessMessages;
- procedure ProcessMessagesWait(TimeOut: Integer);
- procedure HandlePostedMessage(const Msg: TMsg); inline;
- function HaveQueuedMessages: Boolean; inline;
- function CountQueuedMessages: Integer; inline;
- procedure CheckWndProcException; inline;
- private
- procedure ReadMsgData(var Msg: TMsg);
- function TryReadMsgData(var Msg: TMsg; out Error: String): Boolean;
- procedure SetWndProcException(const ErrorMsg: String); inline;
- public
- constructor Create(AOwner : TSimpleIPCServer); override;
- destructor Destroy; override;
- Procedure StartServer; override;
- Procedure StopServer; override;
- Function PeekMessage(TimeOut : Integer) : Boolean; override;
- Procedure ReadMessage ; override;
- Function GetInstanceID : String;override;
- Property WindowName : String Read FWindowName;
- end;
- { ---------------------------------------------------------------------
- MsgWndProc
- ---------------------------------------------------------------------}
- function MsgWndProc(Window: HWND; uMsg: UINT; wParam: WPARAM; lParam: LPARAM): LRESULT; {$ifdef wince}cdecl;{$else}stdcall;{$endif}
- Var
- Server: TWinMsgServerComm;
- Msg: TMsg;
- MsgError: String;
- begin
- Result:=0;
- if (uMsg=WM_COPYDATA) then
- begin
- // Post WM_USER to wake up GetMessage call.
- PostMessage(Window, WM_USER, 0, 0);
- // Read message data and add to message queue.
- Server:=TWinMsgServerComm({$ifdef wince}GetWindowLong{$else}GetWindowLongPtr{$endif}(Window,GWL_USERDATA));
- if Assigned(Server) then
- begin
- Msg.Message:=uMsg;
- Msg.wParam:=wParam;
- Msg.lParam:=lParam;
- // Exceptions thrown inside WindowProc may not propagate back
- // to the caller in some circumstances (according to MSDN),
- // so capture it and raise it outside of WindowProc!
- if Server.TryReadMsgData(Msg, MsgError) then
- Result:=1 // True
- else
- begin
- Result:=0; // False
- Server.SetWndProcException(MsgError);
- end;
- end;
- end
- else
- begin
- Result:=DefWindowProcW(Window,uMsg,wParam,lParam);
- end;
- end;
- { ---------------------------------------------------------------------
- TWinMsgServerComm
- ---------------------------------------------------------------------}
- function TWinMsgServerComm.AllocateHWnd(const aWindowName: WideString): HWND;
- var
- cls: TWndClassW;
- isreg : Boolean;
- begin
- MsgWindowClass.lpfnWndProc:=@MsgWndProc;
- MsgWindowClass.hInstance := HInstance;
- MsgWindowClass.lpszClassName:=PWideChar(MsgWndClassName);
- isreg:=GetClassInfoW(HInstance,PWideChar(MsgWndClassName),@cls);
- if not isreg then
- if ({$IFDEF FPC_DOTTEDUNITS}WinApi.{$ENDIF}Windows.RegisterClassW(MsgWindowClass)=0) then
- Owner.DoError(SErrFailedToRegisterWindowClass,[]);
- Result:=CreateWindowExW(WS_EX_TOOLWINDOW, PWideChar(MsgWndClassName),
- PWideChar(aWindowName), WS_POPUP {!0}, 0, 0, 0, 0, 0, 0, HInstance, nil);
- if (Result=0) then
- Owner.DoError(SErrFailedToCreateWindow,[aWindowName]);
- {$ifdef wince}SetWindowLong{$else}SetWindowLongPtr{$endif}(Result,GWL_USERDATA,PtrInt(Self));
- end;
- constructor TWinMsgServerComm.Create(AOwner: TSimpleIPCServer);
- begin
- inherited Create(AOwner);
- FWindowName := Owner.ServerID;
- If not Owner.Global then
- FWindowName := FWindowName+'_'+InstanceID;
- FWndProcException := False;
- FWndProcExceptionMsg := '';
- end;
- destructor TWinMsgServerComm.Destroy;
- begin
- StopServer;
- inherited;
- end;
- procedure TWinMsgServerComm.StartServer;
- begin
- StopServer;
- FHWND := AllocateHWND(WideString(FWindowName));
- end;
- procedure TWinMsgServerComm.StopServer;
- begin
- if FHWND <> 0 then
- begin
- DestroyWindow(FHWND);
- FHWND := 0;
- end;
- end;
- procedure TWinMsgServerComm.SetWndProcException(const ErrorMsg: String); inline;
- begin
- FWndProcException := True;
- FWndProcExceptionMsg := ErrorMsg;
- end;
- procedure TWinMsgServerComm.CheckWndProcException; inline;
- var
- Msg: String;
- begin
- if FWndProcException then
- begin
- Msg := FWndProcExceptionMsg;
- FWndProcException := False;
- FWndProcExceptionMsg := '';
- Owner.DoError(Msg, []);
- end;
- end;
- function TWinMsgServerComm.HaveQueuedMessages: Boolean; inline;
- begin
- Result := (Owner.Queue.Count > 0);
- end;
- function TWinMsgServerComm.CountQueuedMessages: Integer; inline;
- begin
- Result := Owner.Queue.Count;
- end;
- procedure TWinMsgServerComm.HandlePostedMessage(const Msg: TMsg); inline;
- begin
- if Msg.message <> WM_USER then
- begin
- TranslateMessage(Msg);
- DispatchMessage(Msg);
- end
- end;
- procedure TWinMsgServerComm.ProcessMessages;
- var
- Msg: TMsg;
- begin
- // {$IFDEF FPC_DOTTEDUNITS}WinApi.{$ENDIF}Windows.PeekMessage dispatches incoming sent messages by directly
- // calling associated WindowProc, and then checks the thread message queue
- // for posted messages and retrieves a message if any available.
- // Note: WM_COPYDATA is a sent message, not posted, so it will be processed
- // directly via WindowProc inside of {$IFDEF FPC_DOTTEDUNITS}WinApi.{$ENDIF}Windows.PeekMessage call.
- while {$IFDEF FPC_DOTTEDUNITS}WinApi.{$ENDIF}Windows.PeekMessage(Msg, FHWND, 0, 0, PM_REMOVE) do
- begin
- // Empty the message queue by processing all posted messages.
- HandlePostedMessage(Msg);
- end;
- end;
- procedure TWinMsgServerComm.ProcessMessagesWait(TimeOut: Integer);
- var
- Msg: TMsg;
- TimerID: UINT_PTR;
- GetMessageResult: BOOL;
- begin
- // Not allowed to wait.
- if TimeOut = 0 then
- Exit;
- // Setup a timer to post WM_TIMER to wake up GetMessage call.
- if TimeOut > 0 then
- TimerID := SetTimer(FHWND, 0, TimeOut, nil)
- else
- TimerID := 0;
- // Wait until a message arrives.
- try
- // We either need to wait infinitely or we have a timer.
- if (TimeOut < 0) or (TimerID <> 0) then
- begin
- // {$IFDEF FPC_DOTTEDUNITS}WinApi.{$ENDIF}Windows.GetMessage dispatches incoming sent messages until a posted
- // message is available for retrieval. Note: WM_COPYDATA will not actually
- // wake up {$IFDEF FPC_DOTTEDUNITS}WinApi.{$ENDIF}Windows.GetMessage, so we must post a dummy message when
- // we receive WM_COPYDATA inside of WindowProc.
- GetMessageResult := {$IFDEF FPC_DOTTEDUNITS}WinApi.{$ENDIF}Windows.GetMessage(Msg, FHWND, 0, 0);
- case LongInt(GetMessageResult) of
- -1, 0: ;
- else HandlePostedMessage(Msg);
- end;
- end;
- finally
- // Destroy timer.
- if TimerID <> 0 then
- KillTimer(FHWND, TimerID);
- end;
- end;
- function TWinMsgServerComm.PeekMessage(TimeOut: Integer): Boolean;
- begin
- // Process incoming messages.
- ProcessMessages;
- // Do we have queued messages?
- Result := HaveQueuedMessages;
- // Wait for incoming messages.
- if (not Result) and (TimeOut <> 0) then
- begin
- ProcessMessagesWait(TimeOut);
- Result := HaveQueuedMessages;
- end;
- // Check for exception raised inside WindowProc.
- CheckWndProcException;
- end;
- procedure TWinMsgServerComm.ReadMsgData(var Msg: TMsg);
- var
- CDS: PCopyDataStruct;
- MsgItem: TIPCServerMsg;
- begin
- CDS := PCopyDataStruct(Msg.lParam);
- MsgItem := TIPCServerMsg.Create;
- try
- MsgItem.MsgType := CDS^.dwData;
- MsgItem.Stream.WriteBuffer(CDS^.lpData^,CDS^.cbData);
- except
- FreeAndNil(MsgItem);
- // Caller is expected to catch this exception, so not using Owner.DoError()
- raise;
- end;
- PushMessage(MsgItem);
- end;
- function TWinMsgServerComm.TryReadMsgData(var Msg: TMsg; out Error: String): Boolean;
- begin
- Result := True;
- try
- ReadMsgData(Msg);
- except on E: Exception do
- begin
- Result := False;
- Error := E.Message;
- end;
- end;
- end;
- procedure TWinMsgServerComm.ReadMessage;
- begin
- // Do nothing, PeekMessages has pushed messages to the queue.
- end;
- function TWinMsgServerComm.GetInstanceID: String;
- begin
- Result:=IntToStr(HInstance);
- end;
- { ---------------------------------------------------------------------
- TWinMsgClientComm
- ---------------------------------------------------------------------}
- Type
- TWinMsgClientComm = Class(TIPCClientComm)
- Private
- FWindowName: String;
- FHWND : HWND;
- function FindServerWindow: HWND;
- function FindServerWindow(const aWindowName: WideString): HWND;
- Public
- Constructor Create(AOWner : TSimpleIPCClient); override;
- Procedure Connect; override;
- Procedure Disconnect; override;
- Procedure SendMessage(MsgType : TMessageType; Stream : TStream); override;
- Function ServerRunning : Boolean; override;
- Property WindowName : String Read FWindowName;
- end;
- constructor TWinMsgClientComm.Create(AOWner: TSimpleIPCClient);
- begin
- inherited Create(AOWner);
- FWindowName:=Owner.ServerID;
- If (Owner.ServerInstance<>'') then
- FWindowName:=FWindowName+'_'+Owner.ServerInstance;
- end;
- function TWinMsgClientComm.FindServerWindow: HWND;
- begin
- Result := FindServerWindow(WideString(FWindowName));
- end;
- function TWinMsgClientComm.FindServerWindow(const aWindowName: WideString): HWND;
- begin
- Result := FindWindowW(PWideChar(MsgWndClassName), PWideChar(aWindowName));
- end;
- procedure TWinMsgClientComm.Connect;
- begin
- FHWND:=FindServerWindow;
- If (FHWND=0) then
- Owner.DoError(SErrServerNotActive,[Owner.ServerID]);
- end;
- procedure TWinMsgClientComm.Disconnect;
- begin
- FHWND:=0;
- end;
- procedure TWinMsgClientComm.SendMessage(MsgType: TMessageType; Stream: TStream);
- var
- CDS : TCopyDataStruct;
- Data,FMemstr : TMemorySTream;
- begin
- if Stream is TMemoryStream then
- begin
- Data:=TMemoryStream(Stream);
- FMemStr:=nil;
- end
- else
- begin
- FMemStr:=TMemoryStream.Create;
- Data:=FMemstr;
- end;
- try
- if Assigned(FMemStr) then
- begin
- FMemStr.CopyFrom(Stream,0);
- FMemStr.Seek(0,soFromBeginning);
- end;
- CDS.dwData:=MsgType;
- CDS.lpData:=Data.Memory;
- CDS.cbData:=Data.Size;
- {$IFDEF FPC_DOTTEDUNITS}WinApi.{$ENDIF}Windows.SendMessage(FHWND,WM_COPYDATA,0,PtrInt(@CDS));
- finally
- FreeAndNil(FMemStr);
- end;
- end;
- function TWinMsgClientComm.ServerRunning: Boolean;
- begin
- Result:=FindServerWindow<>0;
- end;
- { ---------------------------------------------------------------------
- Set TSimpleIPCClient / TSimpleIPCServer defaults.
- ---------------------------------------------------------------------}
- Function TSimpleIPCServer.CommClass : TIPCServerCommClass;
- begin
- if (DefaultIPCServerClass<>Nil) then
- Result:=DefaultIPCServerClass
- else
- Result:=TWinMsgServerComm;
- end;
- Function TSimpleIPCClient.CommClass : TIPCClientCommClass;
- begin
- if (DefaultIPCClientClass<>Nil) then
- Result:=DefaultIPCClientClass
- else
- Result:=TWinMsgClientComm;
- end;
|