Delphi/C++Builder 2010加入了回叫機制, 我也在2009年的部落格文章中說明了如何使用Delphi/C++Builder 2010的回叫功. 然而Delphi/C++Builder XE又再次強化了回叫機制, 讓這個功能更為強大和完善, 由於XE版的回叫機制提供了眾多新的功能, 因此我想藉由這篇文章說明一下如何使用XE版的回叫機制, 雖然我無法一次把所有的功能寫完, 但起個頭還是很有用的.
DataSnap XE在原有的基礎回叫機制之上加入了許多強大的新功能,從DataSnap XE開始開發人員可以使用下面的回叫功能:
用戶端可向伺服端註冊回叫通道,如此一來伺服器可以一次回叫所有在同回叫通道中所有註冊的用戶端回叫函式
用戶端可以同時註冊多個不同的回叫通道
用戶端可以藉由回叫通道呼叫不同的用戶端
新增回叫元件以幫助開發人員簡化開發回叫機制
DataSnap XE新的回叫功能雖然很多,但使用起來仍然相當的容易,下面說明了如何使用這些DataSnap XE新的回叫功能的基本步驟:
用戶端使用TDSClientCallbackChannelManager向伺服器註冊一個回叫通道
伺服器使用TDSServer元件的BroadcastMessage方法回叫所有註冊的用戶端
當然,開發人員可以更進一步的使用DataSnap XE進階的回叫功能,不過在那之前也許我們應該先說明數個範例讓讀者瞭解如何使用這些基本的步驟。
在Delphi整合發展環境中建立一個DataSnap Server專案:
<a target="_blank" href="http://gordonliwei.files.wordpress.com/2011/01/image1.jpg"></a>
在設定此伺服器的特性時,讓我們目前只選擇使用TCP/IP通訊協定,如下圖所示:
<a target="_blank" href="http://gordonliwei.files.wordpress.com/2011/01/image2.jpg"></a>
開啟專案中的ServerContainer程式單元,此時在ServerContainer中產生了兩個元件,TDSServer以及TDSTCPServerTransport,由於接下來我們將先展示Windows用戶端的回叫功能,因此現在使用TCP/IP通訊協定就足夠了。
<a target="_blank" href="http://gordonliwei.files.wordpress.com/2011/01/image8.jpg"></a>
現在開啟專案主表單,並且在其中置入如下的元件:
<a target="_blank" href="http://gordonliwei.files.wordpress.com/2011/01/image7.jpg"></a>
主表格上方使用了TListBox元件,它可以顯示所有用戶端註冊的回叫識別ID,而下方的TMemo元件則是使用來回叫註冊用戶端,在稍後我們將在此TMemo元件中輸入資訊,這些輸入的資訊就會藉由回叫通道自動傳遞給用戶端。
DataSnap伺服器要回叫所有註冊的用戶端是非常容易的,只需要藉由TDSServer類別定義的BroadcastMessage方法即可,TDSServer類別中定義了兩個BroadcastMessage方法原型如下:
function BroadcastMessage(const ChannelName: String; const Msg: TJSONValue; const ArgType: Integer = TDBXCallback.ArgJson): boolean; overload;
function BroadcastMessage(const ChannelName: String; const CallbackId: String; const Msg: TJSONValue; const ArgType: Integer = TDBXCallback.ArgJson): boolean; overload;
這兩個BroadcastMessage方法的差異在於上述第一個BroadcastMessage可以傳遞訊息給它第一個參數ChannelName指定的通道中所有的回叫用戶端,而第二個BroadcastMessage方法則是只傳遞訊息給它第一個參數ChannelName指定的通道中由第二個參數CallbackId指定的回叫用戶端,最後這個兩個BroadcastMessage方法傳遞給用戶端的訊息則由第二個參數Msg封裝。
瞭解了如何使用BroadcastMessage方法之後,我們就可以看看如何把DataSnap伺服器中於TMemo元件中輸入的訊息傳遞給回叫用戶端。現在為主表單中的TMemo元件實作OnChange事件處理函式如下:
001 procedure TForm17.mmMessageChange(Sender: TObject);
002 var
003 vMessage : TJSONString;
004 begin
005 vMessage := TJSONString.Create(mmMessage.Lines.Text);
006 ServerContainer5.DSServer1.BroadcastMessage(DEMOChannel, vMessage);
007 end;
在005行我們把輸入於TMemo(mmMessage)中的資訊以TJSONString物件封裝,然後在006行藉由呼叫ServerContainer中的TDSServer元件的BroadcastMessage方法傳遞給所有註冊的用戶端。但誰是註冊的用戶端呢?請看BroadcastMessage的第一個參數DEMOChannel,這代表DataSnap伺服器會傳遞資訊給所有在DEMOChannel通道中註冊的用戶端。而DEMOChannel是一個通道的名稱,我們在DataSnap伺服器中定義它如下:
const
DEMOChannel = ‘DemoChannel’;
因此用戶端只要使用這個名稱向伺服器註冊回叫通道的話,就可以讓DataSnap伺服器回叫用戶端,當然也用戶端可以先向伺服器查詢已經定義在DataSnap伺服器中的回叫通道名稱,或是由用戶端自行在DataSnap伺服器中建立指定名稱的回叫通道。
由於回叫用戶端是向DataSnap伺服器中指定名稱的回叫通道註冊,而且每一個用戶端都使用一個特定的回叫識別ID來代表,因此我們也可以藉由TDSServer元件來查詢某一個名稱的回叫通道中所有註冊的用戶端回叫識別ID。
此範例DataSnap伺服器主表單中的『列出所有回叫識別』按鈕的OnClick事件處理函式就可以在主表單上方的TListBox中列出特定回叫通道中所有的用戶端回叫識別ID,下面就是它的OnClick實作程式碼:
001 procedure TForm17.Button1Click(Sender: TObject);
003 aIdList : TList<String>;
004 sId : String;
005 begin
006 aIdList := ServerContainer5.DSServer1.GetAllChannelCallbackId(DEMOChannel);
007 try
008 for sId in aIdList do
009 ListBox1.Items.Add(sId);
010 finally
011 aIdList.Free;
012 end
013 end;
GetAllChannelCallbackId方法會回傳TList<String>型態的執行結果,其中即包含了所有在此通道名稱中註冊的用戶端回叫識別ID,因此在006行藉由TDSServer元件呼叫GetAllChannelCallbackId之後,就可以取得到在DEMOChannel中註冊的識別ID,接著從008行到012行就把這些識別ID顯示在主表單的TListBox中。
現在請編譯並且執行此範例DataSnap伺服器,接著我們就可以實作回叫用戶端了,首先讓我們先說明如何建立Windows應用程式型態的用戶端,接著再說明如何建立瀏覽器型態的用戶端。
在專案管理員中再建立一個VCL Form應用程式專案,並且在其中放入TSQLConnection,TDSClientCallbackChannelManager,TMemo,兩個TButton元件,如下圖所示。其中TSQLConnection是連結上一小節的範例DataSnap伺服器,而TDSClientCallbackChannelManager則是使用來向DataSnap伺服器註冊回叫用戶端的元件。
<a target="_blank" href="http://gordonliwei.files.wordpress.com/2011/01/image6.jpg"></a>
接著設定TDSClientCallbackChannelManager特性值如下:
特性名稱
特性值
ChannelName
DemoChannel
CommunicationProtocol
tcp/ip
DSHostname
localhost
DSPort
211
Name
DemoChannelManager
設定TDSClientCallbackChannelManager的ChannelName特性值為DemoChannel是因為前面範例DataSnap伺服器使用的通道名稱就是DemoChannel,而且前面範例DataSnap伺服器是支援TCP/IP通訊協定和使用211通信埠,因此我們需要設定TDSClientCallbackChannelManager相對應的特性值,下圖顯示了在物件檢視器中設定TDSClientCallbackChannelManager元件的特性值:
<a target="_blank" href="http://gordonliwei.files.wordpress.com/2011/01/image4.jpg"></a>
設定好TDSClientCallbackChannelManager元件之後,我們就可以看看用戶端主表單中的『啟動回叫功能』按鈕的實作程式碼了:
001 procedure TfmMainForm.btnStartClick(Sender: TObject);
002 begin
003 SetupTask;
004 EnableDisableButtons(False, True);
005 DemoChannelManager.RegisterCallback(callbackId, TDemoCallback.Create)
006 end;
007
008 procedure TfmMainForm.SetupTask;
009 begin
010 if not scnnCallbackServer.Connected then
011 begin
012 scnnCallbackServer.Connected := True;
013 end;
014 callbackId := DateTimeToStr(Now);
015 Self.Caption := callbackId;
016 end;
在btnStartClick事件處理函式中先於003行呼叫SetupTask方法以開啟TSQLConnection元件連結範例DataSnap伺服器,並且在014行根據目前的時間建立一個獨特的識別ID,callbackId。最後在005行呼叫TDSClientCallbackChannelManager元件的RegisterCallback方法向範例DataSnap伺服器註冊這個回叫用戶端。
TDSClientCallbackChannelManager元件的RegisterCallback方法原型定義如下:
function RegisterCallback(const CallbackId: String; const Callback: TDBXCallback): boolean; overload;
RegisterCallback接受兩個參數,第一個是每一個回叫用戶端的識別ID,第二個參數則是型態為TDBXCallback的物件,在前面的小節中我們已經解釋過TDBXCallback類別,因此在這裡我們需要建立一個從TDBXCallback衍生類別的物件,在這個衍生類別中我們需要複載虛擬方法Execute,如此一來範例DataSnap伺服器就可以藉由呼叫複載虛的擬方法Execute來呼叫到用戶端。
由於我們在上面的005行是傳遞TDemoCallback物件給RegisterCallback方法,因此我們需要在這個範例用戶端應用程式中定義和實作TDemoCallback類別,它需要從TDBXCallback繼承下來:
type
TDemoCallback = class(TDBXCallback)
public
constructor Create;
function Execute(const Arg: TJSONValue): TJSONValue; override;
end;
TDemoCallback需要實作虛擬方法Execute,當DataSnap伺服器回叫用戶端時就會執行虛擬方法Execute。由於在這個範例中我們希望範例DataSnap伺服器在TMemo中輸入的資訊能夠立刻顯示在用戶端,因此我們實作虛擬方法Execute如下:
001 function TDemoCallback.Execute(const Arg: TJSONValue): TJSONValue;
003 sDemoMessage : String;
005 Result := TJSONTrue.Create;
006
007 if (Arg is TJSONString) then
008 begin
009 sDemoMessage := TJSONString(Arg).Value;
010 TThread.Synchronize(nil,
011 procedure
012 begin
013 fmMainForm.mmDemoMessage.Lines.Text := sDemoMessage;
014 end
015 );
016 end;
017 end;
當DataSnap伺服器藉由回叫功能呼叫用戶端複載的Execute時,DataSnap伺服器可以把伺服端傳遞到用戶端的資訊封裝在Execute的參數Arg中。由於在前面的範例DataSnap伺服器是把在伺服端TMemo中的資訊封裝成TJSONString傳遞到用戶端,因此在007行先判斷伺服端傳遞來的是否是TJSONString型態,如果是的話,就00行取出伺服端傳遞來的資訊,接著由於我們需要把這個資訊顯示在用戶端主表單中的TMemo元件中,因此我們藉由呼叫TThread類別的類別方法Synchronize來更新用戶端的使用者介面(這是因為用戶端使用者介面的主執行緒和在背景的回叫執行緒是不同的,因此需要使用Synchronize來讓背景回叫執行緒更新主執行緒中的元件)。由於Synchronize定義了如下的原型:
class procedure TThread.Synchronize(AThread: TThread; AMethod: TThreadMethod);
因此在上面的010行中我們直接使用匿名方法來更新主表單的TMemo元件。
最後當我們不再需要讓用戶端被回叫時,可以呼叫TDSClientCallbackChannelManager元件的UnregisterCallback方法並且傳遞用戶端識別ID,例如下面的程式碼就是主表單中『停止回叫功能』按鈕OnClick事件處理函式的實作程式碼:
procedure TfmMainForm.btnStopClick(Sender: TObject);
begin
EnableDisableButtons(True ,False);
DemoChannelManager.UnregisterCallback(callbackId);
現在編譯並且執行此範例回叫用戶端,從下圖我們可以看到,點選範例DataSnap伺服器中的『列出所有回叫識別』按鈕就可以在上方的TListBox中列出用戶端傳遞來的識別ID,而且只要用戶端應用程式點選了主表單中的『啟動回叫功能』按鈕註冊回叫用戶端,那麼我們在範例DataSnap伺服器的TMemo中輸入的任何資訊就會立刻顯示在用戶端應用程式的TMemo元件中,證明了回叫功能是成功的。
<a target="_blank" href="http://gordonliwei.files.wordpress.com/2011/01/image9.jpg"></a>
當然,用戶端可以註冊多個不同的回叫通道,在同一個回叫通道中也可以註冊多個識別ID,例如讓我們修改剛才的範例用戶端應用程式,加入另外一個TMemo元件,以及一個TEdit元件讓使用者可以輸入不同的用戶端識別ID來註冊:
<a target="_blank" href="http://gordonliwei.files.wordpress.com/2011/01/image11.jpg"></a>
接著在『啟動回叫功能1』和『啟動回叫功能2』按鈕中都使用下面的程式碼實作,現在用戶端識別ID就由使用者輸入而不是使用目前的日期:
procedure TfmMainForm.btnStartClick(Sender: TObject);
SetupTask;
EnableDisableButtons(False, True, True);
DemoChannelManager.RegisterCallback(edtCallbackId.Text, TDemoCallback.Create);
編譯並且執行新的用戶端應用程式,在下面的圖形中我們可以看到筆者在用戶端應用程式中註冊了兩個用戶端識別ID,分別是『用戶端識別ID1』和『用戶端識別ID2』,而範例DataSnap伺服器也可以列出這兩個不同的用戶端識別ID,在用戶端註冊了兩個不同的識別ID之後,範例DataSnap伺服器可以藉由同時回叫這兩個不同的用戶端回叫物件。例如下圖就顯示了當我們在範例DataSnap伺服器的TMemo中輸入了訊息之後,這些訊息會立刻顯示在用戶端應用程式中兩個不同的TMemo元件之中:
<a target="_blank" href="http://gordonliwei.files.wordpress.com/2011/01/image101.jpg"></a>
如何? DataSnap XE的回叫機制是不是更強大了?不過故事還沒結束,接下來我們將繼續討論如何讓不同的用戶端都能夠藉由回叫機制來溝通,以及如何開發以瀏覽器為基礎的回叫用戶端,這些是更精彩的主題,我們下次再見了, Have Fun!