程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> 更多編程語言 >> Delphi >> RemObjects(一)客戶端遠程調用服務端接口過程

RemObjects(一)客戶端遠程調用服務端接口過程

編輯:Delphi

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封閉得相當好,所以了解客戶端調用服務端接口的過程,關鍵是意識到

代理類,消息格式類,通道傳輸類 是如何被聯結在一起的

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved