RemObjects SDK 是高度封裝的產物,對OOP發揮極致。
本文將以RemObjects SDK最簡單的DEMO——FirstSample為例,
介紹客戶端是如何完成遠程調用服務端接口的全過程。
也理解為什麼可以通過不同傳輸通道(TCP/HTTP...),不同消息格式(二進制,SOAP...) 與服務端進行通訊
客戶端就這三個RO控件,是如何完成一個完整的調用過程的呢?
在程序啟動的時候,RO已經完成了一系列動作,
先了解一個Delphi主程序代碼的執行順序
程序啟動 -->
執行 initialization 處的代碼 (在主程序運行前運行並且只運行一次)-->
{工程文件}
begin
Application.Initialize;
Application.CreateForm(TForm, Form1);
Application.Run;
end. -->
執行 finalization 處的代碼 (在程序退出時運行並且只運行一次)
也就initialization處的代碼是最先運行的,當我們把這三個控件擺上的時候,
會自動加入 uROClient,uROBinMessage,uROWinInetHttpChannel,
同時為加調用服務端接口,手動把接口聲明文件也加入FirstSample_Intf
而這三個文件都有initialization ,它們做了什麼呢?
{unit uROClient;} procedure RegisterMessageClass(aROMessageClass : TROMessageClass); begin _MessageClasses.Add(aROMessageClass); if Classes.GetClass(aROMessageClass.ClassName) = nil then Classes.RegisterClass(aROMessageClass); end; procedure RegisterTransportChannelClass(aTransportChannelClass : TROTransportChannelClass); begin if _TransportChannels.IndexOf(aTransportChannelClass.ClassName)<0 then begin _TransportChannels.AddObject(aTransportChannelClass.ClassName, TObject(aTransportChannelClass)); if Classes.GetClass(aTransportChannelClass.ClassName) = nil then Classes.RegisterClass(aTransportChannelClass); end; end; procedure RegisterProxyClass(const anInterfaceID : TGUID; aProxyClass : TROProxyClass); var s : string; begin s := GUIDToString(anInterfaceID); if _ProxyClasses.IndexOf(s)<0 then _ProxyClasses.AddObject(GUIDToString(anInterfaceID), TObject(aProxyClass)) end; initialization _MessageClasses := TClassList.Create; //TClassList 只是給 TList 起個別名 _ExceptionClasses := TClassList.Create; _ProxyClasses := TStringList.Create; _ProxyClasses.Duplicates := dupError; _ProxyClasses.Sorted := TRUE; _TransportChannels := TStringList.Create; _TransportChannels.Duplicates := dupError; _TransportChannels.Sorted := TRUE; ... ...
這裡初始化了3個列表,將分別用於存儲 消息格式類,代理類,傳輸通道類
而三個全局函數只是將相應的對象添加到列表
{unit uROBinMessage;} initialization RegisterMessageClass(TROBinMessage); {unit uROWinInetHttpChannel;} initialization RegisterTransportChannelClass(TROWinInetHTTPChannel); {unit FirstSample_Intf;} initialization //第一個函數是接口ID,第二個是接口的實現類 RegisterProxyClass(IFirstSampleService_IID, TFirstSampleService_Proxy);
這樣一來,程序啟動的時候就已經完成了一系列操作
接下來到了主窗體創建時執行的代碼
{unit FirstSampleClientMain;} constructor TFirstSampleClientMainForm.Create(aOwner: TComponent); begin inherited; fFirstService := (RORemoteService as IFirstSampleService); end;
也許對初學者來講有點不可思議, 對象RORemoteService與接口IFirstSampleService之間根本不存在任何關系,居然可以 as ?
這是因為 as 操作會先調用接口查詢 QueryInterface,看下TRORemoteService的QueryInterface函數是如何實現的
{unit uRORemoteService;} function TRORemoteService.QueryInterface(const IID: TGUID; out Obj): HResult; var proxyclass : TROProxyClass; proxy : TROProxy; begin result := inherited QueryInterface(IID, Obj); if (result <> S_OK) then begin //通過接口ID查詢到接口實現類的引用 proxyclass := FindProxyClass(IID, TRUE); if (proxyclass=NIL) then Exit else begin CheckCanConnect(false); //創建接口實現對象 proxy := proxyclass.Create(Self); proxy.GetInterface(IID, Obj); result := S_OK; end; end; end;
其中的 FindProxyClass 定義在 ROClient
{unit uROClient;} function FindProxyClass(const anInterfaceID : TGUID; Silent : boolean = FALSE) : TROProxyClass; var idx : integer; s : string; begin result := NIL; s := GUIDToString(anInterfaceID); idx := _ProxyClasses.IndexOf(s); if (idx>=0) then result := TROProxyClass(_ProxyClasses.Objects[idx]) else begin if not Silent then RaiseError(err_UnknownProxyInterface, [s]) end; end;
所以 fFirstService := (RORemoteService as IFirstSampleService); 就獲取了代理類的實現接口
而代理類做了些什麼呢?
proxy := proxyclass.Create(Self); //在TRORemoteService的QueryInterface函數
proxyclass是一個TROProxyClass對象,而TROProxyClass= class of TROProxy;也就是TROProxy類引用
看下TROProxy的構造函數
{unit uROClient;} constructor TROProxy.Create(const aRemoteService: IRORemoteService); begin Create(aRemoteService.Message, aRemoteService.Channel); fCloneMessage := aRemoteService.CloneMessage; end; constructor TROProxy.Create(const aMessage: IROMessage; const aTransportChannel: IROTransportChannel); begin inherited Create; fMessage := pointer(aMessage); fTransportChannel := pointer(aTransportChannel); fCloneMessage := False; end;
至此,一個 TRORemoteService對象 將
代理類 TFirstSampleService_Proxy,消息格式 TROBinMessage,傳輸通道TROWinInetHTTPChannel 聯合在一起,
程序啟動時執行的 fFirstService := (RORemoteService as IFirstSampleService); 就已經完成了這麼多
接下來看接口函數的調用
{unit FirstSampleClientMain;} procedure TFirstSampleClientMainForm.GetButtonClick(Sender: TObject); begin NamesBox.Items.CommaText := fFirstService.Nicknames(eFullname.Text); end;
在客戶端,接口IFirstSampleService的實現是在TFirstSampleService_Proxy類實現的,
而fFirstService := (RORemoteService as IFirstSampleService);已經完成了對象的創建,
所以fFirstService.Nicknames(eFullname.Text);實際調用的是TFirstSampleService的Nicknames函數
function TFirstSampleService_Proxy.Nicknames(const FullName: UnicodeString): UnicodeString; begin try __Message.InitializeRequestMessage(__TransportChannel, 'FirstSample', __InterfaceName, 'Nicknames'); __Message.Write('FullName', TypeInfo(UnicodeString), FullName, []); __Message.Finalize; __TransportChannel.Dispatch(__Message); __Message.Read('Result', TypeInfo(UnicodeString), result, []); finally __Message.UnsetAttributes(__TransportChannel); __Message.FreeStream; end end;
__Message是TROProxyr的一個屬性,
{unit uROClient;} function TROProxy._GetMessage: IROMessage; begin result := IROMessage(fMessage); end;
從代碼可以看出,其實就是代理類創建時從RORemoteService.Message傳入,也就是窗體上的 ROBinMessage,
同理 __TransportChannel 就是窗體上的 ROWinInetHTTPChannel 。
TROBinMessage的基類是TROMessage,而Write,Read在TROMessage是虛函數
{unit uROClient;} {TROMessage} procedure Write(const aName : string; aTypeInfo : PTypeInfo; const Ptr; Attributes : TParamAttributes); virtual; procedure Read(const aName : string; aTypeInfo : PTypeInfo; var Ptr; Attributes : TParamAttributes); virtual;
所以實際調用的是TROBinMessage的Write 和 Write
而TROBinMessage的Write 和 Write實現了二進制格式的讀寫,此處略出實現代碼
TROSOAPMessage的Write 和 Write實現了SOAP格式的讀寫
所以當TRORemoteService 綁定的 Message 是 TROBinMessage 時,消息就會按 二進制格式讀寫,
當TRORemoteService 綁定的 Message 是 TROSOAPMessage時,消息就會按 SOAP格式讀寫,
相同的道理,TROTransportChannel 的子類 TROIndyUDPChannel, TROWinInetHTTPChannel
實現了在不同方式連接時的實現過程,這部分實際是調用了底層通訊
客戶端TROMessage的Write函數將函數名及參數按照一定格式打包
TROTransportChannel的Dispatch把TROMessage發送到服務端並等待返回,
服務端將返回結果寫入TROMessage
客戶端TROMessage的Read函數再解包把結果讀取
這就是客戶端調用服務端接口的基本過程
由於RemObject SDK封閉得相當好,所以了解客戶端調用服務端接口的過程,關鍵是意識到
代理類,消息格式類,通道傳輸類 是如何被聯結在一起的