Implementing Callbacks to a Datasnap Server Interface by Xavier Pacheco

By: Xavier Pacheco

Abstract: Add the ability for a Datasnap Server to call your client application through a callback.

Implementing Callbacks to a Datasnap Server Interface

By Xavier Pacheco

Having the ability to invoke a client-side method from a Datasnap server can be useful in several circumstances. For instance, from the server, you could invoke an unattended client shutdown that would gracefully disconnect from the server and save any state information. While it is relatively simple to communicate with a Datasnap server from the client, calling the client from the server takes a little more work. Nevertheless, the procedure is also simple once you understand it. In this article, I will demonstrate the steps to implement this functionality.

This example will be very basic, that is, I will not demonstrate functionality beyond the callback implementation. I have included the sample application with this article that you can download at CodeCentral.

Setting up the Server Type Library

When setting up the server, I first modified the type library from the Type Library Editor by adding another IDispatch interface that will define the callback for both the server and client. I did this by selecting "View | Type Library" from the Delphi main menu and by selecting the "New Interface" speed button . Figure 1 shows the type library for my example application after having added the additional interface which I named, IClientCallback. Note the the Parent Interface for IClientCallback is IDispatch.

Figure 1. Type Library for Datasnap Server Demo

You'll also see that I have added the method, AssignCallback to the remote data module interface, IxwCallBackRDM, and the method CallbackMethod to the IClientCallback interface. AssignCallback is the method that the client will call, passing its callback interface as a parameter to the server - more on this later. CallBackMethod is the client callback method that the server will invoke. See Figures 2 and 3 to examine the parameters for the AssignCallback and CallBackMethod methods, respectively.

Figure 2. AssignCallback Parameters

Figure 3. CallBackMethod Parameters

In Figure 2, you'll see that I have added an OleVariant parameter, ACallback, to the AssignCallback function. This parameter will refer to the IClientCallback implementation passed by the client that will be used by the server. In Figure 3, I've added the parameter, AMessage, of the type WideString. With this parameter, the server may pass back a text message to the client. By pressing the "Refresh Implementation" speed button in the Type Library Editor, the method, AssignCallback, is added to my remote datamodule's unit. No code is added for the IClientCallback interface as this is not currently declared in the source. I will implement this interface on the client manually.

Adding the Code to the Server

For this example, the server code is very basic. I simply added one line to the AssignCallback function that does just as the name implies. It assigns the IClientCallback implementation from the client to a local OleVariant, FClientCallback.

Additionally, I needed to add code that invoked the callback. Normally, this would be initiated through user interaction with the server's GUI. However, for simplicity, I did not get into the technicalities of communicating between the main form and remote datamodule. That is a topic for another forthcoming article. Therefore, I added a timer to the remote datamodule that calls the callback function 5 seconds after the remote data module is created. The code for these methods is shown below.

procedure TxwCallBackRDM.AssignCallback(ACallback: OleVariant);
begin
  FClientCallback := ACallback;
end;

procedure TxwCallBackRDM.Timer1Timer(Sender: TObject);
begin
  Timer1.Enabled := False;
  FClientCallback.CallbackMethod('This is the message.');
end;

procedure TxwCallBackRDM.RemoteDataModuleCreate(Sender: TObject);
begin
  Timer1.Enabled := True;
end;

Setting up the Client

On the client, I implemented the IClientCallback interface as the class TClientCallback within the main form's unit. The code for the main form is shown below.

unit xwcMainFrm;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, CallBackSrv_TLB, DB, DBClient, MConnect, ComObj, ActiveX;

type

  TClientCallback = class(TAutoIntfObject, IClientCallback)
  protected
    procedure CallbackMethod(const AMessage: WideString); safecall;
  end;

  TMainForm = class(TForm)
    DCOMConnection1: TDCOMConnection;
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
    FClientCallback: TClientCallback;
  public
    { Public declarations }
  end;

var
  MainForm: TMainForm;

implementation

{$R *.dfm}

{ TClientCallback }

procedure TClientCallback.CallbackMethod(const AMessage: WideString);
begin
  ShowMessage(AMessage);
end;

procedure TMainForm.FormCreate(Sender: TObject);
var
  typelib: ITypeLib;
begin
  DCOMConnection1.Connected:=true;
  OleCheck(LoadRegTypeLib(LIBID_CallbackSrv, 1, 0, 0, typelib));
  FClientCallback := TClientCallback.Create(typelib, IClientCallback);

  DCOMConnection1.AppServer.AssignCallback(FClientCallback as IDispatch);
end;

end.

First, note that I declared the TClientCallback as a descendant of TAutoIntfObject which implements the IClientCallback interface defined in CallBackSrv_TLB.pas. CallBackSrv_TLB was created by my server application. It is the type library unit in which IClientCallback is declared.

Then, in the FormCreate method, I created the TClientCallback class and pass it to the server by calling the AppServer.AssignCallback method. To create my TClientCallback instance, I first use the Windows API function LoadRegTypeLib to load the type information. With the type information for IClientCallback, I then invoke the constructor for TClientCallback and assign this reference to FClientCallback. At this point, I pass FClientCallback to the server.

The CallbackMethod implementation is a simple one line call to ShowMessage to display the text passed from the server. This is the point at which you would place your logic to handle the call from the server such as the graceful server disconnect as mentioned before.

You can test this demo by downloading it from CodeCentral. Remember to first register the server by running it with the /RegServer parameter. Run the client, wait about 5 seconds and you should see your message as shown in Figure 4 below. Stay tuned for my forthcoming article on remote data module/main form communication in which I'll use the callback functionality demonstrated here.

About the Author

Xavier Pacheco is the president of Xapware Technologies Inc., a Colorado based development and consulting company focusing on common sense approaches to driving development success. Xavier is the co-author for Delphi 6 Developer's Guide and has also written for publications such as the Delphi Informant and Delphi Magazine. Xavier's most recent achievement is the successful completion of Active! Focus, Xapware's workgroup solution to Application Lifecycle Management.

Copyright © 2002 Xapware Technologies Inc.
ALL RIGHTS RESERVED. NO PART OF THIS DOCUMENT CAN BE COPIED IN ANY FORM WITHOUT THE EXPRESS, WRITTEN CONSENT OF THE AUTHOR.


Server Response from: BDN10A

 
© Copyright 2008 Embarcadero Technologies, Inc. All Rights Reserved. Contact Us   Site Map   Legal Notices   Privacy Policy   Report Software Piracy