RemObjects(一)客户端远程调用服务端接口过程

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 ,它们做了什么呢?

[delphi]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. {unit uROClient;}  
  2.    
  3. procedure RegisterMessageClass(aROMessageClass : TROMessageClass);  
  4. begin  
  5.   _MessageClasses.Add(aROMessageClass);  
  6.   if Classes.GetClass(aROMessageClass.ClassName) = nil then  
  7.     Classes.RegisterClass(aROMessageClass);  
  8. end;  
  9.    
  10. procedure RegisterTransportChannelClass(aTransportChannelClass : TROTransportChannelClass);  
  11. begin  
  12.   if _TransportChannels.IndexOf(aTransportChannelClass.ClassName)<0 then begin  
  13.     _TransportChannels.AddObject(aTransportChannelClass.ClassName, TObject(aTransportChannelClass));  
  14.     if Classes.GetClass(aTransportChannelClass.ClassName) = nil then  
  15.       Classes.RegisterClass(aTransportChannelClass);  
  16.   end;  
  17. end;  
  18.    
  19. procedure RegisterProxyClass(const anInterfaceID : TGUID; aProxyClass : TROProxyClass);  
  20. var s : string;  
  21. begin  
  22.   s := GUIDToString(anInterfaceID);  
  23.   if _ProxyClasses.IndexOf(s)<0  
  24.     then _ProxyClasses.AddObject(GUIDToString(anInterfaceID), TObject(aProxyClass))  
  25. end;  
  26.    
  27. initialization  
  28.   _MessageClasses := TClassList.Create;   //TClassList 只是给 TList 起个别名  
  29.   _ExceptionClasses := TClassList.Create;  
  30.    
  31.   _ProxyClasses := TStringList.Create;  
  32.   _ProxyClasses.Duplicates := dupError;  
  33.   _ProxyClasses.Sorted := TRUE;  
  34.    
  35.   _TransportChannels := TStringList.Create;  
  36.   _TransportChannels.Duplicates := dupError;  
  37.   _TransportChannels.Sorted := TRUE;  
  38.    
  39.   ... ...  


 

这里初始化了3个列表,将分别用于存储 消息格式类,代理类,传输通道类

而三个全局函数只是将相应的对象添加到列表

[delphi]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. {unit uROBinMessage;}  
  2.    
  3. initialization  
  4.   RegisterMessageClass(TROBinMessage);  
  5.    
  6. {unit uROWinInetHttpChannel;}  
  7.    
  8. initialization  
  9.   RegisterTransportChannelClass(TROWinInetHTTPChannel);  
  10.    
  11. {unit FirstSample_Intf;}  
  12.    
  13. initialization  
  14.  //第一个函数是接口ID,第二个是接口的实现类  
  15.   RegisterProxyClass(IFirstSampleService_IID, TFirstSampleService_Proxy);  


 

这样一来,程序启动的时候就已经完成了一系列操作

接下来到了主窗体创建时执行的代码

[delphi]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. {unit FirstSampleClientMain;}  
  2.    
  3. constructor TFirstSampleClientMainForm.Create(aOwner: TComponent);  
  4. begin  
  5.   inherited;  
  6.   fFirstService := (RORemoteService as IFirstSampleService);  
  7. end;  


 

也许对初学者来讲有点不可思议, 对象RORemoteService与接口IFirstSampleService之间根本不存在任何关系,居然可以 as ?
这是因为 as 操作会先调用接口查询 QueryInterface,看下TRORemoteService的QueryInterface函数是如何实现的

[delphi]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. {unit uRORemoteService;}  
  2.    
  3. function TRORemoteService.QueryInterface(const IID: TGUID; out Obj): HResult;  
  4. var  
  5.     proxyclass : TROProxyClass;  
  6.     proxy : TROProxy;  
  7. begin  
  8.   result := inherited QueryInterface(IID, Obj);  
  9.    
  10.   if (result <> S_OK) then begin  
  11.     //通过接口ID查询到接口实现类的引用  
  12.     proxyclass := FindProxyClass(IID, TRUE);  
  13.    
  14.     if (proxyclass=NIL) then Exit  
  15.     else begin  
  16.       CheckCanConnect(false);  
  17.     //创建接口实现对象  
  18.       proxy := proxyclass.Create(Self);  
  19.       proxy.GetInterface(IID, Obj);  
  20.       result := S_OK;  
  21.     end;  
  22.   end;  
  23. end;  


 

 其中的 FindProxyClass 定义在 ROClient

[delphi]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. {unit uROClient;}  
  2.    
  3. function FindProxyClass(const anInterfaceID : TGUID; Silent : boolean = FALSE) : TROProxyClass;  
  4. var idx : integer;  
  5.     s : string;  
  6. begin  
  7.   result := NIL;  
  8.   s := GUIDToString(anInterfaceID);  
  9.   idx := _ProxyClasses.IndexOf(s);  
  10.   if (idx>=0)  
  11.     then result := TROProxyClass(_ProxyClasses.Objects[idx])  
  12.   else begin  
  13.     if not Silent  
  14.       then RaiseError(err_UnknownProxyInterface, [s])  
  15.   end;  
  16. end;  


 

所以 fFirstService := (RORemoteService as IFirstSampleService); 就获取了代理类的实现接口

而代理类做了些什么呢? 

proxy := proxyclass.Create(Self);  //在TRORemoteService的QueryInterface函数

proxyclass是一个TROProxyClass对象,而TROProxyClass= class of TROProxy;也就是TROProxy类引用

看下TROProxy的构造函数

[delphi]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. {unit uROClient;}  
  2.    
  3. constructor TROProxy.Create(const aRemoteService: IRORemoteService);  
  4. begin  
  5.   Create(aRemoteService.Message, aRemoteService.Channel);  
  6.   fCloneMessage := aRemoteService.CloneMessage;  
  7. end;  
  8.    
  9. constructor TROProxy.Create(const aMessage: IROMessage;  
  10.   const aTransportChannel: IROTransportChannel);  
  11. begin  
  12.   inherited Create;  
  13.   fMessage := pointer(aMessage);  
  14.   fTransportChannel := pointer(aTransportChannel);  
  15.   fCloneMessage := False;  
  16. end;  


 

至此,一个 TRORemoteService对象 将

代理类 TFirstSampleService_Proxy,消息格式 TROBinMessage,传输通道TROWinInetHTTPChannel 联合在一起,

程序启动时执行的 fFirstService := (RORemoteService as IFirstSampleService); 就已经完成了这么多

接下来看接口函数的调用

[delphi]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. {unit FirstSampleClientMain;}  
  2.    
  3. procedure TFirstSampleClientMainForm.GetButtonClick(Sender: TObject);  
  4. begin  
  5.   NamesBox.Items.CommaText := fFirstService.Nicknames(eFullname.Text);  
  6. end;  


 

在客户端,接口IFirstSampleService的实现是在TFirstSampleService_Proxy类实现的,

而fFirstService := (RORemoteService as IFirstSampleService);已经完成了对象的创建,

所以fFirstService.Nicknames(eFullname.Text);实际调用的是TFirstSampleService的Nicknames函数

[delphi]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. function TFirstSampleService_Proxy.Nicknames(const FullName: UnicodeString): UnicodeString;  
  2. begin  
  3.   try  
  4.     __Message.InitializeRequestMessage(__TransportChannel, 'FirstSample', __InterfaceName, 'Nicknames');  
  5.     __Message.Write('FullName', TypeInfo(UnicodeString), FullName, []);  
  6.     __Message.Finalize;  
  7.    
  8.     __TransportChannel.Dispatch(__Message);  
  9.    
  10.     __Message.Read('Result', TypeInfo(UnicodeString), result, []);  
  11.   finally  
  12.     __Message.UnsetAttributes(__TransportChannel);  
  13.     __Message.FreeStream;  
  14.   end  
  15. end;  


 

 __Message是TROProxyr的一个属性,

[delphi]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. {unit uROClient;}  
  2.    
  3. function TROProxy._GetMessage: IROMessage;  
  4. begin  
  5.   result := IROMessage(fMessage);  
  6. end;  


 

从代码可以看出,其实就是代理类创建时从RORemoteService.Message传入,也就是窗体上的 ROBinMessage,

同理 __TransportChannel 就是窗体上的 ROWinInetHTTPChannel 。

 

TROBinMessage的基类是TROMessage,而Write,Read在TROMessage是虚函数

[delphi]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. {unit uROClient;}  
  2.    
  3. {TROMessage}  
  4.    
  5. procedure Write(const aName : string; aTypeInfo : PTypeInfo; const Ptr; Attributes : TParamAttributes); virtual;  
  6. 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、 RemObjects SDK 综述 欢迎使用RemObjects SDK,这个框架可用简单灵活的方式创建可升级高灵活性的多层系统。 多层系统 一个多层系统分为两层或两层以上。通常人们分为3层: 表示层:终端用户程序,Web页面或可执行文件 业务逻辑/中间层:这个层的对象(运行于一些不可见的容器中)执行确认和业务逻辑。 数据存储层:通常是数据库。 基于这个基础结构上还有很多其他形式的框架,并且都在我们文档讨论的范围之外.但是你必须知道很重要的一点,创建任何分布式系统都需要一种消息协议让客户端和中间层通讯. 标准的消息协议是RPC-protocol (DCOM的基础), Java的 RMI 或 SOAP. RemObjects SDK适合作什么 为什么当一些协议都是适用的我们还要”重复制造车轮”?这有以下几个原因: 对于DCom,如果你所有的机器都运行Windows系统并且你会配置安全,他可以在局域网中运行的很好.但COM/DCOM对Windows和Unix的通讯不适用.事实上他是Windows上的标准.而且你要在你的机器上使用基于HTTP的COM对象就必须为RPC-通讯打开防火墙的几个端口. 对于RMI,RMI是针对Java的. Borland从来没有提供和RMI通讯的工具.就算有这种工具,你还是不能和COM对象通讯. 而SOAP呢?它是唯一的公认标准消息. 看起来他实现了互用性,但是却建立在解析XML高代价之上. 除非你有高速网络或只需要发送很小的包,否则你很难使用它. 这样的例子还很很多. RemObjects的目标 RemObjects为实现下面的目标而设计: 简单:开发者不需要是专家,不需要很长的时间就可以为网络中的电脑或Internet的客户端中发布自己的简单服务.Delphi开发者不用面对自己不熟悉的语法.并且你可以轻松的理解他的原理. 高效:我们通常在本机的两个进程通讯时使用Socket,使用标准协议像Soap做客户端和服务器的通讯,为什么没有一种通用的方式可以发布我们的服务呢?RemObjects SDK允许我们创建高效的服务,并可以使用我们需要的协议方式通讯. 灵活:RemObjects SDK使用TCP/IP,HTTP,和Windows消息作为传输信道.而我们要使用UDP或管道时我们就可以轻松的创建新的信道,只要通知服务器和客户端即可,不用修改其他部分.RemObjects SDK的插入式框架可以让我们写一个简单的函数或实现一个接口IROTransportChannel即可建立新的通讯信道而扩展基础框架.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值