Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SendMessage(WM_COPYDATA) + Record + String

Tags:

delphi

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...

like image 383
LessStress Avatar asked Feb 19 '16 22:02

LessStress


1 Answers

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.

like image 94
Remy Lebeau Avatar answered Nov 15 '22 05:11

Remy Lebeau