这个文档展示了
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
含有很多体现这些新特性的范例
.