Link to home
Start Free TrialLog in
Avatar of Palamedes
PalamedesFlag for United States of America

asked on

WM_COPYDATA

Howdy all..

I am trying to write an application that communicates between itself and another application.  It does this through a handle.. and uses WM_COPYDATA.  I have never used this before and am unsure as to how it really works.

The original code was in C++ (written by some others) and it looks like this;

case WM_COPYDATA:
    {
    COPYDATASTRUCT *cds = (COPYDATASTRUCT *) lParam;
    if (NULL == cds->lpData)
         {
         SetWindowLong(hDlg, DWL_MSGRESULT, 0);
         return (TRUE);               // No data received; ask for disconnection.
         }
    //
    ExternalEventData *event_data = (ExternalEventData *)cds->lpData;
    //
    if (event_data->valid_fields & EEDF_TEXT_PRESENT)
         {
         OutputDebugString("Chat received is:");
         OutputDebugString(event_data->text_message);
         }
    //
    SetWindowLong(hDlg, DWL_MSGRESULT, 1);
    return (TRUE);

I asked on here for a conversion and dragonslayer came through with flying colors with this;

type
 TForm1 = class(TForm)
   ..
 protected
   procedure HandleWMCopyData(var Msg: TWMCopyData); message WM_COPYDATA;
 end;

and later to use it

procedure TForm1.HandleWMCopyData(var Msg: TWMCopyData);
var
 event_data: PExternalEventData;
begin
 if (Msg.CopyDataStruct.lpData = nil) then
   SetWindowLong(hDlg, DWL_MSGRESULT, 0)
 else begin
   event_data := PExternalEventData(Msg.CopyDataStruct.lpData);
   if Boolean(event_data.valid_fields and EEDF_TEXT_PRESENT) then
   begin
     OutputDebugString('...')
     SetWindowLong(hDlg, DWL_MSGRESULT, 1);
   end;
 end;
end;


Now... I don't know how to use that.. haha  I have the code working.. but it isn't pulling data from the other application like it should..

My question then is, where do I call this?  Or how do I call this?  Does this get automatically called when a bit of data is pushed down to the handle?  

For the life of me I can't figure this out.. Please help..
Avatar of DragonSlayer
DragonSlayer
Flag of Malaysia image

just to make sure that WM_COPYDATA is handled, perhaps you would like to just add a ShowMessage in the handler?

procedure TForm1.HandleWMCopyData(var Msg: TWMCopyData);
var
  event_data: PExternalEventData;
begin
  ShowMessage('Received WM_COPYDATA!');
  if (Msg.CopyDataStruct.lpData = nil) then
  begin
    ShowMessage('Received nil in the struct!');
    SetWindowLong(hDlg, DWL_MSGRESULT, 0);
  end else
  begin
    event_data := PExternalEventData(Msg.CopyDataStruct.lpData);
    if Boolean(event_data.valid_fields and EEDF_TEXT_PRESENT) then
    begin
      OutputDebugString('...')
      SetWindowLong(hDlg, DWL_MSGRESULT, 1);
    end;
  end;
end;
end;
ASKER CERTIFIED SOLUTION
Avatar of Member_2_248744
Member_2_248744
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of robert_marquardt
robert_marquardt

I just have written a tiny component for the Jedi VCL.
The window to send (with SendMessage!) the message to is Application.Handle. So your receiving app has to hand the handle of that window to the sending app.
The data to send must be a single block of memory not containing any pointers to other data.
The reason is that the pointer is only meaningful in the sending app. The receiving app has its own virtual address space. Windows copies the data from one address space to the other.

-----------------------------------------------------
unit JvCopyData;

interface

uses
  Windows, Messages, SysUtils, Classes, Forms;

type
  TJvCopyDataEvent = procedure(const Sender: HWND; const Data: TCopyDataStruct) of object;

  TJvCopyData = class(TComponent)
  private
    FCopyData: TJvCopyDataEvent;
    FData: TCopyDataStruct;
    function EventPipe(var Msg: TMessage): Boolean;
  protected
    procedure DoCopyData(Win: HWND; const Data: TCopyDataStruct);
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    property Data: TCopyDataStruct read FData;
  published
    property OnCopyData: TJvCopyDataEvent read FCopyData write FCopyData;
  end;

procedure Register;

implementation

constructor TJvCopyData.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FCopyData := nil;
  FillChar(FData, SizeOf(TCopyDataStruct), #0);
  Application.HookMainWindow(EventPipe);
end;

destructor TJvCopyData.Destroy;
begin
  FCopyData := nil;
  Application.UnhookMainWindow(EventPipe);
  if FData.lpData <> nil then
    FreeMem(FData.lpData);
  inherited Destroy;
end;

function TJvCopyData.EventPipe(var Msg: TMessage): Boolean;
var
  Data: PCopyDataStruct;
begin
  Result := Msg.Msg = WM_COPYDATA;
  if Result then
  begin
    if FData.lpData <> nil then
      FreeMem(FData.lpData);
    Data := PCopyDataStruct(Msg.LParam);
    FData.dwData := Data.dwData;
    FData.cbData := Data.cbData;
    GetMem(FData.lpData, Data.cbData);
    Move(Data.lpData^, FData.lpData^, Data.cbData);
    DoCopyData(HWND(Msg.WParam), FData);
  end;
end;

procedure TJvCopyData.DoCopyData(Win: HWND; const Data: TCopyDataStruct);
begin
  if Assigned(FCopyData) then
    FCopyData(Win, FData);
end;

procedure Register;
begin
  RegisterComponents('Samples', [TJvCopyData]);
end;

end.
----------------------------------
Avatar of Palamedes

ASKER

Thank you all for your help so far.. And Merry Christmas..

Okay here is where my confusion comes in..  Does the WM_COPYDATA function happen automatically when another application pushes data to it?  OR do I have to call it every now and then with my program? Set up a timer or loop or something?

The "sender" application is not my code.. but rather was written by another..  ( Here is his documentation: http://www.nicelycrafted.com/todc/main.aspx?a=3rdparty )

His code is in C++ and I am very much not a C++ programmer.. (hardly a Delphi programmer.. hehehe)  So I'm trying to convert his C++ code to Delphi 5.

As it is I can get my client to register with his program.. (his program has a method that allows me to check what programs are registered)  but I am not recieving anything.. I am sure its something I'm doing/not doing.. Thanks guys.
It works like any other message, except that the system has to do some additional work.
Aha, you hand over a window handle of your app. Best use Application.Handle for this because a forms window can be recreated sometimes.

typedef struct ExternalEventData
     {
     unsigned long     valid_fields;
     ExternalEventID     event_id;
     char          text_message[MAX_EED_TEXT_LENGTH];
     TODOBJECT          object_identifier;
     TODPOSITION     theatre_position;
     TODISLAND          island_identifier;
     TODPLAYER          player_identifier;
     } ExternalEventData;

type
  ExternalEventData = record
    valid_fields: Cardinal;
    event_id: ExternalEventID;
    text_message: array [0..MAX_EED_TEXT_LENGTH-1] of Char;
    object_identifier: TODOBJECT;
    theatre_position: TODPOSITION;
    island_identifier: TODISLAND;
    player_identifier: TODPLAYER;
  end;

I am not sure if it is packed record or not. I will check. If no info jis available then you should tell them to define it. I will convert the other records later.

In my component you will get an OnCopyData event. The Data parameter contains a pointer to an ExternalEventData record.
Slick812 I have played with your example there.. Thank you.. You have answered my question with your example..

DragonSlayer,  The ShowMessages aren't coming through at all which implies to me that I am never recieving a WM_COPYDATA from the sender application.  Frustrating because other people are.. that tells me that for whatever reason, my program isn't registering with the sender program.. even though the sender program lists my app.. Confusing still .. *sigh*

Robert, Thanks for you efforts.. To be honest with you.. You're a much better programmer than I am asI don't understand your code.. hehehe But I will go over it and see what I can gleen from it.. Thanks =)
???, , , , Can you put a test in your WM_COPYDATA message func to just tell you if you ever get a message, not get any data from it. . . .

procedure GetCopyData(var Msg: TWmCopyData); message WM_COPYDATA;


procedure TForm1.GetCopyData(var Msg: TWmCopyData);
begin
Msg.Result := 1;
Label1.Caption := 'Got a WM_COPYDATA message';
end;

and by the way, if you return a zero it says it wil disconect you
I did exactly that.. I never get a WM_COPYDATA from the sender.. which tells me that I am not registering myself with it correctly.. yet when I ask that sender what clients are registered, it lists my client.  And others have gotten it to work..
Well I finally got it working..

The problem was I was sending the sender application the wrong handle.

Application.handle was not the handle I needed to use, but rather my forms handle.  (Evidently they are different.. )

Thanks again everyone.
Thank you for good info too.

I faced the same problem.

Thank you to let's me know that WM_USER can't PostMessage
data record across the process.

I lost my time 2 days to discover this behavier.
---

By the way SendMessage WM_DATACOPY is too late for
keyboard HOOK because it place in thread message queue.