Recent

Author Topic: Messages between Windows/Apps  (Read 1741 times)

Birger52

  • Sr. Member
  • ****
  • Posts: 309
Messages between Windows/Apps
« on: September 21, 2019, 12:49:36 pm »
https://www.thoughtco.com/send-information-between-applications-1058476

Seems straight forward - but it's not playing...

One app is doing the work - the other controlling (and starting) it, when the work needs to be done.
The controlling app is not having a visible form - only a trayicon with a menu.
The worker does have a form, and does show what it is doing.

I would like to send a message from the worker to the icon, that the worker has in fact started as it was instructed to, and again, when it is done doing what it should, and closes.

For now, I have this for sending a message:
Code: Pascal  [Select][+][-]
  1. procedure TWorkForm.MsgToTray(Ident : integer);
  2. var
  3.   copyDataStruct : TCopyDataStruct;
  4.   receiverHandle : THandle;
  5.   res : integer;
  6. begin
  7.   copyDataStruct.dwData := Ident; //use it to identify the message contents
  8.   copyDataStruct.cbData := 1 + Length(FBckName) ;
  9.   copyDataStruct.lpData := PChar(FBckName) ;
  10.   receiverHandle := FindWindow(nil,PChar('TrayForm')) ;
  11.   if receiverHandle = 0 then begin
  12.     ShowMessage('CopyData Receiver NOT found!') ;
  13.     Exit;
  14.   end
  15.   else begin
  16.     res := SendMessage(receiverHandle, WM_COPYDATA, Integer(Handle), Integer(@copyDataStruct));
  17.     if res <> 10 then ShowMessage('Sent but not received');
  18.   end;
  19. end;
it seems to be doing what it should - if tray is not running, I get the "CopyData Receiver NOT found!" message.
When it is running, I get the "Sent but not received" message.
But the message is never received.

(I did try FindWindow(PChar('TTrayForm'),PChar('TrayForm')) - but it never finds the app/Window, so nothing is ever sent)

Receiving programming looks like this:
Code: Pascal  [Select][+][-]
  1. type
  2.   TWMCopyData = packed record
  3.     Msg: Cardinal;
  4.     From: HWND;                      //  Handle of the Window that passed the data
  5.     CopyDataStruct: PCopyDataStruct; //  data passed
  6.     Result: Longint;                 //  Use it to send a value back to the "Sender"
  7.   end;
  8.  
  9.   TTrayForm = class(TForm)
  10. ...
  11.   private
  12.     procedure WMCopyData(var Msg : TWMCopyData); message WM_COPYDATA;
  13.  
  14. procedure TTrayForm.WMCopyData(var Msg: TWMCopyData);
  15. var
  16.   s : string;
  17.   tp : integer;
  18. begin
  19.   s := PChar((Msg.CopyDataStruct^).lpData);
  20.   tp := (Msg.CopyDataStruct^).dwData;
  21.   msg.Result := 10;  //  Send something back
  22.   ShowMessage('Recieved');
  23. end;

The message is never shown, and debugging, execution never seem to get there. (Breakpoint at the line that is here nr. 19.)

Any good ideas?
Lazarus 2.0.8 FPC 3.0.4
Win7 64bit
Playing and learning - strictly for my own pleasure.

ASerge

  • Hero Member
  • *****
  • Posts: 2223
Re: Messages between Windows/Apps
« Reply #1 on: September 21, 2019, 01:54:44 pm »
Use ready-made components.
Example.
First app (Controller). New empty project. Put TMemo and TSimpleIPCServer (from System), set it ServerId=TestServer, Global=True, Active=True and add OnMessageQueued event:
Code: Pascal  [Select][+][-]
  1. procedure TForm1.SimpleIPCServer1MessageQueued(Sender: TObject);
  2. begin
  3.   SimpleIPCServer1.ReadMessage;
  4.   Memo1.Append(SimpleIPCServer1.StringMessage);
  5. end;
Started it.

Second app (Worker). New empty project. Put TSimpleIPCClient, set it ServerId=TestServer, add events for form:
Code: Pascal  [Select][+][-]
  1. procedure TForm1.FormCreate(Sender: TObject);
  2. begin
  3.   if SimpleIPCClient1.ServerRunning then
  4.   begin
  5.     SimpleIPCClient1.Connect;
  6.     SimpleIPCClient1.SendStringMessage('I started');
  7.   end;
  8. end;
  9.  
  10. procedure TForm1.FormDestroy(Sender: TObject);
  11. begin
  12.   if SimpleIPCClient1.Active then
  13.     SimpleIPCClient1.SendStringMessage('I am finished');
  14. end;

When the Worker started / finished, the Controller adds the appropriate lines to the memo.

valdir.marcos

  • Hero Member
  • *****
  • Posts: 1106
Re: Messages between Windows/Apps
« Reply #2 on: September 21, 2019, 03:00:01 pm »
https://www.thoughtco.com/send-information-between-applications-1058476

Seems straight forward - but it's not playing...

One app is doing the work - the other controlling (and starting) it, when the work needs to be done.
The controlling app is not having a visible form - only a trayicon with a menu.
The worker does have a form, and does show what it is doing.

I would like to send a message from the worker to the icon, that the worker has in fact started as it was instructed to, and again, when it is done doing what it should, and closes.

For now, I have this for sending a message:
Code: Pascal  [Select][+][-]
  1. procedure TWorkForm.MsgToTray(Ident : integer);
  2. var
  3.   copyDataStruct : TCopyDataStruct;
  4.   receiverHandle : THandle;
  5.   res : integer;
  6. begin
  7.   copyDataStruct.dwData := Ident; //use it to identify the message contents
  8.   copyDataStruct.cbData := 1 + Length(FBckName) ;
  9.   copyDataStruct.lpData := PChar(FBckName) ;
  10.   receiverHandle := FindWindow(nil,PChar('TrayForm')) ;
  11.   if receiverHandle = 0 then begin
  12.     ShowMessage('CopyData Receiver NOT found!') ;
  13.     Exit;
  14.   end
  15.   else begin
  16.     res := SendMessage(receiverHandle, WM_COPYDATA, Integer(Handle), Integer(@copyDataStruct));
  17.     if res <> 10 then ShowMessage('Sent but not received');
  18.   end;
  19. end;
it seems to be doing what it should - if tray is not running, I get the "CopyData Receiver NOT found!" message.
When it is running, I get the "Sent but not received" message.
But the message is never received.

(I did try FindWindow(PChar('TTrayForm'),PChar('TrayForm')) - but it never finds the app/Window, so nothing is ever sent)

Receiving programming looks like this:
Code: Pascal  [Select][+][-]
  1. type
  2.   TWMCopyData = packed record
  3.     Msg: Cardinal;
  4.     From: HWND;                      //  Handle of the Window that passed the data
  5.     CopyDataStruct: PCopyDataStruct; //  data passed
  6.     Result: Longint;                 //  Use it to send a value back to the "Sender"
  7.   end;
  8.  
  9.   TTrayForm = class(TForm)
  10. ...
  11.   private
  12.     procedure WMCopyData(var Msg : TWMCopyData); message WM_COPYDATA;
  13.  
  14. procedure TTrayForm.WMCopyData(var Msg: TWMCopyData);
  15. var
  16.   s : string;
  17.   tp : integer;
  18. begin
  19.   s := PChar((Msg.CopyDataStruct^).lpData);
  20.   tp := (Msg.CopyDataStruct^).dwData;
  21.   msg.Result := 10;  //  Send something back
  22.   ShowMessage('Recieved');
  23. end;

The message is never shown, and debugging, execution never seem to get there. (Breakpoint at the line that is here nr. 19.)

Any good ideas?


Use ready-made components.
Example.
First app (Controller). New empty project. Put TMemo and TSimpleIPCServer (from System), set it ServerId=TestServer, Global=True, Active=True and add OnMessageQueued event:
Code: Pascal  [Select][+][-]
  1. procedure TForm1.SimpleIPCServer1MessageQueued(Sender: TObject);
  2. begin
  3.   SimpleIPCServer1.ReadMessage;
  4.   Memo1.Append(SimpleIPCServer1.StringMessage);
  5. end;
Started it.

Second app (Worker). New empty project. Put TSimpleIPCClient, set it ServerId=TestServer, add events for form:
Code: Pascal  [Select][+][-]
  1. procedure TForm1.FormCreate(Sender: TObject);
  2. begin
  3.   if SimpleIPCClient1.ServerRunning then
  4.   begin
  5.     SimpleIPCClient1.Connect;
  6.     SimpleIPCClient1.SendStringMessage('I started');
  7.   end;
  8. end;
  9.  
  10. procedure TForm1.FormDestroy(Sender: TObject);
  11. begin
  12.   if SimpleIPCClient1.Active then
  13.     SimpleIPCClient1.SendStringMessage('I am finished');
  14. end;

When the Worker started / finished, the Controller adds the appropriate lines to the memo.
Interesting.

Birger52

  • Sr. Member
  • ****
  • Posts: 309
Re: Messages between Windows/Apps
« Reply #3 on: September 21, 2019, 03:06:53 pm »
@ASerge - Thx, I will try that.
Lazarus 2.0.8 FPC 3.0.4
Win7 64bit
Playing and learning - strictly for my own pleasure.

Birger52

  • Sr. Member
  • ****
  • Posts: 309
Re: Messages between Windows/Apps
« Reply #4 on: September 21, 2019, 04:19:27 pm »
@ASerge - and other interested.

I need to be able to start "the worker" manually as well.
So the IPCClient can not be activated at design time, or the program crashes, even before the CreateForm event is fired (and even at desing time, if server is not running)...
So checking if Server is running and setting Client activate to true, if it is, is needed.
Need more checking  (like what happens if Server is shut down while Client is active? Will the worker crash immediately, or when the closing message is attempted sent?)

Also, OnMessageQueed, does not deliver data.
It is necessary to call ReadMessage, that will trigger OnMessage, to get the actual data transferred.

Code: Pascal  [Select][+][-]
  1. procedure TTrayForm.IPCServerMessage(Sender: TObject);
  2. begin
  3.   ShowMessage('Received' +sLineBreak + IPCServer.StringMessage);
  4. end;
  5.  
  6. procedure TTrayForm.IPCServerMessageQueued(Sender: TObject);
  7. begin
  8.   IPCServer.ReadMessage;
  9. end;
  10.  

Other than that, this functions...
Lazarus 2.0.8 FPC 3.0.4
Win7 64bit
Playing and learning - strictly for my own pleasure.

ASerge

  • Hero Member
  • *****
  • Posts: 2223
Re: Messages between Windows/Apps
« Reply #5 on: September 21, 2019, 05:04:16 pm »
I need to be able to start "the worker" manually as well.
So the IPCClient can not be activated at design time, or the program crashes, even before the CreateForm event is fired (and even at desing time, if server is not running)...
So checking if Server is running and setting Client activate to true, if it is, is needed.
In my example is done precisely during runtime.

Quote
Need more checking  (like what happens if Server is shut down while Client is active? Will the worker crash immediately, or when the closing message is attempted sent?)
Check it by timer, for example.

Quote
Also, OnMessageQueed, does not deliver data.
It is necessary to call ReadMessage, that will trigger OnMessage, to get the actual data transferred.
Yes, it's only trigger. Server create invisible window, which receive messages WM_COPYDATA, cache all inputs and trigger OnMessageQueue. By calling ReadMessage you pop all cached messages.

Example. Controller as above, but in Worker I added TLabel, TTimer and write this code.
Code: Pascal  [Select][+][-]
  1. procedure TForm1.FormDestroy(Sender: TObject);
  2. begin
  3.   if SimpleIPCClient1.Active then
  4.     SimpleIPCClient1.SendStringMessage('I am finished');
  5. end;
  6.  
  7. procedure TForm1.Timer1Timer(Sender: TObject);
  8. var
  9.   ClientActive, ServerActive: Boolean;
  10. begin
  11.   ClientActive := SimpleIPCClient1.Active;
  12.   ServerActive := SimpleIPCClient1.ServerRunning;
  13.   if ClientActive <> ServerActive then
  14.   begin
  15.     SimpleIPCClient1.Active := ServerActive;
  16.     if SimpleIPCClient1.Active then
  17.       SimpleIPCClient1.SendStringMessage('Hello from client');
  18.   end;
  19.   Label1.Caption := BoolToStr(SimpleIPCClient1.Active, 'Active', 'Not active');
  20. end;
Now you can start stop applications in any order and see what happens.

Birger52

  • Sr. Member
  • ****
  • Posts: 309
Re: Messages between Windows/Apps
« Reply #6 on: September 21, 2019, 05:39:14 pm »
Quote
In my example is done precisely during runtime.
May have been the intention.
But Connected and Active for the Client is not the same.
(Splitting words, probably, and no need. I'm grateful for the help.)
I set Active to true at design time, and it makes the program crash, before FormCreate event is fired, if the server is not running.

I was thinking, I'd check if server is running and connect, before sending messages, and disconnect after sending the message.
Should make it safe, even if server is shut down, while "the worker" program is running.
A timer is probably like shooting sparrow with missiles - after all, it is only when starting and shutting down, it is sending messages.

These ISP components, also have msgType (for backwards compatibility, it says) and a way to read raw data.

Know if it is safe to use the MsgType, for internal codidng - like 1 for started and 2 for stopped, when string message is name of the job f.ex.?
Lazarus 2.0.8 FPC 3.0.4
Win7 64bit
Playing and learning - strictly for my own pleasure.

Birger52

  • Sr. Member
  • ****
  • Posts: 309
Re: Messages between Windows/Apps
« Reply #7 on: September 22, 2019, 11:52:27 am »
Just info

In worker-app I removed activating IPC from Form creation, and connected, when sending message instead:
Code: Pascal  [Select][+][-]
  1. procedure TWorkForm.MsgToTray(Ident : integer);
  2. begin
  3.   if IPCClient.ServerRunning then begin
  4.     IPCClient.Active := true;
  5.     IPCClient.Connect;
  6.     IPCClient.SendStringMessage(Ident, FBckName);
  7.     IPCClient.DisConnect;
  8.     IPCClient.Active := false;
  9.   end;
  10. end;

Works fine, with Icon running and without Icon running, and I can't seem to create any problems, starting or stopping Icon, while worker running.
Lazarus 2.0.8 FPC 3.0.4
Win7 64bit
Playing and learning - strictly for my own pleasure.

Thaddy

  • Hero Member
  • *****
  • Posts: 14205
  • Probably until I exterminate Putin.
Re: Messages between Windows/Apps
« Reply #8 on: September 22, 2019, 12:36:37 pm »
Messages between windows are easiest achieved with a blow pipe made of electricity tubing and the message format as a little dart. Beware of the range: worse than Bluetooth. windows must be open and in a less than 180 degree radius in absence of wind.  %) %) %)
« Last Edit: September 22, 2019, 12:39:54 pm by Thaddy »
Specialize a type, not a var.

 

TinyPortal © 2005-2018