LessStress
LessStress

Reputation: 187

SendMessage(WM_COPYDATA) + Record + String

I want to send a record, that right now have only a string on it, but I will add more variables. Is the first time I work with records, so this maybe is a silly question. But, why this works:

type
  TDataPipe = record
    WindowTitle: String[255];
  end;

var
  Data: TDataPipe;
  copyDataStruct : TCopyDataStruct;
begin
  Data.WindowTitle:= String(PChar(HookedMessage.lParam));
  copyDataStruct.dwData := 0;
  copyDataStruct.cbData := SizeOf(Data);
  copyDataStruct.lpData := @Data;
  SendMessage(FindWindow('TForm1', nil), WM_COPYDATA, Integer(hInstance), Integer(@copyDataStruct));    
end;

Receiving side:

type
  TDataPipe = record
    WindowTitle: String[255];
  end;

procedure TForm1.WMCopyData(var Msg: TWMCopyData);
var
  sampleRecord : TDataPipe;
begin
  sampleRecord.WindowTitle:= TDataPipe(Msg.CopyDataStruct.lpData^).WindowTitle;
  Memo1.Lines.Add(sampleRecord.WindowTitle);
end;

Why if on the record, I use:

WindowTitle: String; //removed the fixed size

and on the sending side I use:

Data.WindowTitle:= PChar(HookedMessage.lParam); //removed String()

it simply doesn't go?

I get access violations / app freeze...

The scenario is: sending side is a DLL hooked using SetWindowsHookEx, receiving side a simple exe that loaded / called SetWindowsHookEx...

Upvotes: 5

Views: 5595

Answers (2)

IceCold
IceCold

Reputation: 21134

Preparation:

procedure TMainForm.CreateParams(var Params: TCreateParams);
begin
 inherited;
 StrCopy(Params.WinClassName, PChar(SingleInstClassName)); // Copies a null-terminated string. StrCopy is designed to copy up to 255 characters from the source buffer into the destination buffer. If the source buffer contains more than 255 characters, the procedure will copy only the first 255 characters.
end;

Sender:

procedure TAppData.ResurectInstance(Arg: string);
VAR
   Window: HWND;
   DataToSend: TCopyDataStruct;
begin
  Arg:= Trim(Arg);

  { Prepare the data you want to send }
  DataToSend.dwData := CopyDataID;  // CopyDataID = Unique ID for my apps
  DataToSend.cbData := Length(Arg) * SizeOf(Char);
  DataToSend.lpData := PChar(Arg);

  { We should never use PostMessage() with the WM_COPYDATA message because the data that is passed to the receiving application is only valid during the call. Finally, be aware that the call to SendMessage will not return until the message is processed.}
  Window:= WinApi.Windows.FindWindow(PWideChar(SingleInstClassName), NIL);    // This is a copy of cmWindow.FindTopWindowByClass
  SendMessage(Window, WM_COPYDATA, 0, LPARAM(@DataToSend));
end;

Receiver:

procedure TMainForm.WMCopyData(var Msg: TWMCopyData);
VAR
   FileName: string;
begin
 { Receives filename from another instance of this program }
 if  (Msg.CopyDataStruct.dwData = AppData.CopyDataID) { Only react on this specific message }
 AND (Msg.CopyDataStruct.cbData > 0)                  { Do I receive an empty string? }
 then
  begin
    SetString(FileName, PChar(Msg.CopyDataStruct.lpData), Msg.CopyDataStruct.cbData div SizeOf(Char));
    msg.Result:= 2006;                                { Send something back as positive answer }

    AppData.Restore;  

   ...
  end
 else
   inherited;
end;

Upvotes: 0

Remy Lebeau
Remy Lebeau

Reputation: 595742

A String[255] is a fixed 256-byte block of memory, where the character data is stored directly in that memory. As such, it is safe to pass as-is across process boundaries without serialization.

A String, on the other hand, is a dynamic type. It just contains a pointer to character data that is stored elsewhere in memory. As such, you can't pass a String as-is across process boundaries, all you would be passing is the pointer value, which has no meaning to the receiving process. You have to serialize String data into a flat format that can safely by passed to, and deserialized by, the receiving process. For example:

Sending side:

type
  PDataPipe = ^TDataPipe;
  TDataPipe = record
    WindowTitleLen: Integer;
    WindowTitleData: array[0..0] of Char;
    //WindowTitleData: array[0..WindowTitleLen-1] of Char;
  end;

var
  Wnd: HWND;
  s: String;
  Data: PDataPipe;
  DataLen: Integer;
  copyDataStruct : TCopyDataStruct;
begin
  Wnd := FindWindow('TForm1', nil);
  if Wnd = 0 then Exit;

  s := PChar(HookedMessage.lParam);
  DataLen := SizeOf(Integer) + (SizeOf(Char) * Length(s));
  GetMem(Data, DataLen);
  try
    Data.WindowTitleLen := Length(s);
    StrMove(Data.WindowTitleData, PChar(s), Length(s));

    copyDataStruct.dwData := ...; // see notes further below
    copyDataStruct.cbData := DataLen;
    copyDataStruct.lpData := Data;
    SendMessage(Wnd, WM_COPYDATA, 0, LPARAM(@copyDataStruct));    
  finally
    FreeMem(Data);
  end;
end;

Receiving side:

type
  PDataPipe = ^TDataPipe;
  TDataPipe = record
    WindowTitleLen: Integer;
    WindowTitleData: array[0..0] of Char;
    //WindowTitleData: array[0..WindowTitleLen-1] of Char;
  end;

procedure TForm1.WMCopyData(var Msg: TWMCopyData);
var
  Data: PDataPipe;
  s: string;
begin
  Data := PDataPipe(Msg.CopyDataStruct.lpData);
  SetString(s, Data.WindowTitleData, Data.WindowTitleLen);
  Memo1.Lines.Add(s);
end;

That being said, in either situation, you really should be assigning your own custom ID number to the copyDataStruct.dwData field. The VCL itself uses WM_COPYDATA internally, so you don't want to get those messages confused with yours, and vice versa. You can use RegisterWindowMessage() to create a unique ID to avoid conflicts with IDs used by other WM_COPYDATA users:

var
  dwMyCopyDataID: DWORD;

...

var
  ...
  copyDataStruct : TCopyDataStruct;
begin
  ...
  copyDataStruct.dwData := dwMyCopyDataID;
  ...
end;

...

initialization
  dwMyCopyDataID := RegisterWindowMessage('MyCopyDataID');

var
  dwMyCopyDataID: DWORD;

...

procedure TForm1.WMCopyData(var Msg: TWMCopyData);
var
  ...
begin
  if Msg.CopyDataStruct.dwData = dwMyCopyDataID then
  begin
    ...
  end else
    inherited;
end;

...

initialization
  dwMyCopyDataID := RegisterWindowMessage('MyCopyDataID');

Lastly, the WPARAM parameter of WM_COPYDATA is an HWND, not an HINSTANCE. If the sender does not have its own HWND, just pass 0. Do not pass your sender's HInstance variable.

Upvotes: 10

Related Questions