使用RemObject SDK

/黃忠成 EMail:code6421@pchome.com.tw
 前言
 
 
DELPHI 3rd-Party 元件數量之多,遠超過其它的開發工具,其用途之廣可說創下前所未有的記錄。這也為 DELPHI 程式設計師省下許多重新製造輪子的時間,令系統開發速度倍增,同時減少了因實作碼增加而使錯誤率升高。可惜的是 VCL 元件似乎都有著一個通病,就是缺少完整的說明檔 ! 許多 VCL 元件甚至連範例都少的可憐,幸運的是 VCL 元件有個不成文的慣例,那就是多數都會附上完整的原始碼,這一點可以稍減其說明檔不足的現象。即便如此,說明檔不足依舊對使用者造成相當大的困擾,時間就是金錢,在設計者探索原始碼時,時間也一點點的流失了。本文所介紹的 RemObjects SDK( 以下簡稱 RO) 也不能例外,由於這套元件的開發者只有兩位,因此說明檔一直都是相當短缺,有些地方甚至還有描述錯誤的情形,但這些缺點卻無法掩蓋其嶄新的創意與高延展性的設計概念,這也是本文為何會出現在讀者眼前的主要原因, RO 是筆者看過 VCL 元件中唯一令筆者感到驚豔的,當然 ! 這只是筆者個人的感覺,對讀者不見得是如此,不過多了解一樣東西,於汝何損 ?? 因此,細細品嘗吧 !!
 
PS: 本文省略了討論 Web Services 的基本知識部份,如果讀者對於 Web Services 不熟悉,可參閱筆者的另幾篇文章。
 
參考文章
 
 
如何取得RO?
 
   讀者可至 http://www.remobjects.com 取得測試版本,正式版本的定價是 229 EUR ,未來的 Enterprise 版本的定價是 603.90 EUR ,這兩個版本都附上了完整的原始碼,目前 RemObjects Enterprise SDK 版本尚在 Beta 中,此版本擁有許多新功能,除了加強的 RO 2.0 之外還有抽象化資料存取的 Data Abstract 元件組,協助除錯的 Debug Server 工具,以及完全使用 C# 寫成的 RO.NET Client SDK
 
PS: 測試版本僅能運行於 DELPHI IDE 中,讀者可利用 Project Group 來輔助運行 Server 端與 Client 端的程式。
PS2: RO 1.x 支援 DELPHI 5 6 7 Professional(DataSnap 部份需 Enterprise) Kylix 3 for DELPHI
 
 
What’s RemObjects SDK
 
  隨著各家廠商的強力背書與推銷, Web Services 儼然成為未來分散式系統開發的主流架構,但是 Web Services 至今仍然存在一些問題,其中有些是屬於規格的問題,有些則是先天上的限制,許多使用 Web Services 開發系統的人都會有一個困擾,那就是效率不高,其原因很簡單, XML 本身屬於純文字型態,加上必須依賴 XML Parser 剖析 XML 文件,在傳輸與解譯上都是造成效率不彰的原因,這是 Web Services 的先天限制,也是為了相容性所付出的代價。當然 ! 如果網路頻寬夠大,電腦速度夠快,這些都不是問題。但事實是目前的頻寬與電腦速度還不足以勝任,這使得 Web Services 的應用面縮減不少,因此許多的 Web Servcies 開發工具都會提供將 SOAP 訊息壓縮的解決方案,藉此減少網路傳輸時間。另一個問題則是 Web Services 必須依賴網路通訊協定,以現今的情況來看是以 HTTP TCP 兩種網路通訊協定為主流,假如客戶想將系統安裝於一台電腦上 ( 不管是何理由,或許是因為節省金錢 ) Web Services 還是需要一個佔用 Port ,就實務上來看這並不是什麼大問題,但如果可以不佔用 Port 豈不更好 ?? RO 就是這樣一套元件,首先 ! RO 支援兩種訊息標準,一個是 SOAP( 也就是 Web Services) 、另一個則是 Binary( 二進位訊息 ) ,支援 SOAP 可讓其它支援 Web Services 的開發工具經由 SOAP 連上 RO Server ,支援 Binary 可以讓 RO Client 以更快的速度與 RO Server 溝通,這比起將 SOAP 壓縮後傳遞的效率高上許多,更令人興奮的是 RO 允許設計者混用這兩種訊息協定,也就是說只須撰寫一個 Server 並放上這兩個訊息元件,這一個 Server 就可以同時服務使用 SOAP Binary 訊息的 Client 端。有趣嗎 ?? 更有趣的事情還在後面, RO 支援 HTTP TCP Windows Message DLL UDP(2.0) MSMQ(RO Enterprise) 多種通訊協定,並且允許設計者混用這些協定 (DLL 是例外 ) ,簡單的說 ! 就是寫一個 Server 同時允許 Client 端以 HTTP TCP Windows Message UDP MSMQ 方式連結,再加上之前所提的兩種訊息標準,這個 Server 是不是更有趣了呢 ?? ! 還沒講完呢, RO 不但具備這些特色,同時也允許設計者撰寫自己的訊息協定與通訊協定,其步驟也不複雜,這些都是 RO 出色的主要原因。另外 RO 也支援 Kylix 3 for DELPHI ,這代表著使用 RO 可撰寫 Linux Server/Client Windows Server/Client ,日後的 RO Client SDK.NET 支援 .NET Framework Mono Ractor ,及 Compact Framework ,你能想像這種情況嗎 ??
 
PS: TCP Windows Message 同時只能支援一種訊息格式,如 SOAP 或是 Binary ,原因是這兩種協定並沒有類似 URL 的概念, HTTP 則無此限制,另外 RO Enterprise SDK 將會支援 .NET Binary(.NET Remoting) RO Binary 兩種格式。
 
 
初試RemObjects SDK
 
  談了這麼多空話,現在是時候試試 RO 的能力了,這一節中以一個簡單的計算機為範例 ( !! 這是 RO 送的,不要都不行 ….) ,在安裝完 RO 後元件盤上會出現 RemObjects SDK 頁,如下圖所示 :
其中分為五類,見下表 :
元件
功能
類別
TROBinMessagwTROSOAPMessage
訊息元件,用來處理訊息。
訊息類
TROIndyHTTPServer
TROIndyTCPServer
TROBPDXHTTPServer
TROBPDXTCPServer
TROWinMessageServer
Server 端元件,用來接收訊息,支援 HTTP TCP Window Message DLL(DLL 不需要元件,只需 export 一個 function 即可 )
Server
TROIndyHTTPChannelTROIndyTCPChannel
TROBPDXHTTPChannel
TROBPDXTCPChannel
TROWinInetHTTPChannel
TRODLLChannel
Client 端元件,用來送出訊息到 Server 端,支援 HTTP TCP Windows Message DLL
Client
TRODataSnapConnectionTRODataSnapProviderPublisher
支援 DataSnap 運作的元件,是的, RemObjects SDK 允許使用 DataSnap 運行於其上。
DataSnap 支援
TROWebBrokerServer
Web Broker 支援,允許任何架構於 Web Broker 之上的網頁程式直接掛載 RemObjects SDK Server
Web 支援
表中所提及的元件除 BPDX( 這是一組 Internet 元件,名為 DXSock ,與 Indy 有相同功能,但在效率與穩定性上都比 Indy 強,不過在易用性上卻遠不及 Indy ,而且她屬於商業型元件,不過當讀者購買 RO 後不須額外付費就可使用這套元件 ) WebBrokerServer DLLxxx 之外,其它都會在本文中運用到。
在對這些元件有一個概略的認識後,現在就可以開始撰寫一個簡單的程式了,首先請開啟 New Dialogs 對話盒,切換到 RemObjects SDK 這一頁,其內有幾個 Wizard 可協助設計者快速的產生骨架程式 :
下表是這幾個 Wizard 的簡單說明 :
Wizard
說明
Apache 2 Shared Module Server Project
建立 Apache 2 Shared Module Server 程式。
Apache Shared Module Server Project
建立 Apache 1.x Shared Module Server 程式。
DLL Server Project
建立 DLL Server 程式。
ISAPI/NSAPI Server Project
建立 ISAPI NSAPI Server 程式。
RemObjects DataSnap Server Module
建立一個支援 DataSnap 操作的 TRODataSnapDataModule
Windows Executable Server Project
建立一個可獨立執行的 Server
表中除了 RemObjects DataSnap Server Module 之外,其它都是用來建立一個新 RO Server 專案,這裡請選擇最簡單的 Windows Executable Server Project ,按下 OK 後會開啟下面這個視窗 :
下表是這個視窗的欄位說明 :
欄位
說明
Project Name
專案名稱。
Service Library Name
Library 名稱,在 RO 1.x 中這個參數的用途不大。
Service Name
Web Service 的名稱。
Server Class
通訊協定,見 Server 類元件。
Message Class
訊息協定,見 Client 類元件。
Project Directory
專案存放的目錄。
輸入資訊後按下 OK 就完成了骨架程式的建立工作了,這個程式已包含所有必須
用到的元件,接下來只需啟動位於主選單上的 RemObjects->Service Builder 工具定義 Web Services 的方法即可完成 Server 端的程式 :
 RO 預設會幫使用者產生兩個方法,一個是 Sum 、另一個是 GetServerTime( 不要都不行 …) ,為求簡單 ! 這裡直接運用這兩個方法,不做任何的變動。請將 Service Builder 關閉後編譯這個專案,此時 RO 會跳出一個對話窗詢問是否產生 Service 的定義與實作檔案,請選擇是,並且在產生後切換到 CalcService_Impl.pas 加入這兩個方法的實作碼 :
function TCalcService.Sum(const A: Integer; const B: Integer): Integer;
begin
  Result:=A+B;
end;
 
function TCalcService.GetServerTime: DateTime;
begin
  Result:=Now;
end;
最後將 TROIndyHTTPServer( 她被命名為 ROServer) Active 設為 Ture 就完成了 Server 端的程式了 ( 假如選擇的是 BPDX 類元件,那麼這個動作必須寫於程式中,因為 BPDX 不允許在設計時期啟動 )
在撰寫 Client 端程式之前必須先將 Server 程式執行起來,因為 Client 端必須由 Server 取得 WSDL( 事實上也可以直接由 RODL(RO 的定義檔 ) 產生呼叫端的程式,後面會介紹這一部份 ) ,接著請開立一個新的專案,並且在其 FORM 上放入 TROWinInetHTTPChannel TROSOAPMessage 兩個元件,然後啟動位於主選單上的 RemObjects->Import Service 來讀入 WSDL 定義 (Import WSDL):
8099 TROIndyHTTPChannel 預設的 Port ,讀者可經由 TROIndyHTTPChannel.Port 設定可更改其 Port 位址。
按下 Import 後會開出 Service Builder ,其中可以看到 CalcService 的定義,將 Service Builder 關閉後 RO 會要求輸入檔案名稱,請將她命名為 CalcService_Intf.pas 。最後有個不方便的地方,由於 Service Builder 的一個 Bug ,這個檔案必須做一些修改才能正常運作 :
//CalcService_EndPointURI = 'http://localhost/soap';
CalcService_EndPointURI = 'http://localhost:8099/soap';
//CalcService_DefaultURN = 'urn-NewLibrary:CalcService';
CalcService_DefaultURN = 'CalcService';
註解部份是原來的程式碼,粗體字部份是修改後的程式碼,這個 Bug 已經被確認在下一個版本就會修正。
最後加上一些 UI 介面在 FORM 上,並加上呼叫 Web Services 的程式碼就完成了 Client 端程式了,下圖是 UI 介面 :
接著是呼叫 Web Services 的程式碼 :
……………
uses CalcService_Intf;
 
procedure TfmMain.btnCalcClick(Sender: TObject);
var
  vService:CalcService;
begin
  vService:= CoCalcService.Create(ROSOAPMessage1,ROWinInetHTTPChannel1);
  try
    lblResult.Caption:=IntToStr(vService.Sum(StrToInt(edtValue1.Text),StrToInt(edtValue2.Text)));
  finally
    vService:=Nil;
  end;
end;
 
procedure TfmMain.Button1Click(Sender: TObject);
var
  vService:CalcService;
begin
  vService:= CoCalcService.Create(ROSOAPMessage1,ROWinInetHTTPChannel1);
try
    lblServerTime.Caption:=DateTimeToStr(vService.GetServerTime);
  finally
    vService:=Nil;
  end;
end;
下圖是這個程式的執行畫面 :
很簡單是吧 ?? 唯一美中不足的是那個 Bug….
範例程式中附上了 BizSnap .NET Java 三個 Client 端的原始碼,有興趣的讀者可自行觀看,這裡就不再說明了。
 
 
非同步呼叫模式
 
  RO 支援非同步呼叫模式,簡單的說就是建立一個執行緒來呼叫遠端的 Web Services ,這樣在呼叫 Web Services 的期間主執行緒就可以做其它的事,不會因為長時間的呼叫而導致程式無回應的狀況出現。由於目前版本的 Service Builder 並不會自動產生非同步呼叫的 Unit ,因此使用者必須自行使用命令列的 RODL2.EXE 來產生這個 Unit ,這個工具位於 RemObjects SDK/Bin 目錄下,只要鍵入下面這個命令就可產生出這個 Unit:
RODL2 /rodl:CalcLibrary.rodl /language:pascal /type:async
CalcLibrary.rodl 是由 Service Builder 所產生出來的,通常位於 Server 所在目錄下, /language:pascal 則是指定產生 pascal 語言的程式碼 (RO 未來支援 Pascal C# 兩種語言 ) /type:async 則是表示產生出非同步呼叫的 Unit ,完成後會產生一個名為 CalcLibrary_Async.pas 的程式檔,將它加入 Client 專案中,並修改呼叫 Web Services 部份的程式碼 :
uses CalcLibrary_Async;
 
procedure TfmMain.btnCalcClick(Sender: TObject);
var
  vService:CalcService_Async;
begin
  ROWinInetHTTPChannel1.TargetURL:='http://localhost:8099/soap';
  vService:=CoCalcService_Async.Create(ROSOAPMessage1,ROWinInetHTTPChannel1);
  try
    vService.Invoke_Sum(StrToInt(edtValue1.Text),StrToInt(edtValue2.Text));
    Sleep(3000);
    lblResult.Caption:=IntToStr(vService.Retrieve_Sum);
  finally
    vService:=Nil;
  end;
end;
 
由於產生的 CalcLibrary_Async.pas 有一些小 Bug ,編譯時 uses 區段會有錯誤,請將 uses CalcLibrary_Intf 改為 CalcService_Intf ,這樣就可以正常編譯並執行了。
 
 
多種訊息標準
 
  前面的章節曾經提過, RO 支援混用多種通訊協定與訊息標準,這個功能使得 Server 的延展性大幅提升,要完成這個功能的步驟相當簡單,請開啟前一節的 Server 端專案,並在 FORM 上放上一個 TROBINMessage 元件,接著雙按 TROIndyHTTPServer.Dispatcher 屬性開出下面這個對話窗 :
請按下 Add 的按紐,接著在 Message 處選擇 ROBinMessage1 ,這樣就算完成了訊息標準的設定了,讀者也可以藉由更改 Path Info 屬性來變更這個訊息所對應的 URL ,除此之外, TROBinMessage 支援將資料壓縮後再傳送,這個選項預設是開啟的。現在 Server 已經可以支援 SOAP Binary 兩種訊息格式了,編譯後執行程式後開啟 Client 端的專案,並在 FORM 上放入一個 TROBinMessage 與切換使用訊息的 RadioGroup 元件 :
最後修改呼叫端的程式碼 :
function TfmMain.GetService:CalcService;
begin
  Result:=Nil;
  case RadioGroup1.ItemIndex of
       0:
        begin
         ROWinInetHTTPChannel1.TargetURL:='http://localhost:8099/soap';
         Result:=CoCalcService.Create(ROSOAPMessage1,ROWinInetHTTPChannel1);
        end;
       1:
        begin
         ROWinInetHTTPChannel1.TargetURL:='http://localhost:8099/bin';
         Result:=CoCalcService.Create(ROBINMessage1,ROWinInetHTTPChannel1);
        end;
  end;
end;
 
procedure TfmMain.btnCalcClick(Sender: TObject);
var
  vService:CalcService;
begin
  vService:=GetService;
  try
    lblResult.Caption:=IntToStr(vService.Sum(StrToInt(edtValue1.Text),StrToInt(edtValue2.Text)));
  finally
    vService:=Nil;
  end;
end;
 
procedure TfmMain.Button1Click(Sender: TObject);
var
  vService:CalcService;
begin
  vService:=GetService;
  try
    lblServerTime.Caption:=DateTimeToStr(vService.GetServerTime);
  finally
    vService:=Nil;
  end;
end;
粗體字是變動後的程式碼,主要在於切換不同的訊息標準。完成後執行程式,這時 Client 已經可以使用 SOAP Binary Server 端溝通了,當然 ! 兩個不一樣的 Client 可以同時使用兩種不同的訊息標準與 Server 溝通。
要了解 SOAP Binary 的差別,只需使用 TCPTrace 這個工具來觀察 Client Server 的訊息流動狀態 :
 
SOAP
BINARY
兩種訊息有著近 10 倍的差距。
 
 
訊息編碼
 
  除了支援 SOAP BINARY 訊息格式之外, RO 也允許將訊息編碼,這對需要較高安全性的 Multi-Tier 系統來說相當有用,要為系統加入這個功能,只須要設定 ROServer Encryption 中的幾個屬性即可,首先選取欲使用的編碼演算法 :
 
接著再設定 EncryptionRecvKey EncryptionSendKey 就可以了,這兩個特性還支援多種演算法,這可以防堵有能力的破解高手經由反組譯取得加密與解密的 Key:
如有需要,也可以將 Encryption.UseCompression 設為 True ,這樣更增加訊息在傳送期間的安全性。
 
 
多種通訊協定
 
  上一節中實作了支援多種訊息的 Server Client 端,這一節將繼續實作支援多種通訊協定的 Server 端與使用不同通訊協定的 Client 端。首先開啟 Server 端的專案,在 FORM 上放入 TROIndyTCPServer TROWinMessageServer ,接著設定 TROWinMessageServer.ServerID ”ROCalcServer” ,完成後請雙按 TROIndyTCPServer.Dispatcher TROWinMessageServer.Dispatcher 來設定其支援的訊息元件,由於這兩種 Server 只允許一個訊息元件存在,在本例中選擇 Binary ,最後將這兩個元件的 Active 設成 Ture 就完成了,下面兩個程式是使用 TCP Windows Message Client 端程式碼,完整的程式可於範例目錄中找到 :
 
TCPChannel
procedure TfmMain.btnCalcClick(Sender: TObject);
var
  vService:CalcService;
begin
  vService:=CoCalcService.Create(ROBINMessage1,ROIndyTCPChannel1);
  try
    lblResult.Caption:=IntToStr(vService.Sum(StrToInt(edtValue1.Text),StrToInt(edtValue2.Text)));
  finally
    vService:=Nil;
  end;
end;
 
procedure TfmMain.Button1Click(Sender: TObject);
var
  vService:CalcService;
begin
  vService:=CoCalcService.Create(ROBINMessage1,ROIndyTCPChannel1);
  try
    lblServerTime.Caption:=DateTimeToStr(vService.GetServerTime);
  finally
    vService:=Nil;
  end;
end;
 
Window Message Channel
procedure TfmMain.btnCalcClick(Sender: TObject);
var
  vService:CalcService;
begin
  vService:=CoCalcService.Create(ROBINMessage1,ROWinMessageChannel1);
  try
    lblResult.Caption:=IntToStr(vService.Sum(StrToInt(edtValue1.Text),StrToInt(edtValue2.Text)));
  finally
    vService:=Nil;
  end;
end;
 
procedure TfmMain.Button1Click(Sender: TObject);
var
  vService:CalcService;
begin
  vService:=CoCalcService.Create(ROBINMessage1,ROWinMessageChannel1);
  try
    lblServerTime.Caption:=DateTimeToStr(vService.GetServerTime);
  finally
    vService:=Nil;
  end;
end;
執行畫面與之前的程式大同小異,這裡就不再貼上來了。
 
 
複雜資料型態(Complex Type)
 
  Web Services 除了可以表示簡單的資料型態之外,也可以表示複雜的資料型態,這一部份在 RO 中稱之為 Struct( 結構 ) ,這一節中將介紹如何在 RO 中定義這種資料型態。
其實步驟相當簡單,只需在 Service Builder 中按下 Struct 按紐新增一個 Struct 資料型態,並在其下方定義內部的結構就可以了,如下圖 :
然後定義一個方法,將其傳回值設成這個 Struct 即可;
 
下面是這個方法的實作碼 :
 
function TComplexTypeService.GetPerson: Person;
begin
  Result:=Person.Create;
  Result.Name:=' 黃忠成 ';
  Result.Age:=18;
end;
 
Client 端部份只需以 Import WSDL 或是 Import RODL( 選擇位於 Server 專案目錄下的那個 ComplexTypeService.rodl) 即可產生 Invoke 部份的程式碼,最後只要加入呼叫 Web Services 的程式碼就算完成 Client 端了 :
Procedure TfmMain.Button1Click(Sender: TObject);
var
  vService:ComplexTypeService;
  vData:Person;
begin
  vService:=CoComplexTypeService.Create(ROSOAPMessage1,ROWinInetHTTPChannel1);
  try
    vData:=vService.GetPerson;
    try
     edtName.Text:=vData.Name;
     edtAge.Text:=IntToStr(vData.Age);
    finally
     vData.Free;
    end;
  finally
    vService:=Nil;
  end;
end;
 
完整的程式請參照範例,除了 Struct 之外,眼尖的讀者應該也看到了 RO 支援 Enum( 列舉 ) Array( 陣列 ) 型態,這些型態的使用方式大同小異,日後 2.x 會增加 Set Exception 的型態定義。
另外有一點必須注意,當 Client 端是 .NET RO 會產生中文亂碼的問題,這一點目前筆者與 RO Team 正在討論中,下一版本就可解決。
 
 
DataSnap 支援
 
  前面曾經提過, RO 允許 DataSnap 運行於其上,這給了程式設計師一個相當大的禮物,以往 DataSnap 只能運行於 CORBA DCOM Socket SOAP 之上,前面三個通訊協定都屬於封閉型的,而 SOAP 又會遭遇效率低落的問題,使用 RO 的話不但可以使用 HTTP TCP Windows Message DLL ,又可以混用 SOAP Binary 兩種訊息格式,同時具備了延展性與效率,其建構步驟也相當簡單,這一節中將撰寫一個簡單的 RO DataSnap Server Client 端。首先請建立一個新專案,並如往常般放置 TROIndyHTTPServer TROSOAPMessage TROBinMessage FORM 上,接著開啟 New Dialogs 來加入一個 RO DataSnap DataModule:
在其上放置 TDatabase TSession TQuery TDataSetProvider 等元件 :
接著照以往設定 Remote DataModule 般設定這幾個元件的關聯,最後雙按 DataModule.Providers 加入欲開放給 Client 端的 DataSetProvider 就完成了 Server 端程式了。
Client 端程式也很簡單,只需放置需要的 UI 元件及 TROWinInetHTTPChannel TROBinMessage TRODataSnapConnection 再做一些設定即可,這部份有一點需注意,由於 Indy Bug ,因此無法在 Design Time 混用 TRODataSnapConnection TROIndyHTTPChannel ,因此請先利用 TROWinInetHTTPChannel ,待設定好各 ClientDataSet ProviderName 後再將 TROWinInetHTTPChannel 換成 TROIndyHTTPChannel( 如果需要的話 )
現在設定 TROWinInetHTTPChannel.TargetURL http://localhost:8099/bin ,並將其 Connected 設成 True ,接著切換至 TRODataSnapConnection 設定 Message Channel 這兩個特性值分別為 TROWinInetHTTPChannel TROBinMesage 後將 Connect 設成 True ,現在放置一個 TClientDataSet Form 上,並設定其 RemoteServer TRODataSnapConnection ,完成後應該就能在 ProvideName 的下拉盒中找到 DataSetProvider1 了,最後放置一個 DataSource 與相關的 UI 元件就完成了 :
一個簡單但完整的 3-Tier 程式就完成了。
 
 
PacketRecords
 
  DataSnap PacketRecords 一直都是多數人的最愛, RO 也支援這個功能,但事實上這只是 RO 眾多物件建立模式的一種,下面的章節中會介紹 RO 的物件模式與設計概念,這裡只需修改 DataSnap Server Data Module 程式碼就可支援這個功能 :
Initialization
  { To adjust scalability of the DataSnapModule and avoid recreation of
    instances for each roundtrip, you might want to choose a  different
    ClassFactory type.
 
    Make sure to check the following url for more information:
    http://www.remobjects.com/page.asp?id=C771266D-6D99-4301-B77D-D7B92D3BCD4D }
 
  //TROPooledClassFactory.Create('IAppServer', Create_DataSnapModule, TIAppServer_Invoker, 25);
  //TROClassFactory.Create('IAppServer', Create_DataSnapModule, TIAppServer_Invoker);
  TROPerClientClassFactory.Create('IAppServer', Create_DataSnapModule, TIAppServer_Invoker, 20*60 { seconds Session timeout });
TROPerClientClassFactory 會為每一個 Client 建立一個專有的 Service 物件,因此運用她就可使 PacketRecords 功能正常運作。
 
 
DataSnap Server Method
 
  以往在撰寫 DataSnap Server 時常常會定義一些供 Client 端呼叫的方法,在 RO 中一樣可以這樣做,不過因為 RO 目前版本的一些限制,所以無法很便利的定義這些方法,這點日後會改善 ( 事實上,在 RO 2.x Beta 中這一點已經解決了 )
 
 
RemObjects SDK Server Client運作模式
 
  相信許多讀者一定很好奇, RO 內部到底是如何處理 Client 端的呼叫, Server 又是如何將 SOAP 轉換為呼叫動作執行目的物件中的方法呢 ?? 事實上,在這一點上 RO 有非常獨到的見解,在詳細介紹其運作模式之前,筆者先就目前幾種主流的 Web Services 開發工具的運作模式做一簡單的介紹,其中包括 BizSnap .NET Web Services .NET Remoting Java
 
BizSnap.NET Remoting Server端運作模式
 
  BizSnap .NET Remoting 有著非常相似的處理模式,甚至可以說除了實作的語言與編譯技術不同之外,其主要的概念是完全相同的,下圖是其運作模式的流程圖 :
Client Request 送達 Server 端後,會經過一個 Message Dispatcher 機制,這個機制大多是幾個重要的元件合作完成,主要在於解出 Request 中對於所要求物件的描述,以及欲呼叫的方法等資訊,有了這些資訊後 Dispatcher 就可以找到對應的物件與方法,接著就開始了呼叫動作,由於 Request SOAP 訊息格式,並不能直接用來呼叫物件的方法,因此得先將 SOAP 訊息轉化為 Stack( 堆疊 ) ,完成這個轉換動作後就到了這種處理模式中的核心概念了,也就是建立起目的物件並呼叫對應的方法,這個動作非常依賴前面的 Message To Stack 程序,因為這個程序會將 SOAP 訊息轉化為 Stack ,有了 Stack 之後 Push Stack and Call Method 動作才能正確的執行,那麼如何呼叫目的方法呢 ? 很簡單,只要利用該語言所提供的 RTTI 資訊 (.NET 中則是 MetaData) ,就可取得該方法的記憶體位址,接著只須以低階的 ASM IL 所提供的 CALL 指令即可呼叫該方法,由於已將 SOAP 訊息轉為 Stack ,因此傳入參數就不是問題了。在呼叫結束後, Stack 中已經有了傳回的參數,接著只須將 Stack 轉回 SOAP 訊息傳回給 Client 端就可以了。
 
BizSnap.NET Remoting Client端運作模式
 
  上一節所提的是 Server 端接到要求後的處理流程,這一節將介紹 Client 端的呼叫動作,
下圖是大略的流程 :
不管是 BizSnap 或是 .NET Remoting ,當 Client 端欲呼叫 Web Services 時都會經過一個 Proxy Object ,於 BizSnap 中這個物件就是 THTTPRIO .NET Remoting 中這個物件就是 RealProxy ,由於這個物件屬於靜態的,因此在使用之前必需將其轉型回目的物件的型別,當 Client 端下達轉型動作後整個魔法就開始運行了,首先 Proxy Object 會利用 RTTI 或是 MetaData 資訊取得欲轉型的類別資訊,並依照這些資訊建立起一個相容於該類別的物件 (Transparent Proxy Object) ,接著將這個物件中的所有方法位址替換為 Stub Method Stub Method 做的事情很單純,只是將 Stack 轉為 SOAP Message 後送出,當 Server 端回應後再將 SOAP Message 轉換為 Stack 後返回,這樣整個 Client 端呼叫動作就完成了,下次再呼叫時只需由 Cache 中取出這個已建立好的 Transparent Proxy Object ,就可以直接進行呼叫,這可以避免因反覆以 RTTI 或是 MetaData 建立 Transparent Proxy Object 而失去效率。
BizSnap .NET Remoting 的處理模式屬於較低階的方法,這種方法的壞處大於好處,
好處是設計者可以完全不了解其內部運作,以傳統方式來撰寫程式,壞處是過度依賴編譯器及平台,增加日後移植到其它語言或平台上的難度,另外使用動態產生物件類別的低階技術很容易引發效率及記憶體管理的問題。
 
 
.NET Web Services Java
 
  .NET Web Services Java 的處理模式與 .NET Remoting BizSnap 大同小異,其間最大的不同之處在於這種模式利用了其語言的特性,採動態呼叫的方式來執行呼叫的動作,而非如先前所提的模式在 Stack Message 之間進行轉換,這種模式簡單的在 Client 端與 Server 端之間插入一個預先編譯好的 Proxy Object ,這個 Object 是由 WSDL 所產生的,其中定義了所有目標物件的方法,在這些方法中簡單的將傳入的參數轉換為 SOAP Message ,將傳回的訊息轉回參數,其間的運作完全屬於高階型態 :
 
Client 端的呼叫
 
Server 端的處理
由上面兩個圖上可看出,這種模式講求簡單, Client 端的 Stub Method 由原本的一個變成每個方法一個, Server 端則由原本的低階 CALL 命令轉為語言本身所提供的動態呼叫功能。這樣的簡化帶來了效率,由於 Client 端不再經過動態轉型與建立中介物件的動作,因此在效率上有顯著的提升,也因為少了這些低階的動作,記憶體管理上顯得容易多了。但這種方式卻有著另外的幾個缺點,由於 Proxy Object 的程式碼增加,相對的程式所佔用的記憶體也隨之變大,另外 Server 採用動態呼叫的方式來喚起方法,這種方式通常效率不高。
 
 
RemObjects SDK
 
  前面所提的兩種模式皆有其優缺點, RO 在這方面則提出了另一個嶄新的處理模式,
下圖是 RO Server 端處理模式 :
 
上圖中大約與前面所提及的模式相同,其中不同之處在於 Invoke Object ,這是一個預先編譯好的物件,其作用與 .NET Web Services Proxy Object 相同,這個物件中所有方法都是 Stub Method ,將 SOAP 訊息轉為參數後呼叫 Real Object(Implement Object) 的方法,完成後將參數轉回訊息後返回 Client 端。那麼這種模式有何獨到之處呢 ?? 答案是效率,整個動作之中看不到低階的 Stack 或是動態呼叫,沒有這些動作的加入,當然速度上也就加快不少。
 
RO Client 端處理方式與 Server 端大同小異,因此結論是 ! RO 沒有用到任何的中介技術,也沒有用到任何語言獨有的功能,這也是 RO .NET 為何能在短短的幾個月內就能完成的主要原因。下面是 CalcService xxx_Intf.pas xxx_Invk.pas xxx_Impl.pas 的原始碼,交替著看應可讓讀者更清楚這種模式的運作。
 
unit CalcLibrary_Invk;
 
{----------------------------------------------------------------------------}
{ This unit was automatically generated by the RemObjects SDK after reading  }
{ the RODL file associated with this project .                               }
{                                                                            }
{ Do not modify this unit manually, or your changes will be lost when this   }
{ unit will regenerated the next time you compile the project.               }
{----------------------------------------------------------------------------}
 
interface
 
uses
  {vcl:} Classes,
  {RemObjects:} uROServer, uROServerIntf, uROTypes, uROClientIntf,
  {Generated:} CalcLibrary_Intf;
 
type
  TCalcService_Invoker = class(TROInvoker)
  private
  protected
  published
    procedure Invoke_Sum(const __Instance:IInterface; const __Message:IROMessage; const __Transport:IROTransport);
    procedure Invoke_GetServerTime(const __Instance:IInterface; const __Message:IROMessage; const __Transport:IROTransport);
  end;
 
implementation
 
uses
  {RemObjects:} uRORes;
 
{ TCalcService_Invoker }
 
procedure TCalcService_Invoker.Invoke_Sum(const __Instance:IInterface; const __Message:IROMessage; const __Transport:IROTransport);
{ function Sum(const A: Integer; const B: Integer): Integer; }
var
  A: Integer;
  B: Integer;
  Result: Integer;
begin
  try
    __Message.Read('A', TypeInfo(Integer), A, []);
    __Message.Read('B', TypeInfo(Integer), B, []);
 
    Result := (__Instance as CalcService).Sum(A, B);
 
    __Message.Initialize(__Transport, 'CalcLibrary', 'CalcService', 'SumResponse');
    __Message.Write('Result', TypeInfo(Integer), Result, []);
    __Message.Finalize;
 
  finally
  end;
end;
 
procedure TCalcService_Invoker.Invoke_GetServerTime(const __Instance:IInterface; const __Message:IROMessage; const __Transport:IROTransport);
{ function GetServerTime: DateTime; }
var
  Result: DateTime;
begin
  try
 
    Result := (__Instance as CalcService).GetServerTime;
 
    __Message.Initialize(__Transport, 'CalcLibrary', 'CalcService', 'GetServerTimeResponse');
    __Message.Write('Result', TypeInfo(DateTime), Result, [paIsDateTime]);
    __Message.Finalize;
 
  finally
  end;
end;
 
end.
 
unit CalcLibrary_Intf;
 
{----------------------------------------------------------------------------}
{ This unit was automatically generated by the RemObjects SDK after reading  }
{ the RODL file associated with this project .                               }
{                                                                            }
{ Do not modify this unit manually, or your changes will be lost when this   }
{ unit will regenerated the next time you compile the project.               }
{----------------------------------------------------------------------------}
 
interface
 
uses
  {vcl:} Classes, TypInfo,
  {RemObjects:} uROProxy, uROTypes, uROClientIntf;
 
const
  LibraryUID = '{D62FF60B-5567-43E3-8386-161869372E27}';
 
 
type
  { Forward declarations }
  CalcService = interface;
 
  { CalcService }
 
  { Description:
      Service CalcService. This service has been automatically generated using the RODL template you can find in the Templates directory. }
  CalcService = interface
    ['{D62FF60B-5567-43E3-8386-161869372E27}']
    function Sum(const A: Integer; const B: Integer): Integer;
    function GetServerTime: DateTime;
  end;
 
  { CoCalcService }
  CoCalcService = class
    class function Create(const aMessage : IROMessage; aTransportChannel : IROTransportChannel) : CalcService;
  end;
 
implementation
 
uses
  {vcl:} SysUtils,
  {RemObjects:} uRORes;
 
type
  TCalcService_Proxy = class(TROProxy, CalcService)
  private
  protected
    function Sum(const A: Integer; const B: Integer): Integer;
    function GetServerTime: DateTime;
  end;
 
{ CoCalcService }
 
class function CoCalcService.Create(const aMessage : IROMessage; aTransportChannel : IROTransportChannel) : CalcService;
begin
  result := TCalcService_Proxy.Create(aMessage, aTransportChannel);
end;
 
{ TCalcService_Proxy }
 
function TCalcService_Proxy.Sum(const A: Integer; const B: Integer): Integer;
var __request, __response : TMemoryStream;
begin
  __request := TMemoryStream.Create;
  __response := TMemoryStream.Create;
 
  try
     __Message.Initialize(__TransportChannel, 'CalcLibrary', 'CalcService', 'Sum');
     __Message.Write('A', TypeInfo(Integer), A, []);
     __Message.Write('B', TypeInfo(Integer), B, []);
     __Message.Finalize;
 
     __Message.WriteToStream(__request);
     __TransportChannel.Dispatch(__request, __response);
     __Message.ReadFromStream(__response);
 
     __Message.Read('Result', TypeInfo(Integer), result, []);
  finally
    __request.Free;
    __response.Free;
  end
end;
 
function TCalcService_Proxy.GetServerTime: DateTime;
var __request, __response : TMemoryStream;
begin
  __request := TMemoryStream.Create;
  __response := TMemoryStream.Create;
 
  try
     __Message.Initialize(__TransportChannel, 'CalcLibrary', 'CalcService', 'GetServerTime');
     __Message.Finalize;
 
     __Message.WriteToStream(__request);
     __TransportChannel.Dispatch(__request, __response);
     __Message.ReadFromStream(__response);
 
     __Message.Read('Result', TypeInfo(DateTime), result, [paIsDateTime]);
  finally
    __request.Free;
    __response.Free;
  end
end;
 
 
initialization
end.
 
unit CalcService_Impl;
 
{----------------------------------------------------------------------------}
{ This unit was automatically generated by the RemObjects SDK after reading  }
{ the RODL file associated with this project .                               }
{                                                                            }
{ This is where you are supposed to code the implementation of your objects. }
{----------------------------------------------------------------------------}
 
interface
 
uses
  {vcl:} Classes,SysUtils,
  {RemObjects:} uROClientIntf, uROTypes, uROServer, uROServerIntf,
                uRORemoteDataModule,
  {Generated:} CalcLibrary_Intf;
 
type
  TCalcService = class(TRORemoteDataModule, CalcService)
  private
  protected
    function Sum(const A: Integer; const B: Integer): Integer;
    function GetServerTime: DateTime;
  end;
 
implementation
 
{$R *.DFM}
uses
  {Generated:} CalcLibrary_Invk;
 
procedure Create_CalcService(out anInstance : IUnknown);
begin
  anInstance := TCalcService.Create(NIL);
end;
 
function TCalcService.Sum(const A: Integer; const B: Integer): Integer;
begin
  Result:=A+B;
end;
 
function TCalcService.GetServerTime: DateTime;
begin
  Result:=Now;
end;
 
initialization
  TROClassFactory.Create('CalcService', Create_CalcService, TCalcService_Invoker);
 
finalization
 
end.
 
 
RemObjects SDK Class Factory
 
  還記得前面在 DataSnap 中所提到的 TROPerClientClassFactory ?? 她是 RO 眾多 ClassFactroy 的一種,預設情況下,當使用 Service Builder 建立一個新的 Service 之後, RO 會產生一個以 TROClassFactroy 建立的 Service 物件,這些 ClassFactory 可以主宰物件的建立與釋放方式,這在 Multi-Tier 程式中是極重要的課題,挑選一個適合的 ClassFactroy 會直接影響 Server 的效率與資源耗損量,下表分別介紹 RO 所提供的幾個 ClassFactorys
 
ClassFactory
說明
建立方式
TROClassFactory
預設產生的 ClassFactory ,會在收到 Client 端要求後建立目標物件,完成呼叫動作後釋放該物件,簡單的說就是 Single-Call 模式。
TROClassFactory.Create('CalcService', Create_CalcService, TCalcService_Invoker);
TROSingletonClassFactory
Singleton 模式,不管 Client 端有多少個,只使用一個物件來服務所有 Client 端,使用這種模式必須注意多執行緒下的關鍵區段控制。
TROSingletonClassFactory.Create('SingletonService', Create_SingletonService, TSingletonService_Invoker);
TROSynchronizedSingletonClassFactory
TROSingletonClassFactroy 相同,唯一不同之處是此 ClassFactory 會自行處理多執行緒下的關鍵區段控制,不需設計者擔心。
TROSynchronizedSingletonClassFactory.Create('SingletonService', Create_SingletonService, TSingletonService_Invoker);
TROPooledClassFactory
使用 Pooling 機制來建立物件,簡單的說是預先建立一定數量的物件,不管 Client 端有多少, Server 端就只有一定數量的物件服務所有 Client 端,假如 Client 端大於 Server 端物件數量,有些 Client 端會面臨排隊等待或是失敗 ( 取決於第五個參數 ) Server 端的物件不會自行釋放。
  TROPooledClassFactory.Create('PooledService', Create_PooledService, TPooledService_Invoker, 2,[pbCreateAdditional],false);
最後三個參數分別代表著 Pool 的數量,也就是最大物件數, pbCreateAdditional 則是告知當 Client 端大於 Pool 物件數量時,是否建立新物件來因應,這個參數可以是 pbWait ,要 Client 端等待,或是 pbFail ,直接傳回錯誤訊息, pbCreateAdditional 則是直接建立新的物件服務 Client 端,這個新物件將以 Single-Call 模式執行,第三個參數則是控制是否在程式啟動後就將 Pool 數量的物件建立好,如果這個參數是否的話,那麼物件的建立會被延後到第一個 Client 端連上後再執行。
TROPerClientClassFactory
使用 Session 機制來建立物件,簡單的說就是每一個 Client 端都會有專用的 Server 端物件,也又是具狀態的模式。
TROPerClientClassFactory.Create('SessionSample', Create_SessionSample, TSessionSample_Invoker, 20*60 { seconds Session timeout });
最後一個參數是設定這個物件在建立後經過多少時間沒動作之後就釋放掉。
這些 ClassFactory 適用於大多數的 Multi-Tier 程式,這也是 RO 獨有的特色之一,唯一較可惜的是其它開發工具無法完全應用到這些功能,只有 RO Client 才能完全享受這些 ClassFactory 所帶來的便利。
 
 
TROWebBrokerServer
 
  RO 除了允許使用者撰寫獨立的 Server 之外,同時也允許將 Web Services 加入到既有的 Web Broker 專案中,這是一個非常特殊的模式,使用者只要在 Web Module 上放入 TROWebBrokerServer 元件,並將其 Active 設成 Ture 後執行主選單上的 RemObjects->Turn xxxx to RemObject Project 選項,即可將這個 Web Broker 專案轉換為 RO 的專案,並允許執行 Service Builder 來定義 Service ,有趣的是 ! 這些 Service 可以直接使用原本定義於這個專案中的所有 Data Module 與物件,這形成了一個相當特殊的運行模式,由於這牽扯到了 Web Broker 技術,因此本文不會詳細的敘述這個用法,日後有機會再與讀者分享這種特殊模式的運用。
 
PS: 記得在 TWebDispatcher 元件中加入一個 /soap Action
 
 
Call Back Support?
 
  Multi-Tier 程式中 Call Back 的支援是相當重要的, RO 1.x 中並沒有提供這個功能,其中牽扯到了一些設計上的概念問題,不過 2.x 中預計會提供這個功能,並且採用了可穿越防火牆與 NAT 的雙向溝通技術。
 
 
後記
 
  本文介紹了 RemObject SDK 大多數的功能,配合上 RO 所附的範例程式,相信對於運用這套元件應該不成問題,日後若有機會筆者會再針對其它深入的運用及細節做更詳細的介紹。
 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值