3

I'm trying to write a component, to send string messages between applications by WM_COPYDATA. I'd like trap the WM_COPYDATA, but this doesn't work:

TMyMessage = class(TComponent)
private
{ Private declarations } 
…
protected
{ Protected declarations }
…
procedure WMCopyData(var Msg : TMessage); message WM_COPYDATA;
…
end;

Searching Google a lot, found some reference using wndproc. I tried it, but it isn't working either.

TMyMessage = class(TComponent)
…
protected
{ Protected declarations }
…
procedure WMCopyData(var Msg : TMessage); message WM_COPYDATA;
procedure WndProc(var Msg: TMessage);
…
end;
…
procedure TMyMessage.WndProc(var Msg: TMessage);
begin
  //inherited;
  if Msg.Msg = WM_COPYDATA then
    WMCopyData(Msg);
end;

Please help, what is wrong?

Jolta
  • 2,620
  • 1
  • 29
  • 42
Göme206
  • 31
  • 2

3 Answers3

3

What you have so far is fine, but you need to arrange for messages to be delivered to your component in the first place. That requires a window handle. Call AllocateHWnd and pass it your component's WndProc method. It will return a window handle, which you should destroy as your component is destroyed.

constructor TMyMessage.Create(AOwner: TComponent);
begin
  inhreited;
  FHandle := AllocateHWnd(WndProc);
end;

destructor TMyMessage.Destroy;
begin
  DeallocateHWnd(FHandle);
  inherited;
end;

Rather than testing for each message directly, you can let TObject do that for you. That's what the Dispatch method is for. Pass it a TMessage record, and it will find and call the corresponding message-handler method for you. If there is no such handler, it will call DefaultHandler instead. Override that can call DefWindowProc.

procedure TMyMessage.WndProc(var Message);
begin
  Dispatch(Message);
end;

procedure TMyMessage.DefaultHandler(var Message);
begin
  TMessage(Message).Result := DefWindowProc(Self.Handle, TMessage(Message).Msg,
    TMessage(Message).WParam, TMessage(Message).LParam);
end;
Rob Kennedy
  • 161,384
  • 21
  • 275
  • 467
  • Why implement `WndProc` that way if all the component does is look for `WM_COPYDATA`? – David Heffernan Feb 01 '12 at 15:09
  • 1
    No particular reason, @David. I saw the question implement `WndProc` and defer handling to the `WMCopyData` method, so I showed an alternative that doesn't require manually checking for message IDs. When there's just one message, there's not much point to writing a separate `WMCopyData` method in the first place, so the `Dispatch` and `DefaultHandler` methods aren't useful. – Rob Kennedy Feb 01 '12 at 15:29
1

Your problem is that TComponent is not a windowed component. WM_COPYDATA is a windows message and is delivered via a window procedure. Hence you need a window handle. Use AllocateHwnd to get hold of one of these.

type
  TMyComponent = class(TComponent)
  private
    FWindowHandle: HWND;
    procedure WndProc(var Msg: TMessage);
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  end;

constructor TMyComponent.Create(AOwner: TComponent);
begin
  inherited;
  FWindowHandle := AllocateHwnd(WndProc);
end;

destructor TMyComponent.Destroy;
begin
  DeallocateHwnd(FWindowHandle);
  inherited;
end;

procedure TMyComponent.WndProc(var Msg: TMessage);
begin
  if Msg.Msg=WM_COPYDATA then
    //do domething
  else
    Msg.Result := DefWindowProc(FWindowHandle, Msg.Msg, Msg.WParam, Msg.LParam);
end;

Whatever is sending the messages will need to find a way to get hold of the window handle.

Zoë Peterson
  • 13,094
  • 2
  • 44
  • 64
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • Ok, i will allocate window hadle, but it will be able to trap the WM_COPYDATA, or i have to send the message directly to the component? – Göme206 Feb 01 '12 at 14:59
  • I don't understand that question. My assumption is that you have two processes involved. Is that the case? – David Heffernan Feb 01 '12 at 15:01
  • I *think* what Göme isn't telling us is that the intention is for this component to *intercept* messages sent somewhere else, such as the form that this component assumes it's being placed on. But if that's the case, I don't know why the component user would use the component instead of just writing a wm_CopyData handler directly. – Rob Kennedy Feb 01 '12 at 15:09
  • No, I don't want intercept anything, just sending message between two applications to control each other. It's only attempting to realize the technology. I tought I write a component to send and receive messages, put it on the form, and handling the messages. If I trap the message on the form, it works fine, but from the component I couldn't do. Thank you for the answers, and sorry for my english. :) – Göme206 Feb 01 '12 at 15:28
  • 1
    A component doesn't magically know about all the messages sent to some other component, not even its owner. So, Göme, if you want this component to handle messages sent to a form without the form having to take any special action, then you *do* want to intercept messages. The alternative is what David and I have shown, which is to send the messages directly to the component's window handle and leave the form out of it entirely. – Rob Kennedy Feb 01 '12 at 15:31
  • 1
    @David, make sure you always call DefWindowProc in AllocateHwnd handlers. http://blogs.msdn.com/b/oldnewthing/archive/2006/04/25/583093.aspx – Zoë Peterson Feb 01 '12 at 17:00
  • Thank you for all the answers. :) – Göme206 Feb 01 '12 at 21:09
1

I did it this way:

My web modules which are running in a thread need to send strings to a memo on the main form. FReceiverFromWS is a THandle

On create:

procedure TWebModuleWebServices.WebModuleCreate(Sender: TObject);
begin
   FReceiverFromWS := FindWindow(PChar('TFormWebServices'),PChar(cFormCaption + FormWebServices.Instance)); // Search by class name and caption of receiving form
   // ==> you could to that without form caption, but I need to distinguish between running services
   if FReceiverFromWS = 0 then
      begin
         Assert(False,'CopyData receiver NOT found!');  // Probably TFormWebServices not yet created
         Exit;
      end;
end;

To send messages:

procedure TWebModuleWebServices.SendAMessage(Msg: String);
// Windows will guarantee that the data sent in the COPYDATASTRUCT will exist until     after the WM_COPYDATA message
// has been carried out. As such, we must use SendMessage() to send a WM_COPYDATA message. We cannot use PostMessage().
var
   lCopyDataStruct: TCopyDataStruct;
begin
   lCopyDataStruct.dwData := 0;
   lCopyDataStruct.cbData := 1 + Length(Msg);
   lCopyDataStruct.lpData := PChar(Msg);
   SendMessage(FReceiverFromWS, WM_COPYDATA, wParam(FReceiverFromWS), lParam(@lCopyDataStruct));
end;

In the main form, public method

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

is:

procedure TFormWebServices.WMCopyData(var Msg: TWMCopyData);
var
   i : integer;
   s : string;
begin
   i := Msg.CopyDataStruct.dwData;
   case i of
      0: begin  // Message to display
            s := String(PChar(Msg.CopyDataStruct.lpData));
            AddMemoLine(s);
         end;
      1: begin  // Statistical data
            s := String(PChar(Msg.CopyDataStruct.lpData));
            FrmWebServiceStats.CollectStats(s);
         end;
   end;
end;

(As you can see, I actually use dwData to signal the kind of message and handle these differently)

Jan Doggen
  • 8,799
  • 13
  • 70
  • 144