Delphi Inter Process Communication (IPC) using SendMessage

'

Recently I've been researching different ways of communication between two applications (IPC) written in Borland Delphi. There are many ways to reach an application from another one. The challenge is to find the best one for your situation and one of the possible solutions is to use a call to the Windows API function SendMessage.

Windows is built around messages. You can create and send messages to windows (or controls) within a Delphi application. In addition, applications send messages to each other, and applications even send messages to themselves.

The SendMessage API function requires 4 arguments. The first argument is the handle of the window to which the message is addressed; the second argument-usually a symbolic constant-is the numeric value of the message; the third and fourth arguments, traditionally named wParam and lParam, carry any additional information needed by the message-in this case, which margin should be set and its new width, respectively. When more than two values are needed, they are usually gathered in a structure and its address is sent in the lParam argument.

The first task here is to establish the communication. To do this the client needs to find the servers window (it needs its window handle). The best way for that is the FindWindow API function. The FindWindow function retrieves the handle to the top-level window whose class name and window name match the specified strings. As a target window we will use Application's window. Why? because the global variable Application, of type TApplication, is in every Delphi Windows application. Application encapsulates your application as well as providing many functions that occur in the background of the program. In this case we already know that the value of the first parameter of FindWindow will be 'TApplication'. The second is a window's title and it equal to Application.Title. Simple, isn't ?

My first solution was to use OnMessage event of TApplication class to trap the messages. However, this solution has a problem. The OnMessage event occurs when an application receives a Windows message. An OnMessage event handler allows an application to respond to messages other than those declared in the events for TApplication. Unfortunately, OnMessage only receives messages that are posted to the message queue, not those sent directly with the Windows API SendMessage function, so we need another way for it.

To solve this problem we can use one of the special methods of the TApplication class usually used internally for displaying Windows common dialogs. TApplication class has a special method called HookMainWindow that enables a non-VCL dialog box to receive messages sent to the application's main window (the window of TApplication, not main form of the application).

To encapsulate the complexities of messages handling I wrote a small component TpsvApplicationHook.

unit psvApplicationHook;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, 
  Dialogs;

type

  TOnHookMessage = procedure (Sender : TObject; 
                              var Message : TMessage; 
                              var Handled : boolean) 
                              of object;

  THookItem = class(TCollectionItem)
  private
    FHook : TWindowHook;
    FOnMessage : TOnHookMessage;
  protected
    function HookProc(var Message : TMessage) : boolean;
  public
    constructor Create(Collection : TCollection); override;
  published
    property OnMessage : TOnHookMessage 
          read FOnMessage 
          write FonMessage;
  end;

  THookItems = class(TCollection)
  private
    FOwner : TPersistent;
  protected
    function GetItem(Index : integer) : THookItem;
    procedure SetItem(Index : integer; Value : THookItem);
    function GetOwner : TPersistent; override;
  public
    constructor Create(AOwner : TPersistent; ItemClass 
        : TCollectionItemClass);
    function Add : THookItem;
    property Items[Index : integer] : THookItem 
        read GetItem 
        write SetItem; default;
  end;

  TpsvApplicationHook = class(TComponent)
  private
    FItems : THookItems;
    procedure SetItems(const Value: THookItems);
  protected
  public
    constructor Create(AOwner : TComponent); override;
    destructor Destroy; override;
    procedure Activate;
    procedure Deactivate;
  published
    property Items : THookItems read FItems write SetItems;
  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('Additional', [TpsvApplicationHook]);
end;

{ THookItem }

constructor THookItem.Create(Collection: TCollection);
begin
  inherited;
  FHook := HookProc;
end;

function THookItem.HookProc(var Message: TMessage): boolean;
begin
  Result := false;
  if Assigned(FOnMessage) then
     FOnMessage(THookItems(Collection).FOwner, Message, Result);
end;

{ TpsvApplicationHook }

procedure TpsvApplicationHook.Activate;
var
 cnt : integer;
begin
  if Assigned(Application) then
  for cnt := 0 to FItems.Count - 1 do
   begin
     Application.HookMainWindow(FItems[cnt].FHook);
   end;
end;

constructor TpsvApplicationHook.Create(AOwner: TComponent);
begin
  inherited;
  FItems := THookItems.Create(Self, THookItem);
end;

procedure TpsvApplicationHook.Deactivate;
var
 cnt : integer;
begin
  if Assigned(Application) then
  for cnt := 0 to FItems.Count - 1 do
   begin
     Application.UnHookMainWindow(FItems[cnt].FHook);
   end;
end;

destructor TpsvApplicationHook.Destroy;
begin
  if (not (csDesigning in ComponentState) ) then
   Deactivate;
  FItems.Free;
  inherited;
end;

procedure TpsvApplicationHook.SetItems(const Value: THookItems);
begin
  FItems.Assign(Value);
end;

{ THookItems }

function THookItems.Add: THookItem;
begin
  Result := THookItem(inherited Add);
end;

constructor THookItems.Create(AOwner: TPersistent;
  ItemClass: TCollectionItemClass);
begin
  inherited Create(ItemClass);
  FOwner := AOwner;
end;

function THookItems.GetItem(Index: integer): THookItem;
begin
  Result := THookItem(inherited GetItem(Index));
end;

function THookItems.GetOwner: TPersistent;
begin
  Result := FOwner;
end;

procedure THookItems.SetItem(Index: integer; Value: THookItem);
begin
  inherited SetItem(Index, Value);
end;

end.
Okay, down to business. Sometimes would be very useful to centralize processing of the events that apply to the application as a whole. I will show how we can use TpsvApplicationHook in standalone Delphi application to create own "processing center".
unit StandaloneExample;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, 
  Dialogs, StdCtrls, psvApplicationHook;

type
  TfrmHookTest = class(TForm)
    psvApplicationHook: TpsvApplicationHook;
    btnSendMessage: TButton;
    btnActivateHook: TButton;
    btnDeactivateHook: TButton;
    procedure ProcessHookMessage(Sender: TObject;
      var Message: TMessage; var Handled: Boolean);
    procedure btnSendMessageClick(Sender: TObject);
    procedure btnActivateHookClick(Sender: TObject);
    procedure btnDeactivateHookClick(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  frmHookTest: TfrmHookTest;

implementation

{$R *.DFM}

const
  WM_MY_MESSAGE = WM_USER +1;

procedure TfrmHookTest.ProcessHookMessage(Sender: TObject;
  var Message: TMessage; var Handled: Boolean);
begin
  if Message.Msg = WM_MY_MESSAGE then
   begin
     ShowMessage('I received a message!');
   end;
end;

procedure TfrmHookTest.btnSendMessageClick(Sender: TObject);
begin
  SendMessage(Application.Handle, WM_MY_MESSAGE, 0, 0);
end;

procedure TfrmHookTest.btnActivateHookClick(Sender: TObject);
begin
  psvApplicationHook.Activate;
end;

procedure TfrmHookTest.btnDeactivateHookClick(Sender: TObject);
begin
   psvApplicationHook.Deactivate;
end;

end.
Next example shows communication between two applications. Client application will send custom message messages to server and Server will receive and process it using TpsvApplicationHook component. Here is the code of the server application:
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  psvApplicationHook, StdCtrls;

type
  TfrmTestServer = class(TForm)
    psvApplicationHook: TpsvApplicationHook;
    LogMemo: TMemo;
    procedure ProcessCustomMessage(Sender: TObject; var Message: TMessage;
      var Handled: Boolean);
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
    MyMsg : UINT;
  end;

var
  frmTestServer: TfrmTestServer;

implementation

{$R *.DFM}

procedure TfrmTestServer.ProcessCustomMessage(Sender: TObject;
  var Message: TMessage; var Handled: Boolean);
begin
  if Message.Msg = MyMsg then
   begin
     LogMemo.Lines.Add(Format('%s : new message was received', [DateTimeToStr(Now)]));
     Handled := true;
   end;
end;


procedure TfrmTestServer.FormCreate(Sender: TObject);
var
 NewHook : THookItem;
begin
  MyMsg := RegisterWindowMessage('MyMessage');
  NewHook := psvApplicationHook.Items.Add;
  NewHook.OnMessage := ProcessCustomMessage;
  psvApplicationHook.Activate;
end;

end.
As a last step we have to create a client application that will send custom messages to the server application.
unit ClientMainForm;

interface

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

type
  TfrmClient = class(TForm)
    btnSendMessage: TButton;
    procedure FormCreate(Sender: TObject);
    procedure btnSendMessageClick(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
    MyMsg : UINT;
    ServerApplicationHandle : THandle;
  end;

var
  frmClient: TfrmClient;

implementation

{$R *.DFM}

procedure TfrmClient.FormCreate(Sender: TObject);
begin
  MyMsg := RegisterWindowMessage('MyMessage');
  ServerApplicationHandle := FindWindow('TApplication', 'Project1');
end;

procedure TfrmClient.btnSendMessageClick(Sender: TObject);
begin
  SendMessage(ServerApplicationHandle, MyMsg, 0, 0);
end;

end.

This tutorial was kindly provided by serge perevoznyk


Google
Web www.Delphi-Central.com
Delphi Central - Delphi Programming Tutorials, Hints and Tips