PHPRPC for Delphi 中自定义类型传输详解

PHPRPC for Delphi 中除了支持基本数据类型、容器类型传递以外,同样也支持自定义类型的传递,而能够进行传递的自定义类型的基类就是 TPHPObject。这个类是在 PHPRPC 单元中定义的。下面我们就对这个类进行一下深入的剖析。

首先第一个问题,为什么要以 TPHPObject 作为 PHPRPC 传输的自定义类型的基类,而不是 TPersistent 呢?

因为 PHPRPC 支持传输的数据类型除了对象类型外,还有基本数据类型,为了可以使它们用同一种方式传输,最简单的方式就是把他们都变成 Variant 类型。而 TPersistent 类是不能转换成 Variant 类型的,而且 TPersistent 提供的持久化方式也与 PHPRPC 所支持的序列化方式不同,因此才没有使用 TPersistent,而是通过定义一个新的类 TPHPObject 作为 PHPRPC 支持的可传输自定义类型的基类。

TPHPObject 有一个无参 Create 方法,因为在反序列化该对象时,是需要调用这个无参构造方法来创建对象的。另外,它还有一个继承自 TComponent 的带有 AOwner 参数的 Create 方法。一般情况下,无需覆盖这两个方法,除非你需要初始化一些数据。

该类中提供的其它方法如果没有特殊需求,一般也都不需要覆盖,只需要定义需要序列化的属性就可以了,例如:

Delphi/Pascal代码
 
  1. TUser = class(TPHPObject)    
  2. private    
  3.   FId: Integer;    
  4.   FName: AnsiString;    
  5.   FPassword: AnsiString;    
  6.   FBirthday: TDateTime;    
  7. published    
  8.   property ID: Integer read FId write FId;    
  9.   property Name: AnsiString read FName write FName;    
  10.   property Password: AnsiString read FPassword write FPassword;    
  11.   property Birthday: TDateTime read FBirthday write FBirthday;  
  12. end;  

这个类中,ID,Name,Password 和 Birthday 这四个属性在传输时就会自动被序列化了,如果某个属性你不希望它被序列化(比如它是一个只读属性),使用 stored 指令将其设置为 False 就可以了。注意,要序列化的属性必须是 published 的,而不能是 public 的。

另外,TPHPObject 有一个 Properties 属性(它不是 published 的,所以无需担心它在传输时会被一起序列化),通过该属性,你可以通过属性名的字符串表示来访问属性值,返回值都是 Variant 类型的。

是否这样定义之后就可以了在 PHPRPC 中传输它了呢?不,还需要做一步小工作,那就是注册它。TPHPObject 提供了一个类方法 RegisterClass 用于注册类自身。例如:

Delphi/Pascal代码
 
  1. TUser.RegisterClass('User');  

就将 TUser 类注册成了别名为 User 的类。

那这个别名是干啥的呢?因为在其它语言中定义类的命名规则可能跟 Delphi 中不同,例如 PHP 或者 .NET 中,是没有在类名前加 T 这个约定的,但是在 Delphi 的类基本上都是以 T 开头的,为了能够跟其它语言互通,所以就提供了这样一个别名机制,注册之后就可以跟其它语言中的具有同样属性(或字段)的 User 类进行交互了。但是在 Delphi 中,你仍然使用的是 TUser 这个类。

另外,向 .NET 有名空间的概念,Java 也有包的概念,比如你定义的 TUser 如果要跟 .NET 或 Java 中的 myNameSpace.mySubNameSpace.User 那么在注册时,注册为:

Delphi/Pascal代码
 
  1. TUser.RegisterClass('myNameSpace_mySubNameSpace_User');  


就可以了,注意这里把 . 变成了 _。

如果类名与其它语言相同是否可以省略这个注册过程呢?不可以的,因为在反序列化时只有注册过的类才能被查找到,否则就不能够被反序列化了(简单的说就是没有经过注册的类可以作为参数传出,但不能作为结果返回)。不过这种同名的注册可以简化一下,直接执行不带参数的 RegisterClass 方法就可以了。1

如何创建 TUser 的对象呢?当然你可以使用 Create 来创建了,但是这样创建的对象是一个普通的对象,而不是一个 Variant 对象。那么怎样才能将它变成一个 Variant 对象呢?TPHPObject 提供了两种方式。一种是使用 New 方法(注意:这不是一个运算符),另一种是使用 ToVariant 方法。实际上 New 方法所做的就是在调用了 Create 方法之后又调用了 ToVariant 方法。因此如果你定义了带参的 Create 构造方法,可以自己同时定义一个带参的 New 方法,这样创建可传递的 Variant 对象就方便多了。

不过有一点大家要注意,Delphi 是没有自动垃圾回收机制的,因此当你创建了对象之后,在你不再需要它时,记得调用 Free 方法将它释放。TPHPObject 的 Variant 类型,在赋值时或传参时是引用传递的,而不是克隆对象,因此在你 Free 之后,它的所有副本也都不可以再用。

那如何将一个 Variant 表示的 TPHPObject 子类对象变成一个普通对象呢?也很简单,TPHPObject 提供了一个 FromVariant 类方法,可以用它来完成这个工作,例如:

Delphi/Pascal代码
 
  1. var   
  2.   user: TUser;   
  3.   vuser: Variant;   
  4. begin   
  5.   vuser := TUser.New;   
  6.   user := TUser(TUser.FromVariant(vuser));   
  7.   user.Free;   
  8. end;  


上面这个例子中,你发现我是在 user 上调用的 Free 方法,实际上在 vuser 上调用 Free 方法也可以得到同样的效果。

那是否可以给这种自定义的类型添加我们自己的方法呢?当然可以。但是你可能会发现,通过 Create 创建出来的对象,可以调用你加的这些方法,而通过 New 创建出来的 Variant 对象却不能像上面的 Free 方法那样调用。如何才能使我们自己定义的方法也可以在 Variant 对象上直接被调用呢?也很简单,TPHPObject 有两个保护方法:DoFunction 和 DoProcedure,只要覆写(override)它们就可以了。下面是 TStringBuffer 中对这两个方法的覆写(override):

Delphi/Pascal代码
 
  1. function TStringBuffer.DoFunction(var Dest: TVarData; const Name: string;  
  2.   const Arguments: TVarDataArray): Boolean;  
  3. var  
  4.   Ident: string;  
  5. begin  
  6.   Ident := LowerCase(Name);  
  7.   Result := True;  
  8.   if Ident = 'readstring' then  
  9.     Variant(Dest) := ReadString(Variant(Arguments[0]))  
  10.   else if Ident = 'seek' then  
  11.     Variant(Dest) := Seek(Variant(Arguments[0]), Variant(Arguments[1]))  
  12.   else  
  13.     Result := inherited DoFunction(Dest, Name, Arguments);  
  14. end;  
  15.   
  16. function TStringBuffer.DoProcedure(const Name: string;  
  17.   const Arguments: TVarDataArray): Boolean;  
  18. var  
  19.   Ident: string;  
  20. begin  
  21.   Ident := LowerCase(Name);  
  22.   Result := True;  
  23.   if Ident = 'insertstring' then  
  24.     InsertString(RawByteString(Variant(Arguments[0])))  
  25.   else if Ident = 'writestring' then  
  26.     WriteString(RawByteString(Variant(Arguments[0])))  
  27.   else  
  28.     Result := inherited DoProcedure(Name, Arguments);  
  29. end;  

大家可以以这个为模板在在自己的类中覆写(override)它们,就可以直接以 Variant 对象的形式来直接调用自定义的方法了。

如果有特殊需求的话,你可能会覆写的方法大约有这几个:__sleep,__wakeup,ToBoolean,ToDate,ToDouble},ToInt64,ToInteger 和 ToString。其中,__sleep 和 __wakeup 与序列化和反序列化有关。ToXXX 的方法是用来进行类型转换的。一般情况下,你自定义的类是不会转换为这些类型的(ToString 也许是个例外)。

__sleep 方法的返回值是一个 TStringDynArray 类型,它用来做一些序列化之前的工作。如果它的返回值为空(nil),则按照默认的方式(就是前面介绍的方式)序列化对象,如果它的返回值不为空,则只序列化那些与返回值中包含的名称相同的属性(当然这些属性必须要出现在 published 声明部分,否则就会出错了)。

__wakeup 方法是在反序列化之后被调用,它没有参数也没有返回值,它一般用于反序列化之后的扫尾工作。

ToBoolean,ToDate,ToDouble,ToInt64 默认是抛出异常,当然它们都是 protected 的方法,如果你不覆写它们,你也不会调用到它们。

ToInteger 也是 protected 方法,它返回的是对象的指针地址的整数表示,不要改写它,它是有特殊用途的。

ToString 默认是返回该对象序列化后的 AnsiString 表示,你当然可以把它覆写成你需要的形式,覆写它不会影响序列化的正常工作。

另外,还有一些虽然也是 virtual 的方法,但是你一定不要去试图覆写它们,除非你知道你在做什么。这些方法包括 DoSerialize,DoUnSerialize,GetProperty,SetProperty,Equal,HashCode 和 ToVariant。

最后,再补充说明一下 ISerializable 接口。如果你的自定义类型继承自 TPHPObject,同时实现了 ISerializable 接口的话,那么这个类型就可以使用你自定义的序列化方式传输了。关于这种自定义序列化方式,可以参见 PHP 手册中关于 Serializable 接口的说明,它们的意义和实现的功能是完全相同的,这里就不再详细叙述了,因为你可能一辈子都不会用到它。

标签: PHPRPC, Delphi

« 上一篇 | 下一篇 »

只显示10条记录相关文章

发表评论

评论 (必须):