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.