RO31 - RemObjects SDK 3.0代码级别的特性

 
RO31 - RemObjects SDK 3.0 代码级别的特性
本文对 RemObjects SDK 3.0 架构的特性做一个综述 . 这里只讨论代码和类级别的特性 , 其他如均衡负载 , 容错处理 , 多服务部署 , 服务事件的特性将在其他文档中说明 .
本文中很多代码范例都可以从新的 Chat 范例和更新的 MegaDame DispatchNotifer 中发现 .
持有对象
如果你写一个返回复杂类型 ( 返回值类型或输出参数类型 ) 的服务端方法 , RemObjects 架构必须保证它们传输到客户端后释放 .
假如写一个简单的方法 Assume that you have written something similar to:
function TChatService.GetLoggedUsers: TUserInfoArray;
begin
  result := TUserInfoArray.Create;
 result.Add;
 result.Add;
end ;
方法调用后 , 返回值变量实例传输到客户端并释放 . 有时情况并不是像我们希望的那样 . 假设你的服务是使用 TROSingletonClassFactory 创建的 Singleton 模式 , 需要在方法中返回服务对象的成员 , GetLoggerUsers 方法的返回值 . 如下 :
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 实例 .
对象持有 (retention) 特性可以通过让对象实现如下接口获得 :
{ 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 接口 .
对象持有(retention)也在客户端通过触发服务器事件实现了.我们讲再以后讨论更多细节.  
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 数组 .
TROArray TROCollection 查询及 GetIndex 方法
当你在 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 可以更灵活的控制查找条件 .
自定义异常
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.
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 事件
新版本的 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 节点 , 它只用于向客户端反馈服务器端异常和错误信息 .
新的 RemObjects_WebBroker
RemObjects SDK 以前的版本有一个单元包含 TROWebBrokerServer RemObjects_Core 包得一部分 . 它依赖于 INet , 而与 BPDX Indy 组件无关 .
RemObjects 3.0 有一个新的包叫做RemObjects_WebBroker,这样你可以编译相关的INet包了.
新的事件
为了开发者在消息序列化前后提供更好的控制 , 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 范例
由于代码是去年的,很多例子对现在来说有些过时了. RemObjects SDK 3.0含有很多体现这些新特性的范例.
 
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值