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封闭得相当好,所以了解客户端调用服务端接口的过程,关键是意识到
代理类,消息格式类,通道传输类 是如何被联结在一起的