Vous êtes sur la page 1sur 7

EDNDelphi

Delphi Labs: DataSnap XE - Callbacks


By: Pawel Glowacki
Abstract: "Delphi Labs" DataSnap XE "Callbacks" demo shows the most simple use of callbacks. Both client and server are Delphi VCL Forms applications. This tutorial covers broadcasting to a channel and
notifying a specific callback.
Introduction
The objective of this tutorial is to create the simplest possible DataSnap Delphi client and server applications that use callbacks for communication.
In this lab exercise, we are going to use Delphi XE to build a simple callbacks demo system consisting of server and client applications. The server application will serve as a communication hub for multiple
client applications running in the network. It is a more realistic scenario as compared to sending notifications directlyfrom server application user interface to clients. In most scenarios, a server application will
not have anyuser interface, so callbacks are a great mechanism for clients to communicate with each other.
Message Exchange Patterns
The most common message exchange pattern in client/server applications is request-response. One application (a client) is sending a message (a request) to another application running in the network
(a server) and the server sends back a message (a response").
Hide image
In manyreal world applications, it would also be useful to have the opposite situation, where it is a server application that sends a message (a notification) to a client application. Aserver application may
want to inform a client that something interesting has happened on the server. This is called a callback a situation when server calls back the client.
expandview>>
Hide image
Imagine a chat application where multiple client applications connected to the server can communicate with each other. One client sends a message to the server and then the server forwards this message
to one or more connected client applications.
expandview>>
Hide image
The possibilityfor the server to asynchronouslysend a notification to one or more clients is veryuseful in manyscenarios.
expandview>>
DataSnap Callbacks and Channels
In order to use callbacks in DataSnap applications, you need to define a custom callback class that is inherited from the abstract TDBXCallback class and override one of its virtual, abstract Execute
methods which are called bythe server and executed on the client. The TDBXCallback class is defined in the DBXJSON unit as follows (some members striped out for readability):
unit DBXJSON;
interface
//
type
TDBXCallback = class abstract
public
function Execute(const Arg: TJSONValue): TJSONValue; overload; virtual; abstract;
function Execute(Arg: TObject): TObject; overload; virtual; abstract;
//
end;
In the previous version of DataSnap that came with RAD Studio 2010 it was onlypossible to use so-called lightweight callbacks. Acallback instance was passed to a long running server method as a
parameter from a client, so the server could call its Execute method within the duration of a method call for example to notifythe client about the progress of a long running operation.
In RAD Studio XE, the latest version available, so called heavyweight callbacks have been introduced. Theycan be used throughout the whole lifetime of a client application and not onlyduring a server
method call. This opens a lot of new possibilities for building different types of applications. In the remaining part of this tutorial we are going to focus on heavyweight callbacks and for simplicitywe are going
to refer to them as just callbacks.
In DataSnap architecture callbacks are associated with channels. In general there could be multiple client applications connected to the server and each of these clients can contain zero or more callbacks.
The server can broadcast to the channel, so all callbacks on everyclient that are registered with a specific channel are going to receive this notification. It is also possible to notifya specific callback using its
unique identifier used during registering the callback with the server. In this wayit is possible to achieve peer-to-peer communication model.
We are going to tryboth approaches: broadcasting to a channel and notifying a specific callback.
The server application calls Execute method on the client callback asynchronously. This is a veryimportant point to realize. EveryDelphi VCL Forms application has its main thread of execution and in case of
multithreaded applications anycalls from other threads that manipulates graphical user interface of the applications need to be synchronized. This is exactlythe situation with using callbacks. The callback
Execute method is called on a different thread then the main thread of the VCL application. There are different ways of synchronizing calls, but probablythe easiest option is to use TThread.Queue class
method, which asynchronouslyexecutes a block of code within the main thread.
COMMUNITIES ARTICLES BLOGS RESOURCES DOWNLOADS HELP
INTHIS ARTICLE
Introduction
Message Exchange Patterns
DataSnapCallbacks and
Channels
Implement the Callback Server
Create the Client Application
Implementinga callback
Broadcastingtothe Channel
NotifyingCallbacks
The Bigger Picture
Summary
TRANSLATIONS
RATING
Download Delphi XE6 now!
Get Free Trial
Webinars ondemand!
Delphi
15,314 people like Delphi.
Facebook social plugin
Like Like
More social media choices:
Delphi on Google+
@RADTools on Twitter
Download Trial
Buy Now
LOGON | | EMBARCADEROHOME

ENGLISH LOCATION
Share This

Watch, Follow, &
Connect with Us

Delphi Labs: DataSnap XE - Callbacks 5/14/2014
http://edn.embarcadero.com/article/41374 1 / 7
Implement the Callback Server
Our server application is going to be super simple. The callback functionalityis built into the DSServer component, which is the central point of everyDataSnap server application. In this demo we do not even
need to create anyserver methods, because we are onlygoing to communicate between client applications using callbacks.
The first step is to create a new DataSnap server application using DataSnap Server wizard.
Select File -> New -> Other and from the New Items dialog double-click on the DataSnap Server icon in the Delphi Projects -> DataSnap Server category.
Hide image
In the first tab keep the default DataSnap Project type which is VCL Forms Application.
expandview>>
Hide image
On the second tab we keep TCP/IP as the communication protocol and we can uncheck the option for generating server methods class, because it is not needed for this simple callbacks demo. If you leave
the default option to generate server methods, it is not a problem. We are just not going to use them.
expandview>>
Hide image
On the third screen we keep the default value 211 for the TCP/IP Port. It is always a good idea to click on the Test Port to make sure that it is available.
expandview>>
Because we have unchecked the option to generate a server class earlier in the wizard, we are not presented with the screen to select a base class for our server method class.
Click on Finish and the wizard should create a new project with just two units: main form and server container. There is no server methods unit this time.
Click on File -> Save All.
Create a new directoryfor all files in this lab for example C:\DataSnapLabs\SimpleCallbacks.
Save main application form as FormServerUnit and keep the default name for the server container unit typicallyServerContainerUnit1.
Save project as SimpleCallbacksServer.
Select the main form in the Object Inspector and change its Name propertyto FormServer and its Caption propertyto Delphi Labs: DataSnap XE - Simple Callbacks Server.
Hide image
@RADTools on Twitter
Delphi Labs: DataSnap XE - Callbacks 5/14/2014
http://edn.embarcadero.com/article/41374 2 / 7


Open the server container unit and verifythat there are onlytwo components there: DSServer1 and DSTCPServerTransport1 components.
Hide image
Thats it. Our server is readyand we do not need to implement anything special on the server, because the support for callbacks is built into the DSServer1 component. We also have a transport component
so that external clients can communicate with the DSServer1 instance in the server.
Save All, Run without Debugging and minimize the server application. It should be running for the rest of this tutorial.
Create the Client Application
Now it is the time to create a client. Just right click on the Project Group node in the Project Manager window and select Add New Project.
Hide image
From the New Items dialog select VCL Forms Application from Delphi Projects category.
Hide image
Click OK. Anew project will be added to the existing project group.
Click on File -> Save All.
Locate the folder where the server project has been saved and save there the main form unit of the client application as FormClientUnit, the new project as SimpleCallbacksClient and the project group as
SimpleCallbacksGrp.
Implementing a callback
The next step is to define a new callback class derived from TDBXCallback and implement its Execute method. This method will be called asynchronouslybythe server to notifythe client.
Add DBXJSON unit to the uses clause of FormClientUnit, because this is where TDBXCallback class is defined.
Define TMyCallback class and override its virtual abstract Execute method. There are two variants of the Execute method you could override. One that takes and returns a TObject and the second that
takes and returns TJSONValue. Im going to use the second option, because at the end both methods use JSON as the underlying format for sending messages.
At this stage the client unit source code looks like this:
unit FormClientUnit;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, DBXJSON;
type
TMyCallback = class(TDBXCallback)
public
function Execute(const Arg: TJSONValue): TJSONValue; override;
end;
TFormClient = class(TForm)
private
{ Private declarations }
public
{ Public declarations }
end;
var
FormClient: TFormClient;
Delphi Labs: DataSnap XE - Callbacks 5/14/2014
http://edn.embarcadero.com/article/41374 3 / 7

implementation
{$R *.dfm}
{ TMyCallback }
function TMyCallback.Execute(const Arg: TJSONValue): TJSONValue;
begin
// ...
end;
end.
So what should happen when our callbacks Execute method is called? This is reallyup to the programmer and depends on the application logic. To keep this example simple, we are going to add a memo
component to the client form and when the callback Execute method is called we are going to add a text line to the memo with the contents of the Arg parameter converted to a text string.
Lets define a public method on the form class called LogMsg that will take a string parameter with a message to displayin the memo. We are also going to add a timestamp.
Drop TMemo component on the client form. Change its name in the Object Inspector to MemoLog.
Add to TFormClient class a public procedure LogMsg(const s: string) and implement it in the following way:
procedure TFormClient.LogMsg(const s: string);
begin
MemoLog.Lines.Add(DateTimeToStr(Now) + ': ' + s);
end;
Now is the trickypart. We need to make a thread-safe call to the TFormClient.LogMsg procedure from our TMyCallback.Execute method.
Lets define the thread-safe version of our LogMsg method, so it could be called from a different thread.
procedure TFormClient.QueueLogMsg(const s: string);
begin
TThread.Queue(nil,
procedure
begin
LogMsg(s)
end
);
end;
The syntaxfor using anonymous methods mayseem to be exotic at first, but think about it like treating code as data. You just pass a block code as the second parameter to TThread.Queue method. This
method is a class method of the TThread class, so we do not need to instantiate TThread object in order to be able to call it.
Now we can call the thread-safe version of our LogMsg method directlyfrom the TMyCallback.Execute method.
function TMyCallback.Execute(const Arg: TJSONValue): TJSONValue;
begin
FormClient.QueueLogMsg(Arg.ToString);
Result := TJSONTrue.Create;
end;
We can return anything from our Execute method, as long as we do return something, so we just return JSON true value.
Now we need to register our callback with the server, so it is informed about what to call back.
There is a special class designed for managing client callbacks called TDSClientCallbackChannelManager and it is defined in the DSHTTPCommon unit.
Drop a TDSClientCallbackChannelManager component on the form and set its properties in the Object Inspector.
We need to select a name for a channel on the server that we want to associate our callback with. Lets call our channel DelphiLabsChannel.
We also need to specifyCommunicationProtocol, DSHostname and DSPort properties.
Hide image
The next thing we are going to do is to clear the ManagerId property, because we are going to generate this value at runtime.
This is a veryimportant thing to do. We want everyclient application instance to be treated bythe server differently. The ManagerId value is used at the server to identifyclients, so this value has to be different
for everyclient instance.
We are going to use TDSClientCallbackChannelManager.RegisterCallback method to register our callback instance with the server. This method takes two parameters: the name of the callback the uniquely
identifies it on the server and the reference to the callback instance, in our case this will be FMyCallback.
If you look into the constructor of the TDSClientCallbackChannelManager class you will see that the value for ManagerId is generated bya call to TDSTunnelSession.GenerateSessionId method that
returns a random string made of three numbers. We are going to use this functionalityto generate a unique name for our callback instance.
Add FCallbackName: string private field to the form class and add code to initialize it in the forms OnCreate event. You will also need to add DSService unit to the uses clause, because this is where the
TDSTunnelSession class is defined.
We also need to add code to initialize DSClientCallbackChannelManager1.ManagerId property.
uses DSService; // for TDSTunnelSession
//
procedure TFormClient.FormCreate(Sender: TObject);
begin
DSClientCallbackChannelManager1.ManagerId :=
TDSTunnelSession.GenerateSessionId;
FMyCallbackName :=
TDSTunnelSession.GenerateSessionId;
DSClientCallbackChannelManager1.RegisterCallback(
FMyCallbackName,
TMyCallback.Create
);
end;
The callback reference that we pass to the RegisterCallback method is owned bythe DSClientCallbackChannelManager1, so we do not need to keep this reference.
At this point we are readyto receive callbacks. The next step is to implement a functionalityto broadcast to a channel. All callbacks registered with a specified channel are going to be notified bythe server.
Broadcasting to the Channel
Note that we could now create a completelydifferent client application for broadcasting to the channel, and our client application, as it is implemented now, would be able to receive the notifications.
To keep things simple, we are going to add broadcasting to channel functionalityto our demo client application, and later we are going to run two instances of the same client application to see if we can send
messages from one client to another.
The first thing to do on the client is to add a TSQLConnection component to the form in order to be able to connect to the server. Probablythe easiest wayto do it is with IDE Insight. Just press F6 and start
typing TSQLConnection to search for it and then select to add to the form.
Set the Driver propertyof SQLConnection1 component on the form to DataSnap.
Set LoginPrompt propertyto False.
Set the Connected propertyto True to verifythat the client is able to connect to the server.
In a typical scenario, at this stage we would need to generate DataSnap client proxycode in order to be able to call server methods. In this case, however, this step is not necessary, since there are no custom
server methods on the server! The Delphi DataSnap proxygenerator uses TDSAdminClient class as a base class for client proxyclasses. This class alreadycontains quite a lot of functionalitythat can be
used on its own, including broadcasting to channels and notifying callbacks. We are going to use TDSAdminClient class directlyas the wayto interact with the server.
We need to extend our client application user interface a bit to support broadcasting to a channel.
Add TButton component to the form. Set its Name propertyto ButtonBroadcast and its Caption propertyto Broadcast to Channel.
Add a TEdit component. Set its Name propertyto EditMsg and optionallyenter some default message into it.
Delphi Labs: DataSnap XE - Callbacks 5/14/2014
http://edn.embarcadero.com/article/41374 4 / 7


You can also add a label next to the message edit, to indicate that this is the place to enter messages.
Double-click on the button and add the following code to be able to broadcast to messages to a channel. Note that we could pass arbitrarycomplexdata encoded as JSON, so it could be something more
complexthan just a string.
uses DSProxy; // <- for TDSAdminClient
//
procedure TFormClient.ButtonBroadcastClick(Sender: TObject);
var AClient: TDSAdminClient;
begin
AClient := TDSAdminClient.Create(SQLConnection1.DBXConnection);
try
AClient.BroadcastToChannel(
DSClientCallbackChannelManager1.ChannelName,
TJSONString.Create(EditMsg.Text)
);
finally
AClient.Free;
end;
end;
Now if you run the client application and press on the broadcast button you should see your message entered into the edit received bythe callback and displayed in the memo.
Run the second instance of the client application and you should be able to see that messages sent from one application are received byall applications!
Hide image
That is cool! We can now broadcast arbitrarydata that can be encoded in JSON to multiple applications running in the network.
expandview>>
What about pure peer-to-peer communication? Maybe I do not want to send message to all callbacks in the channel?
It would be much better if a given client could send message to one and onlyone callback instance on a different client.
This is also possible, but and we need to extend our client application to support notifying specific callbacks.
Notifying Callbacks
Stop both clients, but keep the server running.
The TDSAdminClient class also contains NotifyCallback method that could be used to achieve peer-to-peer communication model. This method has the following signature:
function TDSAdminClient.NotifyCallback(ChannelName: string; ClientId: string; CallbackId: string; Msg: TJSONValue; out Response: TJSONValue): Boolean;
The ChannelName parameter specifies the name of the communication channel the destination client callback is associated with. ClientId and CallbackId are values that were passed to
RegisterCallback method of the DSClientCallbackChannelManager1 at the destination client instance. Theywere both generated randomly. Msg is the JSON value that contains information that we want to
send to the destination callback and Response is an out parameter and contains JSON value with encoded response.
There is also TDSAdminClient.NotifyObject that takes similar parameters, but instead of using TJSONValue for input and output parameters, it is using a TObject-descendant that is automaticallyserialized
and deserialized from its JSON representation.
The process of notifying individual callbacks is going to be a little bit manual that will involve copying and pasting ClientId and CallbackId values from one running instance to another.
Lets add to our client application four additional TEdit components, four TLabel components and a TButton.
Change Caption propertyof the button to NotifyCallback and rename edits to: EditLocalClientId, EditLocalCallbackId, EditDestinationClientId, EditDestinationCallbacksId.
In the OnCreate event of the client form add code to initialize edits:

EditLocalClientId.Text := DSClientCallbackChannelManager1.ManagerId;
EditLocalCallbackId.Text := FMyCallbackName;
EditDestinationClientId.Text := '';
EditDestinationCallbackId.Text := '';
Double-click on the NotifyCallback button and enter the following code to notifyremote callback:
procedure TFormClient.ButtonNotifyClick(Sender: TObject);
var AClient: TDSAdminClient; aResponse: TJSONValue;
begin
AClient := TDSAdminClient.Create(SQLConnection1.DBXConnection);
try
AClient.NotifyCallback(
DSClientCallbackChannelManager1.ChannelName,
EditDestinationClientId.Text,
EditDestinationCallbackId.Text,
TJSONString.Create(EditMsg.Text),
aResponse
);
finally
AClient.Free;
end;
end;
Now start two or more client application instances and copyClientId and CallbackId from a client that you would like to receive notifications to destination edits of the client you want to send notification.
Hide image
Thats it! We have implemented peer-to-peer communication between Delphi DataSnap client applications!
expandview>>
The Bigger Picture
The idea behind this Delphi Labs demo was to make it as simple as possible.
It is also possible to use callbacks with the HTTP protocol in addition to TCP/IP. Similarlyin this demo we have used DataSnap DBXarchitecture, but the callbacks are also available with DataSnap REST.
Delphi Labs: DataSnap XE - Callbacks 5/14/2014
http://edn.embarcadero.com/article/41374 5 / 7

RAD Studio XE comes with a veryinteresting demo project that demonstrates all these possibilities.
You can open this demo directlyfrom inside the IDE using new Subversion integration.
Select File -> Open from Version Control and enter the following URL in the URL or Repository text box:
https://radstudiodemos.svn.sourceforge.net/svnroot/radstudiodemos/branches/RadStudio_XE/Delphi/DataSnap/CallbackChannels
In the Destination text boxenter a folder that you want to download the demo to. In mycase I have created a local C:\DataSnapLabs\CallbackChannelsDemo folder.
Hide image
Just click on OK and be patient. You will see initiallyemptywindow with Updating title. After a while it will show the names of all files checked out from the radstudiodemos public repositoryon
SourceForge.
Hide image
There are three projects there. ChannelsServerProject is the main server application. DBXClientChannels and RESTClientChannels are two client applications. One based on DataSnap DBXarchitecture
and one based on the new DataSnap RESTarchitecture introduced in RAD Studio XE.
Keep the server project selected and click OK to open it in the IDE.
Hide image
Click OK to close the Updating window. At this stage onlythe server project is opened in the IDE.
expandview>>
Now we need to add both client projects to a project group, so we have all three demo projects available inside the IDE.
Right click on the Project Group node in the Project Manager window, select Add Existing Project and choose DBXClientChannels project.
Right click again on the Project Group, select Add Existing Project and this time choose RESTClientChannels project.
Select File -> Save All or just click on the Save All icon.
Give the project group a name. I have chosen for CallbackChannelsDemo.
At this stage myProject Manager looks like this:
Hide image
I will leave you here. There is plentyto explore in this demo
Summary
In this Delphi Lab we have used Delphi XE for building a system consisting of server and client native Win32 applications communicating with each other using TCP/IP protocol and using callbacks.
Callbacks represent a veryuseful alternative to a traditional request/response message exchange pattern in distributed applications.
With callbacks the server application is able to send asynchronous notifications to one or more registered callback instances inside connected client applications.
Delphi Labs: DataSnap XE - Callbacks 5/14/2014
http://edn.embarcadero.com/article/41374 6 / 7
Copyright1994- 2013EmbarcaderoTechnologies, Inc. All rights reserved. SiteMap
The full source code for this article is available from Embarcadero Code Central http://cc.embarcadero.com/item/28288
The video version of steps described in this article can be found on YouTube. There are three parts of the video demonstration:
http://www.youtube.com/watch?v=5zO3_g9Z-wc
http://www.youtube.com/watch?v=geEzwg8XX8k
http://www.youtube.com/watch?v=Hwode7a8O5k
More information about Delphi can be found on the Delphi home page http://www.embarcadero.com/products/delphi
Move mouse over comment to see the full text
Reply Postedby admzhen admzhen onAug192013
C++ Labs: DataSnapXE2 - Callbacks
We desperatelyneedillustrations for DataSnapXE2 - Callbacks writteninC++. Thank you
Reply Postedby Fellipe Henrique onOct 112012
Delphi Labs: DataSnapXE- Callbacks
Hocansendthis Broadcast message from Server?
Reply Postedby Cleidson BarbosaonMar 222012
Delphi Labs: DataSnapXE- Callbacks
Congratulations, Pawel. Veryinterestingarticle.
Reply Postedby LenaIlichevaonApr 162011
Delphi Labs: DataSnapXE- Callbacks
Thank youfor the article. Where onEmbarcaderosite canone findarticles about DataSnapapplicationinC++
Builder XE?We desperatelyneedillustrations for DataSnapwritteninC++. Thank you.
Server Response from: ETNASC04
LATEST COMMENTS
Delphi Labs: DataSnap XE - Callbacks 5/14/2014
http://edn.embarcadero.com/article/41374 7 / 7