0
votes

I try to exchange data between two applications in windows. I use an example from Zarko Gajic. It uses windows messaging and the example works great. There are a sender and a receiving application and some shared data: all coded for VCL. The code is shown below.

unit SenderMain;

{ How to send information (String, Image, Record) between two Delphi applications
http://delphi.about.com/od/windowsshellapi/a/wm_copydata.htm

Learn how to send the WM_CopyData message between two Delphi
applications to exchange information and make two applications
communicate. The accompanying source code demonstrates how to
send a string, record (complex data type) and even graphics
to another application.

~Zarko Gajic
About Delphi Programming
http://delphi.about.com
}
interface

uses
   Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
   Dialogs, StdCtrls, ExtCtrls, TlHelp32,
   shared_data;

type
  TSenderMainForm = class(TForm)
    Button_Send_Data: TButton;
    Log: TListBox;

    procedure Button_Send_DataClick   (Sender: TObject);

   protected
      procedure Loaded; override;
      procedure SendString (send_string: aString);
   end; // Class: TSenderMainForm //

var
   SenderMainForm: TSenderMainForm;

implementation

{$R *.dfm}

{**************** NextWindow ****************}
function NextWindow (wnd: Thandle; list: Tstringlist):boolean; stdcall;
{This is the callback function which is called by EnumWindows procedure
 for each top-level window.  Return "true" to keep retrieving, return
 "false" to stop EnumWindows  from calling}
var
   title: array [0..255] of char;
   receiverHandle: HWND;//THandle;
   win_name: PChar;
   s: AnsiString;
begin
   getwindowtext (wnd, title, 256);
   s := AnsiString (pchar(@title));
   if (s <> '') and (list.indexof (string (s)) < 0) then
   begin
      win_name := PaString (s);
      receiverHandle := FindWindow (win_name, nil); // Find receiving app
      s := AnsiString (Format ('%s (%d)', [s, receiverHandle]));
      list.add (string (s));
   end; // if
   result:=true;
end;

procedure TSenderMainForm.Loaded;
begin
   inherited Loaded;

   enumwindows (@nextwindow, lparam (Log.Items)); {pass the list as a parameter}
end;

procedure TSenderMainForm.SendString (send_string: aString);
var copyDataStruct: TCopyDataStruct; { Declared in Windows.pas: TCopyDataStruct}
    receiverHandle: THandle;
    res: integer;
begin
// Copy string to CopyDataStruct
   copyDataStruct.dwData := 1; //use it to identify the message contents
   copyDataStruct.cbData := (1 + Length (send_string)) * SizeOf (Char);
   copyDataStruct.lpData := PaString (send_string);

   receiverHandle := FindWindow (PaString (cClassName), nil); // Find receiving app
   if receiverHandle = 0 then // not found
   begin
      Log.Items.Add ('CopyData Receiver NOT found!');
   end else // found, send message
   begin
      res := SendMessage (receiverHandle, WM_COPYDATA, Integer(Handle), Integer(@copyDataStruct));
      Log.Items.Add (Format ('String sent, len = %d, result = %d', [copyDataStruct.cbData, res]));
      Log.Items.Add ('"' + PaString (copyDataStruct.lpData) + '"');
   end; // if
end; // SendString

procedure TSenderMainForm.Button_Send_DataClick (Sender: TObject);
begin
   SendString (ParamStr (0));
end;
    ====================== Unit copyDataReceiver ================
unit ReceiverMain;
{ How to send information (String, Image, Record) between two Delphi applications
http://delphi.about.com/od/windowsshellapi/a/wm_copydata.htm }
interface

uses
   Windows, Messages,
   SysUtils, Variants, Classes, Graphics, Controls, Forms,
   Dialogs, StdCtrls, ExtCtrls, shared_data;

type
  TReceiverMainForm = class (TForm)
    Log: TListBox;

    procedure FormCreate(Sender: TObject);

  private
    procedure WMCopyData (var Msg: TWMCopyData); message WM_COPYDATA;
    procedure WMSignalClose (var Msg: TMessage);  message WM_SIGNAL_CLOSE;

    procedure HandleCopyDataString (copyDataStruct: PCopyDataStruct);
  end;

var ReceiverMainForm: TReceiverMainForm;

implementation

{$R *.dfm}

procedure TReceiverMainForm.FormCreate (Sender: TObject);
begin
  Log.Clear;
end;

procedure TReceiverMainForm.WMSignalClose (var Msg: TMessage);
var pfn: PaString;
    fn: aString;
begin
   Log.Items.Add (Format ('Signal received, WParam = %d, LParam = %d', [Msg.WParam, Msg.LParam]));
   pfn := PaString (Msg.LParam);
   fn  := aString (pfn);
   Log.Items.Add (fn);
end;

procedure TReceiverMainForm.WMCopyData (var Msg: TWMCopyData);
var copyDataType: Int32;
begin
   copyDataType := Msg.CopyDataStruct.dwData;

   //Handle of the Sender
   Log.Items.Add (Format ('WM_CopyData (type: %d) from: %d', [copyDataType, msg.From]));
   HandleCopyDataString (Msg.CopyDataStruct);

   //Send something back
   msg.Result := Log.Items.Count;
end;

procedure TReceiverMainForm.HandleCopyDataString (copyDataStruct: PCopyDataStruct);
var mess: aString;
begin
   mess := aString (PaString (copyDataStruct.lpData));
   Log.Items.Add (Format ('Received string of length %d at %s', [Length (mess), DateToStr (Now)]));
   Log.Items.Add ('"' + mess + '"');
end;

end.
    ================ unit shared_data ==========================
unit shared_data;

interface

uses Messages;

const
   WM_SIGNAL_CLOSE = WM_APP + 2012;
   ARG_AMI_1       = 285;
   ARG_AMI_2       = 1;
   cClassName      = 'TReceiverMainForm';

type
   aString  = string;
   PaString = PChar;

implementation

end.

The crux of the sender application is that it sends a WM_COPYDATA to the receiver. In order to find the receiver, FindWindow is used with the name of the receiving application (hard-coded) which returns a handle to the window. If the handle is zero, an error is shown.

When I duplicate this in an FMX application there are troubles. The FMX receiving part does not work, while the VCL receiver can receive messages from either the VCL sender or the FMX sender. The code of the FMX receiver is shown below.

Because I wasn't sure about the name of the windows I enumerated all windows, added the numeric handle to each window name and showed it in a listbox in the sender. All handles are zero. I have two questions:

  1. Why are all handles zero in the enumeration?
  2. Why can't I send a message to the FMX receiving applation?

Any help would be greatly appreciated.

unit copyDataReceiver;

interface

uses
   System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
   FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Layouts, FMX.ListBox,
   Windows, Messages, shared_data;

type
   TReceiverMainForm = class (TForm)
    Log: TListBox;

    procedure FormCreate(Sender: TFMXObject);

  private
    procedure WMCopyData (var Msg: TWMCopyData); message WM_COPYDATA;
    procedure WMSignalClose (var Msg: TMessage);  message WM_SIGNAL_CLOSE;

    procedure HandleCopyDataString (copyDataStruct: PCopyDataStruct);
  end;

var ReceiverMainForm: TReceiverMainForm;

implementation

{$R *.fmx}

procedure TReceiverMainForm.FormCreate (Sender: TFMXObject);
begin
  Log.Clear;
end;

procedure TReceiverMainForm.WMSignalClose (var Msg: TMessage);
var pfn: PaString;
    fn: aString;
begin
   Log.Items.Add (Format ('Signal received, WParam = %d, LParam = %d', [Msg.WParam, Msg.LParam]));
   pfn := PaString (Msg.LParam);
   fn  := aString (pfn);
   Log.Items.Add (fn);
end;

procedure TReceiverMainForm.WMCopyData (var Msg: TWMCopyData);
var copyDataType: Int32;
begin
   copyDataType := Msg.CopyDataStruct.dwData;

   //Handle of the Sender
   Log.Items.Add (Format ('WM_CopyData (type: %d) from: %d', [copyDataType, msg.From]));
   HandleCopyDataString (Msg.CopyDataStruct);

   //Send something back
   msg.Result := Log.Items.Count;
end;

procedure TReceiverMainForm.HandleCopyDataString (copyDataStruct: PCopyDataStruct);
var mess: aString;
begin
   mess := aString (PaString (copyDataStruct.lpData));
   Log.Items.Add (Format ('Received string of length %d at %s', [Length (mess), DateToStr (Now)]));
   Log.Items.Add ('"' + mess + '"');
end;

end.
2

2 Answers

1
votes

FindWindow works just the same under FMX. The problem is that sending messages to the window that you find will not result in them being routed to the form's message handlers.

Instead you should do what you should always have done, even with the VCL. That is use a known window whose lifetime you control. Remember that VCL windows are subject to recreation. In other words, you might have a window handle for a window in another process, but that window may be destroyed before you get a chance to send your message to it.

Resolve this by using AllocateHWnd or CreateWindow to create a window that will not be recreated. A window whose lifetime you control. You'll have to devise a way for the other process to discover your window. Personally I would use CreateWindow with a known class name, and then enumerate top level windows with EnumWindows looking for windows that that class name.

1
votes
  1. Why can't I send a message to the FMX receiving applation?

For VCL-Forms the ClassName is derived from the name of the form by simply adding a leading 'T' to then Name. e.g. If you have a Form named MyForm the ClassName is TMyForm. Self.ClassName returns this name and a call to Winapi.Windows.FindWindow(PChar(Self.ClassName), nil) returns the correct Handle.

With FMX-Forms you will receive a ClassName builded in similar way. For FMX-Forms the ClassName is derived from the name of the form by adding leading 'FMT' to the name of the Form. The ClassName returned by Self.ClassName, however, is the same as for VCL-Forms.

e.g. If you have a Form named MyFMXForm the ClassName is FMTMyFMXForm but Self.ClassName returns TMyFMXForm. Therefore an attempt to get the window-handle with that ClassName fails. The correct call is Winapi.Windows.FindWindow(PChar('FMTMyFMXForm'), nil)); .