[b][color=red]转自 http://coolcode.cn/?action=show&id=317[/color][/b]
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 接口的说明,它们的意义和实现的功能是完全相同的,这里就不再详细叙述了,因为你可能一辈子都不会用到它。
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 接口的说明,它们的意义和实现的功能是完全相同的,这里就不再详细叙述了,因为你可能一辈子都不会用到它。