4

I'm trying to pass information between two of my applications in Delphi 2010.

I'm using a simplified version of code that I've used successfully in the past (simplified because I don't need the sender to know that the send has been successful) I've boiled down the send received to a pair of example applications, which in essence are as follows

Send

procedure TMF.SendString;
var
   copyDataStruct: TCopyDataStruct;
   s: AnsiString;
begin
   s := ebFirm.Text;
   copyDataStruct.cbData := 1 + length(s);
   copyDataStruct.lpData := PAnsiChar(s);
   SendData(copyDataStruct);
end;

procedure TMF.SendData(copyDataStruct: TCopyDataStruct);
var
   rh: THandle;
   res: integer;
begin
   rh := FindWindow(PChar('TMF'), PChar('Get Phone'));
   if rh = 0 then
   begin
      // Launch the target application
      ShellExecute(Handle, 'open', GetPhone, nil, nil, SW_SHOWNORMAL);
      // Give time for the application to launch  
      Sleep(3000);
      SendData(copyDataStruct); // RECURSION!
   end;
   SendMessage(rh, WM_COPYDATA, Integer(Handle), Integer(@copyDataStruct));
end;

Receive Application

procedure TMF.WMCopyData(var Msg: TWMCopyData);
var
   s : AnsiString;
begin
   s := PAnsiChar(Msg.CopyDataStruct.lpData) ;
   jobstatus.Panels[1].Text := s;
end;

The major difference between the working test applications and the application I am adding the code to is that there is a lot of extra activity going on in target application. Especially on startup.

Any suggestions on why the WMCopyData procedure seems not to be firing at all?

CHeers

Dan

Chris Thornton
  • 15,620
  • 5
  • 37
  • 62
Dan Kelly
  • 2,634
  • 5
  • 41
  • 61

4 Answers4

6

There are a few problems with your code.

One, you are not assigning a unique ID to the message. The VCL, and various third-party components, also use WM_COPYDATA, so you have to make sure you are actually processing YOUR message and not SOMEONE ELSE'S message.

Two, you may not be waiting long enough for the second app to start. Instead of Sleep(), use ShellExecuteEx() with the SEE_MASK_WAITFORINPUTIDLE flag (or use CreateProcess() and WaitForInputIdle()).

Third, when starting the second app, your recursive logic is attempting to send the message a second time. If that happened to fail, you would launch a third app, and so on. You should take out the recursion altogether, you don't need it.

Try this:

var
  GetPhoneMsg: DWORD = 0;

procedure TMF.SendString;
var
  copyDataStruct: TCopyDataStruct;
  s: AnsiString;
begin
  if GetPhoneMsg = 0 then Exit;
  s := ebFirm.Text;
  copyDataStruct.dwData := GetPhoneMsg;
  copyDataStruct.cbData := Length(s);
  copyDataStruct.lpData := PAnsiChar(s);
  SendData(copyDataStruct);
end;

procedure TMF.SendData(copyDataStruct: TCopyDataStruct);
var
  rh: HWND;
  si: TShellExecuteInfo;
  res: Integer;
begin
  rh := FindWindow(PChar('TMF'), PChar('Get Phone'));
  if rh = 0 then
  begin
    // Launch the target application and give time to start
    ZeroMemory(@si, SizeOf(si));
    si.cbSize := SizeOf(si);
    si.fMask := SEE_MASK_WAITFORINPUTIDLE;
    si.hwnd := Handle;
    si.lpVerb := 'open';
    si.lpFile := GetPhone;
    si.nShow := SW_SHOWNORMAL;
    if not ShellExecuteEx(@si) then Exit;
    rh := FindWindow(PChar('TMF'), PChar('Get Phone'));
    if rh = 0 then Exit;
  end;
  SendMessage(rh, WM_COPYDATA, WParam(Handle), LParam(@copyDataStruct));
end;

initialization
  GetPhoneMsg := RegisterWindowMessage('TMF_GetPhone');

Receive Application

var
  GetPhoneMsg: DWORD = 0;

procedure TMF.WMCopyData(var Msg: TWMCopyData);
var
  s : AnsiString;
begin
  if (GetPhoneMsg <> 0) and (Msg.CopyDataStruct.dwData = GetPhoneMsg) then
  begin
    SetString(s, PAnsiChar(Msg.CopyDataStruct.lpData), Msg.CopyDataStruct.cbData);
    jobstatus.Panels[1].Text := s;
  end else
    inherited;
end;

initialization
  GetPhoneMsg := RegisterWindowMessage('TMF_GetPhone');
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Thanks for the ShellExecuteEx information. Looks like a better way than the recursion. As mentioned to Chris above, there should be an else on the "if rh = 0" to stop SendMessage being called twice. – Dan Kelly Sep 01 '11 at 10:20
  • Perfect Code. No other send message code from the internet worked including Zarko's and few other Russian websites. I was getting incomplete string. SO developers also were busy elucidating comments on this much sought after snippet rather than posting any full working code. Perhaps it was due to my machine being Win7 64bit. – user30478 Jan 21 '19 at 03:17
3

I think it is a good habit to add

  copyDataStruct.dwData := Handle; 

in procedure TMF.SendString; - if you don't have a custom identifier, putting the source HWND value will help debugging on the destination (you can check for this value in the other side, and therefore avoid misunderstand of broadcasted WMCOPY_DATA e.g. - yes, there should not be, but I've seen some!).

And

    procedure WMCopyData(var Msg : TWMCopyData); message WM_COPYDATA;

in TMF client class definition, right?

There should be a missing exit or else after the nested SendData call:

procedure TMF.SendData(copyDataStruct: TCopyDataStruct);
  (...)
      Sleep(3000);
      SendData(copyDataStruct);
   end else
     SendMessage(rh, WM_COPYDATA, NativeInt(Handle), NativeInt(@copyDataStruct));
end;

But this won't change much.

Check the rh := FindWindow() returned handle: is it the Handle of the TMF client form, or the Application.Handle?

Arnaud Bouchez
  • 42,305
  • 3
  • 71
  • 159
  • I see no reason why you need `copyDataStruct.dwData := Handle` – David Heffernan Aug 31 '11 at 14:51
  • Thanks. Turns out there was a bug in the update code for the target application that was copying an older (i.e. the current live application) over the development copy. This was only happening when the target application was called from the sender application. No problem when running normally or from within Delphi. – Dan Kelly Aug 31 '11 at 15:35
  • 2
    @Arnaud Would you mind clarifying what you mean by your first suggestion regarding `dwData`. If I'm wrong then I'd like to know, and if I'm right then you can remove it and avoid misleading people. – David Heffernan Aug 31 '11 at 15:45
  • @David I changed the comment to specify what was the purpose of this line. – Arnaud Bouchez Aug 31 '11 at 19:20
  • @David This is not pointless. See my point about filtering a *broadcasted* WM_COPYDATA message. You may have this from a badly designed third-party component in your own app! – Arnaud Bouchez Sep 02 '11 at 05:24
  • it duplicates what is passes in wParam and so is not needed – David Heffernan Sep 02 '11 at 06:41
  • 1
    Is not wParam a custom value when you broadcast a message? There is such a parameter in [the corresponding API](http://msdn.microsoft.com/en-us/library/windows/desktop/ms644932(v=vs.85).aspx) AFAIK. This is the reason of my precision - from experiment. Some kind of internal signature is not mandatory, but safer. – Arnaud Bouchez Apr 27 '13 at 12:40
0

It doesn't work anymore if you are using Windows 7. If you are using it, check this page to see how to add an exception: http://msdn.microsoft.com/en-us/library/ms649011%28v=vs.85%29.aspx

Whiler
  • 7,998
  • 4
  • 32
  • 56
  • Using Windows XP, but thanks for the heads up for the future. – Dan Kelly Aug 31 '11 at 14:03
  • 2
    -1 I'm sorry, but this is very misleading. `WM_COPYDATA` functions perfectly well on Vista and up, so long as the recipient process has an integrity level equal to or lower than that of the sender. – David Heffernan Aug 31 '11 at 14:27
  • I have to agree with David. I have personally used `WM_COPYDATA` with Windows 7... – Andreas Rejbrand Aug 31 '11 at 14:58
  • Ok... Sorry for my misunderstanding... I probably had this issue with different levels... – Whiler Sep 01 '11 at 10:51
  • On Vista+, if UIPI is blocking the message from being received from a lower integrity process and you don't want it to blocked, the receiver can use `ChangeWindowMessageFilter/Ex()` to allow the message through. – Remy Lebeau Jan 21 '19 at 04:31
0

I thought there was a problem with the (rh) handle being 0 when you call it, if the app needed to be started. But now I see that SendData calls itself recursively. I added a comment in the code for that, as it was non-obvious. But now there's another problem. The 2nd instance of SendData will have the right handle. But then you're going to pop out of that, back into the first instance where the handle is still 0, and then you WILL call SendMessage again, this time with a 0 handle. This probably is not the cause of your trouble, but it's unintended, unnecessary, and altogether bad. IMO, this is a case complicating things by trying to be too clever.

Chris Thornton
  • 15,620
  • 5
  • 37
  • 62
  • Thanks for pointing that out. I actually have an else in the original code before the send message, but it got missed out on the above example. – Dan Kelly Sep 01 '11 at 10:12