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

来源:互联网 发布:java后端主要做什么 编辑:程序博客网 时间:2024/06/01 07:54
 
RO31 - RemObjects SDK 3.0代码级别的特性
本文对RemObjects SDK 3.0架构的特性做一个综述.这里只讨论代码和类级别的特性,其他如均衡负载,容错处理,多服务部署,服务事件的特性将在其他文档中说明.
本文中很多代码范例都可以从新的Chat范例和更新的MegaDameDispatchNotifer中发现.
持有对象
如果你写一个返回复杂类型(返回值类型或输出参数类型)的服务端方法, 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方法返回集合或数组的索引.附件的参数StartFromOptions可以更灵活的控制查找条件.
自定义异常
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参数可以读取消息名称(例如SumGetServerTime),但是这种方式只能限于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以前的版本有一个单元包含TROWebBrokerServerRemObjects_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含有很多体现这些新特性的范例.
 
原创粉丝点击