Weird behavior on SendMessage between Delphi 10.4 (sender) and Delphi 6 (receiver)

122 Views Asked by At

A weird thing happens when sending a string from a Delphi 10.4 app to a Delphi 6 app: only the first character is caught on the receiver.

I already found some other topics on the web about this issue, but I couldn't solve it.

Here's the code:

Sender - Delphi 10.4

procedure TfrmMain.sendMessageToApp(psValue: string);
var
  DataStruct: CopyDataStruct;
begin
  DataStruct.dwData := 0;
  DataStruct.cbData := SizeOf(PChar(psValue));
  DataStruct.lpData := PChar(psValue);

  SendMessage(
    FindWindow(nil, PChar(Self.psFormTitleERP)),
    WM_COPYDATA, Self.Handle, Integer(@DataStruct));

end;

Receiver - Delphi 6

procedure TForm1.WMCopyData(var Msg: TWMCopyData);
var
  psMessage: String;
begin
  psMessage := PChar(Msg.CopyDataStruct.lpData);
  ShowMessage(psMensagem);  
end;

One interesting thing I saw is a difference on tagCOPYDATASTRUCT between the two versions of Delphi: the dwData parameter is DWORD for Delphi 6, and ULONG_PTR for Delphi 10.4.

So... I have no idea what's wrong with these routines. Can anyone help me?

I think it could be some data conversion error, but I'm not so sure - I'm not used to manipulating bytes this deep.

1

There are 1 best solutions below

6
Andreas Rejbrand On

Wrong size

First you should read the documentation for the WM_COPYDATA message and the COPYDATASTRUCT structure. According to the latter, the cbData member should be the number of bytes in the data pointed to by the lpData member.

You send SizeOf(PChar(psValue)) which is SizeOf(Pointer) which is 4 or 8, depending on whether your app is 32-bit or 64-bit.

But you should instead send the length of the data, which is (Length(psValue) + 1) * SizeOf(Char).

Before Delphi 2009, strings were ANSI (1 byte per char). In Delphi 2009 and later, strings are Unicode (2 bytes per char). So the factor to the right is either 1 or 2.

The + 1 is because you probably also want to send the NULL character at the end of the string (assuming the string you want to send is not empty).

64-bit pointers

Further, in a 64-bit app (which your 10.4 app may or may not be), a pointer is 64 bit wide, so it won't fit in an Integer (still 32 bit). So in a 64-bit app, Integer(@DataStruct) is clearly wrong; you discard the upper 32 bits. It's like your phone number is 1234 5678 but you tell me that it is 0000 5678; I won't be able to call you.

You want LPARAM(@DataStruct). (NativeInt and a few others will also work; the important thing is that you get right pointer size.)

String types

Again, recall that strings became Unicode (2 bytes per char) in Delphi 2009.

So you send a Unicode string, but the receiver expects an ANSI string.

The simplest solution is to send an ANSI string. To do this, use AnsiString and PAnsiChar instead of string and PChar.

Finding the right window

You really should verify that you actually have found the right window. If you don't find it, you likely want to handle that scenario in some appropriate way.

I'd personally match on both class name and window name, to make it a bit safer. Of course, there may be other windows with the same name on the end user's desktop. You may also want to consider the case when there are several (more than one) matching windows.

At the receiver

At the receiver, PChar(Msg.CopyDataStruct.lpData) will work fine since we do have the terminating NULL character in the lpData buffer (or the pointer is nil).

It is also good practice to look at the cbData parameter to make sure that you don't read beyond the end of the buffer.

One possible solution is to use the SetString function:

SetString(psMessage, PChar(Msg.CopyDataStruct.lpData), Msg.CopyDataStruct.cbData - 1)

But if so, please first check that lpData isn't nil (why?).

(If you base the receiver's behaviour on cbData, you can also omit sending the NULL terminator in the first place. Clearly, there are many ways to do this, each with its own security implications. Really, we could write a book on this topic!)