1. 云栖社区>
  2. PHP教程>
  3. 正文

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

作者:用户 来源:互联网 时间:2017-12-01 11:11:22

delphiphprpc传输类型定义详解

PHPRPC for Delphi 中自定义类型传输详解 - 摘要: 本文讲的是PHPRPC for Delphi 中自定义类型传输详解, 转自 http://coolcode.cn/?action=show&id=317PHPRPC for Delphi 中除了支持基本数据类型、容器类型传递以外,同样也支持自定义类型的传递,而能够进行传递的自定义类型的基类就是 TP


转自 http://coolcode.cn/?action=show&id=317


PHPRPC for Delphi 中除了支持基本数据类型、容器类型传递以外,同样也支持自定义类型的传递,而能够进行传递的自定义类型的基类就是 TPHPObject。这个类是在 PHPRPC 单元中定义的。下面我们就对这个类进行一下深入的剖析。
首先第一个问题,为什么要以 TPHPObject 作为 PHPRPC 传输的自定义类型的基类,而不是 TPersistent 呢。
因为 PHPRPC 支持传输的数据类型除了对象类型外,还有基本数据类型,为了可以使它们用同一种方式传输,最简单的方式就是把他们都变成 Variant 类型。而 TPersistent 类是不能转换成 Variant 类型的,而且 TPersistent 提供的持久化方式也与 PHPRPC 所支持的序列化方式不同,因此才没有使用 TPersistent,而是通过定义一个新的类 TPHPObject 作为 PHPRPC 支持的可传输自定义类型的基类。
TPHPObject 有一个无参 Create 方法,因为在反序列化该对象时,是需要调用这个无参构造方法来创建对象的。另外,它还有一个继承自 TComponent 的带有 AOwner 参数的 Create 方法。一般情况下,无需覆盖这两个方法,除非你需要初始化一些数据。
该类中提供的其它方法如果没有特殊需求,一般也都不需要覆盖,只需要定义需要序列化的属性就可以了,例如:
Delphi/Pascal代码
00.TUser = class(TPHPObject)   
01.private   
02.  FId: Integer;   
03.  FName: AnsiString;   
04.  FPassword: AnsiString;   
05.  FBirthday: TDateTime;   
06.published   
07.  property ID: Integer read FId write FId;   
08.  property Name: AnsiString read FName write FName;   
09.  property Password: AnsiString read FPassword write FPassword;   
10.  property Birthday: TDateTime read FBirthday write FBirthday; 
11.end; 
这个类中,ID,Name,Password 和 Birthday 这四个属性在传输时就会自动被序列化了,如果某个属性你不希望它被序列化(比如它是一个只读属性),使用 stored 指令将其设置为 False 就可以了。注意,要序列化的属性必须是 published 的,而不能是 public 的。
另外,TPHPObject 有一个 Properties 属性(它不是 published 的,所以无需担心它在传输时会被一起序列化),通过该属性,你可以通过属性名的字符串表示来访问属性值,返回值都是 Variant 类型的。
是否这样定义之后就可以了在 PHPRPC 中传输它了呢。不,还需要做一步小工作,那就是注册它。TPHPObject 提供了一个类方法 RegisterClass 用于注册类自身。例如:
Delphi/Pascal代码
00.TUser.RegisterClass('User'); 
就将 TUser 类注册成了别名为 User 的类。
那这个别名是干啥的呢。因为在其它语言中定义类的命名规则可能跟 Delphi 中不同,例如 PHP 或者 .NET 中,是没有在类名前加 T 这个约定的,但是在 Delphi 的类基本上都是以 T 开头的,为了能够跟其它语言互通,所以就提供了这样一个别名机制,注册之后就可以跟其它语言中的具有同样属性(或字段)的 User 类进行交互了。但是在 Delphi 中,你仍然使用的是 TUser 这个类。
另外,向 .NET 有名空间的概念,Java 也有包的概念,比如你定义的 TUser 如果要跟 .NET 或 Java 中的 myNameSpace.mySubNameSpace.User 那么在注册时,注册为:
Delphi/Pascal代码
00.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代码
00.var  
01.  user: TUser;  
02.  vuser: Variant;  
03.begin  
04.  vuser := TUser.New;  
05.  user := TUser(TUser.FromVariant(vuser));  
06.  user.Free;  
07.end; 
上面这个例子中,你发现我是在 user 上调用的 Free 方法,实际上在 vuser 上调用 Free 方法也可以得到同样的效果。
那是否可以给这种自定义的类型添加我们自己的方法呢。当然可以。但是你可能会发现,通过 Create 创建出来的对象,可以调用你加的这些方法,而通过 New 创建出来的 Variant 对象却不能像上面的 Free 方法那样调用。如何才能使我们自己定义的方法也可以在 Variant 对象上直接被调用呢。也很简单,TPHPObject 有两个保护方法:DoFunction 和 DoProcedure,只要覆写(override)它们就可以了。下面是 TStringBuffer 中对这两个方法的覆写(override):
Delphi/Pascal代码
00.function TStringBuffer.DoFunction(var Dest: TVarData; const Name: string; 
01.  const Arguments: TVarDataArray): Boolean; 
02.var 
03.  Ident: string; 
04.begin 
05.  Ident := LowerCase(Name); 
06.  Result := True; 
07.  if Ident = 'readstring' then 
08.    Variant(Dest) := ReadString(Variant(Arguments[0])) 
09.  else if Ident = 'seek' then 
10.    Variant(Dest) := Seek(Variant(Arguments[0]), Variant(Arguments[1])) 
11.  else 
12.    Result := inherited DoFunction(Dest, Name, Arguments); 
13.end; 
14. 
15.function TStringBuffer.DoProcedure(const Name: string; 
16.  const Arguments: TVarDataArray): Boolean; 
17.var 
18.  Ident: string; 
19.begin 
20.  Ident := LowerCase(Name); 
21.  Result := True; 
22.  if Ident = 'insertstring' then 
23.    InsertString(RawByteString(Variant(Arguments[0]))) 
24.  else if Ident = 'writestring' then 
25.    WriteString(RawByteString(Variant(Arguments[0]))) 
26.  else 
27.    Result := inherited DoProcedure(Name, Arguments); 
28.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 接口的说明,它们的意义和实现的功能是完全相同的,这里就不再详细叙述了,因为你可能一辈子都不会用到它。

以上是云栖社区小编为您精心准备的的内容,在云栖社区的博客、问答、公众号、人物、课程等栏目也有的相关内容,欢迎继续使用右上角搜索按钮进行搜索delphi , phprpc , 传输 , 类型 , 定义 详解 ,以便于您获取更多的相关知识。