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.
可以在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
事件
新版本的
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含有很多体现这些新特性的范例.