RemObjects SDK3.0框架的几个新特性

    技术2022-05-11  3

    这个文档展示了 RemObjects SDK3.0 框架的几个新特性 . 这个文档只讨论代码和类级别的特性 . 其它的特性像均衡负载 , 容错 , 多服务器支持和服务器事件将在其它文档中介绍 . 这个文档中的范例代码可以在 Chat 范例和升级的 MegaMemo,DispatchNotifier 范例中找到 . 对象的生存期 当你在服务器上写一个返回复杂类型的方法时 ( 或者返回 , 或者 Var 参数 ),RemObjects 框架将其流化发送到客户端后将由客户端释放他们 . 假定你写一个简单的方法 : function TChatService.GetLoggedUsers: TUserInfoArray; begin  result := TUserInfoArray.Create;  result.Add;  result.Add; end; 在方法调用后 , 实例给返回结果赋值 , 发送到客户端 , 并释放 . 但是有时我们并不希望这样 . 假设你的服务器是使用 TROSingletonClassFactory 控件创建的 Singleton 模式的 , 这时需要像 GetLoggedUsers 方法一样返回一个内部对象 : type  { TChatService }  TChatService = class(TRORemoteDataModule, IChatService)  private     fUsers : TUserInfoArray; [..]   function TChatService.GetLoggedUsers: TUserInfoArray; begin  result := fUsers; end; 但是这样的话无论你返回什么都将被框架释放 . RemObjects 2.0 , 只有拷贝一个新的 fUsers 变量返回 ( 例如 result := fUsers.Clone;). RemObjects SDK 3.0 中提供了一个新特性可以让我们自己定义什么对象自动释放什么对象不自动释放 . 防止 fUsers 被释放 , 我们可以使用一个简单的方法 procedure RetainObject(const anObject : TObject); 下面的代码举例说明 : function TChatService.GetLoggedUsers: TUserInfoArray; begin  result := fUsers;  RetainObject(fUsers); end; 在方法调用完毕后 ,fUsers 不会释放 . 但在服务器关闭时必须释放掉 fUsers 实例 . 如果你的对象从下面的接口继承则可以通过架构管理其生存期: { IROObjectRetainer } IROObjectRetainer = interface ['{1DFCCCAB-CD61-415F-ADFB-258C067E9A59}']  procedure RetainObject(const anObject : TObject);  procedure ReleaseObject(const anObject : TObject);  function IsRetained(const anObject : TObject) : boolean; end; TRORemotable TRORemoteDataModule 被强化 , 支持 IROObjectRetainer 接口 . 对象生命周期也在客户端通过触发服务器事件实现了 . 我们将在以后讨论更多细节 . Variant支持 由于客户要求我们介绍一下对 Variant 数据类型的支持 . 你可以创建接受和返回 Variant 类型的服务器方法 . 下面的代码展示了使用 Variant 类型的接口服务器方法 : INewService = interface  ['{509D1C6D-51DF-4269-A160-DB5B5B671874}']  function Sum(const A: Integer; const B: Integer): Integer;  function GetServerTime: DateTime;  procedure EchoVariant(const InputVariant: Variant;                         out OutputVariant: Variant); end; 下面的截图展示了在客户端使用 EchoVariant 方法的例子 . 客户端发送不同类型的 Variant 类型 : integers, strings, datetime, boolean bytes 数组 . 注意这时 TROSOAPMessage 控件不支持 Byte 数组 . 当你在 Service Builder 中建立数组类型时 , RemObjects 可以自动在 XXX_INtf.pas 中生成两个类 . 一个是标准的 TCollection 对象 , 它可以使用 RTTI 反射机制 , 第二个是继承于 TROArray 的类 . 下面的代码是在 Chat 范例中自动产生的 : { TUserInfo } TUserInfo = class(TROComplexType) private  fUserID: String;  fSessionID: String; public  procedure Assign(iSource: TPersistent); override; published  property UserID:String read fUserID write fUserID;  property SessionID:String read fSessionID write fSessionID; end;   { TUserInfoCollection } TUserInfoCollection = class(TROCollection) protected  constructor Create(aItemClass: TCollectionItemClass); overload;  [..] public  constructor Create; overload;  function Add: TUserInfo; reintroduce;  procedure SaveToArray(anArray: TUserInfoArray);  procedure LoadFromArray(anArray: TUserInfoArray);  property Items[Index: integer]:TUserInfo read GetItems                                            write SetItems; default; end;   { TUserInfoArray } TUserInfoArray = class(TROArray) private  [..] protected  [..] public  [..]  function Add: TUserInfo; overload;  function Add(const Value: TUserInfo):integer; overload;   property Count : integer read GetCount;  property Items[Index: integer]:TUserInfo read GetItems                                            write SetItems; default; end; 使用这种类型你需要查找特定的项 , 例如要查找第一个 UserID ’Jack’ 的项 , 代码如下 : funcion SearchByUserID(anArray : TROUserInfoArray;  const aUserID : string) : TUserInfo; var i : integer; begin  result := NIL;    for i := 0 to (userarray.Count-1) do     if SameText(userarray[i].UserID, 'JACK') then begin       result := userarray[i];       Exit;     end; end; 不是很复杂 , 但是使用每个集合体都要写这些是很枯燥的 . TROCollection TROArray 类现在支持 GetIndex 方法和查询 , 允许只写一行代码就能查找集合体或数组 . RemObjects SDK 3.0 中我们作上面的查询 , 可以这样 : myuser := TUserInfo(userarray.Search('UserID', 'JACK')); Search GetIndex 方法声明如下 : function Search(const aPropertyName : string;  const aPropertyValue : Variant;  StartFrom : integer = 0;  Options : TROSearchOptions = [soIgnoreCase]) : TCollectionItem;   function GetIndex(const aPropertyName : string;  const aPropertyValue : Variant;  StartFrom : integer = 0;  Options : TROSearchOptions = [soIgnoreCase]) : integer; Search 方法返回一个集合或数组的一项 . 没有没有匹配的就返回 Nil.GetIndex 方法返回集合或数组的索引 . 附件的参数 StartFrom Options 可以更灵活的控制查找条件 . 自定义异常 The RemObjects SDK 3.0 增强了对自定义异常的支持并可以在新的异常类中添加自定义成员 . 你可以创建一个新的包含自定义类型成员的异常类型 , 并可以不用写序列化和解析代码就能完全的发送到客户端 .MegaDemo 范例中使用了这种特性 . MegaDemo 范例目录中的 NewService_Impl.pas 文件可以发现如下方法 : procedure TNewService.RaiseTestException; begin  raise ETestException.Create(     'This is the exception message',     666,     'Some extra info here'); end; Service Builder 中异常 ETestException 类型包含一个 ErrorCode 整型数型和 AdditionalInfo 字符串属性 . NewLibrary_Intf 单元中 RemObjects 自动生成的代码如下 : { Exceptions } ETestException = class(EROException) private  [..] public  constructor Create(const anExceptionMessage : string;                      aErrorCode: Integer;                      const aAdditionalInfo: String); published  property ErrorCode: Integer read fErrorCode write fErrorCode;  property AdditionalInfo: String read fAdditionalInfo                                   write fAdditionalInfo; end; 可以看到 ErrorCode AdditionalInfo 已经加入到了类和构造函数中 , 我们只需要一行代码就能抛出异常 . 此外 , 注册自定义异常的代码也自动在 NewLibrary_Intf 单元的 Initialization 结中生成 . unit NewLibrary_Intf; [..]   initialization  [..]  RegisterExceptionClass(ETestException);  [..]   finalization  [..]  UnregisterExceptionClass(ETestException);  [..]   end. 原来支持的标准异常也被扩展了 ,Delphi 中的标准异常都没有在 RemObjects 框架中注册 , 在客户端抛出 EROUnregisteredException 类型的异常 . MegaDemo 范例中的 RaiseError 方法 , 抛出一个 Delphi 异常 : procedure TNewService.RaiseError; begin  // Generic and unregistered exceptions  raise EDivByZero.Create('A fake div by zero!'); end; 客户端用简单的代码如下 : function TClientForm.InvokeRaiseError(const aService : INewService)  : integer; [..] begin  try     [..]     if not cbCustomException.Checked       then aService.RaiseError()       else aService.RaiseTestException;  except     on E:ETestException do begin       result := GetTickCount-start;       LogMessage('ETestException --> Message:"%s" ErrorCode:"%d"'+        ' AdditionalInfo:"%s"',         [E.Message, E.ErrorCode, E.AdditionalInfo], result, true);     end;       on E:Exception do begin result := GetTickCount-start;       LogMessage('Generic exception --> '+E.ClassName+' Message: '+                  E.Message, [], result, true);     end;  end; end; 很多异常都被改进并从 EROException 继承 . 上面谈到很多都是 EROException 子类的信息 . 联合服务器 (Combo Servers) RemObjects 项目类型中有一个新的模板 "Combo Standalone". 这个服务器是标准 VCL Standalone NT Service 应用程序的联合形式 , 你可以运行这个服务器项目做其它的事情 . 使用 "/install 命令行参数可以在 Windows NT/2000 的服务列表中注册服务 . 如果要在 Windows 9x 下就直接运行程序就可以了 . 使用 "/uninstall" 卸载 NT service. 可以在 TeamRO 的成员 Reinhold Erlacher 中看到这个模板 ! IROStreamAccess 接口 RemObjects 服务器允许你通过自定义类或实现了 IRODispatchNotifier 接口的类在一些方法执行前后去做一些控制 . 可以在 DispatchNotifier 范例中看到这两种方式 . TRORemoteDataModule 类后来提供了 OnGetDispatchInfo 事件而简化了第二种方式 . 事件 OnGetDispatchInfo 声明为 : procedure(const aTransport : IROTransport;           const aMessage : IROMessage) of object; 当我们有一个传输通道来接受远程请求时 , 通过 aMessage 参数可以读取消息名称 ( 例如 Sum GetServerTime), 但是这种方式只能限于 SOAP 消息其他的方式无法读出消息的值 . 原因在于二进制消息使用的是顺序流 , 所以有时这样做 : aMessage.Read('aMessage', TypeInfo(string), textmessage, []); 我们移动指针并中断调试这个消息 . RemObjects 3.0 通过实现 IROStreamAccess 接口扩充了 TROBINMessage. IROStreamAccess 接口定义如下 : IROStreamAccess = interface ['{DF3D000F-7EB3-4981-AA01-921553CAFF52}']  function GetStream : TStream;    property Stream : TStream read GetStream; end; 通过这个接口我们可以将消息流保存到文件 , 定为当前指针等 . 新的 DispatchNotifier 范例在 GetDispatchInfo 方法中利用这个特性 : procedure TDispService.GetDispatchInfo(const aTransport: IROTransport;  const aMessage: IROMessage); var tcpinfo : IROTCPTransport;     textmessage : string;     streamaccess : IROStreamAccess; begin  if Supports(aTransport, IROTCPtransport, tcpinfo)     then ServerForm.Log('Client '+tcpinfo.GetClientAddress+' connected!');    with aTransport do     ServerForm.Log('Got a reference to a '+GetTransportObject.ClassName);    with aMessage do begin     ServerForm.Log('About to invoke '+InterfaceName+'.'+MessageName);       if (MessageName='SendMessage') then begin      aMessage.Read('aMessage', TypeInfo(string), textmessage, []);       ServerForm.Log('The text message was "'+textmessage+'"');         { New RemObjects 3.0: now you can reset the position of the         message stream }       if Supports(aMessage, IROStreamAccess, streamaccess)         then streamaccess.Stream.Position := 0;     end;  end;    ServerForm.Log(''); end; TROSOAPMessage.OnEnvelopeComplete event 新版本的 TROSOAPMessage 控件为服务器提供了更好的兼容性 , 并有一个新的事件 OnEnvelopeComplete. OnEnvelopeComplete 定义如下 : procedure(Sender : TROSOAPMessage) of object; 这个事件允许我们在将 SOAP 包发送到客户端或服务器之前做更正或写入 . 新的 MegaDemo 范例在客户端利用这个新特性在 SOAP 添加 "Test" 报头 , 其值为 ”1234”. 下面的代码证明了这点 : procedure TClientForm.SOAPMessageEnvelopeComplete(Sender: TROSOAPMessage); begin  Sender.Header.Add('Test').Value := '1234';  memo1.Lines.Text := Sender.EnvNode.XML; end;   TROSOAPMessage也允许我们存取其他节点:       property EnvNode : IXMLNode read GetEnvNode;     property BodyNode: IXMLNode read GetBodyNode;     property MessageNode: IXMLNode read GetMessageNode;     property FaultNode : IXMLNode read GetFaultNode;     property Header : IXMLNode read GetHeader; 有一些属性可能不赋值 , 所以要在使用前检测其值是否为 NIL. 例如不要去存取 FaultNode 节点 , 它只用于向客户端反馈服务器端异常和错误信息 . New RemObjects_WebBroker package RemObjects SDK 以前的版本有一个单元包含 TROWebBrokerServer RemObjects_Core 包得一部分 . 它依赖于 INet , 而与 BPDX Indy 组件无关 . RemObjects 3.0 有一个新的包叫做 RemObjects_WebBroker, 这样你可以编译相关的 INet 包了 . New Events 为了开发者在消息序列化前后提供更好的控制 , RemObjects 3.0 增加了如下事件 : OnInitializeMessage, OnFinalizeMessage, OnWriteMessageParameter OnReadMessageParameter. 下面代码是 MegaDemo 范例的客户端 , 展示了如何使用这些事件 : procedure TClientForm.BINMessageInitializeMessage(Sender: TROMessage;  const aTransport: IROTransport; const anInterfaceName,  aMessageName: String); begin  if cbVerbose.Checked then LogMessage(Sender.Name+' is initialized', []); end;   procedure TClientForm.BINMessageFinalizeMessage(Sender: TROMessage); begin  if cbVerbose.Checked then LogMessage(Sender.Name+' is finalized', []); end;   procedure TClientForm.BINMessageReadMessageParameter(Sender: TROMessage;  const aName: String; aTypeInfo: PTypeInfo; const DataRef: Pointer;  Attributes: TParamAttributes); begin  if cbVerbose.Checked then LogMessage(Sender.Name+' is reading '+aName, []); end;   procedure TClientForm.BINMessageWriteMessageParameter(Sender: TROMessage;  const aName: String; aTypeInfo: PTypeInfo; const DataRef: Pointer;  Attributes: TParamAttributes); begin  if cbVerbose.Checked then LogMessage(Sender.Name+' is writing '+aName, []); end; 注意在服务器端处理这些事件可能会降低执行效率 . 单元文件删除 为了简化和使 RemObjects 组织更加流畅 , 删除了如下单元 : uROProxy, uROBaseConnection, uROXMLRes, uROComponents, uROPools, uROStreamHelpers, uROHelpers. 原代码生成器已经更新可以展示出这个变化 . 在你从新编译你的服务器和客户端是记住删除这些单元文件 . RemObjects SDK Samples 由于代码是去年的 , 很多例子对现在来说有些过时了 . RemObjects SDK 3.0 含有很多体现这些新特性的范例 .    

    最新回复(0)