zoukankan      html  css  js  c++  java
  • 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 ,它们做了什么呢?

    {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封闭得相当好,所以了解客户端调用服务端接口的过程,关键是意识到

    代理类,消息格式类,通道传输类 是如何被联结在一起的

  • 相关阅读:
    hdu 2485 Destroying the bus stations 迭代加深搜索
    hdu 2487 Ugly Windows 模拟
    hdu 2492 Ping pong 线段树
    hdu 1059 Dividing 多重背包
    hdu 3315 My Brute 费用流,费用最小且代价最小
    第四天 下载网络图片显示
    第三天 单元测试和数据库操作
    第二天 布局文件
    第一天 安卓简介
    Android 获取存储空间
  • 原文地址:https://www.cnblogs.com/erp-system/p/3473030.html
Copyright © 2011-2022 走看看