浏览模式: 标准 | 列表

本文是针对 PHPRPC for Delphi 原生程序开发版本的介绍,如果您使用的是 .NET 版本可以忽略本文。

PHPRPC for Delphi 是以组件形式提供的,目前只提供了客户端组件,它是非可视化的组件,所以不论是在窗口程序还是控制台程序中你都可以使用它。

直接在 Delphi 中安装该组件包后就可以使用了,不过目前只提供了 Delphi 7 和 Delphi 2009 的组件安装包。如果你是用其它版本的 Delphi 可以通过自己创建组件包来安装,或者将现有版本的组件包转换为你所使用版本的组件包。

安装组件包非常简单。如果你是用的是 Delphi 7,打开 PHPRPC_D7.dpk 编译安装即可。如果你使用的是 Delphi 2009,打开 PHPRPC_D2009.dproj 编译安装即可。按照之后就可以直接从 Internet 面板上找到 PHPRPC_Client 组件了。

为了方便讲解,这里给出的演示是控制台程序:

Delphi/Pascal代码
  1. program Example;    
  2.     
  3. {$APPTYPE CONSOLE}    
  4.     
  5. uses    
  6.   PHPRPC;    
  7.     
  8. var    
  9.   client: TPHPRPC_Client;    
  10.   clientProxy: Variant;    
  11.   intArray: array of Integer;    
  12.   arraylist: TArrayList;    
  13.   vhashmap: Variant;    
  14.   ohashmap: THashMap;    
  15.   i: Integer;    
  16. begin    
  17.   // 创建客户端对象,如果是图形界面程序,直接拖放组件到 Form 即可。  
  18.   client := TPHPRPC_Client.Create;    
  19.   // 返回远程代理对象,通过它就可以直接调用远程方法了。  
  20.   // 因为实际上该代理对象是 client 对象的 Variant 包装,所以该代理对象不需要单独释放。    
  21.   clientProxy := client.useService('http://localhost/index.aspx');  
  22.   
  23.   // 设置交换密钥的长度(可选,默认 128)  
  24.   client.KeyLength := 256;  
  25.   // 设置加密模式(0 - 不加密、1 - 单向加密、2 - 双向加密、3 - 双向且输出加密),默认为 0  
  26.   client.EncryptMode := 1;  
  27.     
  28.   // 数字参数    
  29.   writeln(clientProxy.add(12));    
  30.     
  31.   // 英文字符串参数    
  32.   writeln(clientProxy.add('foo''bar'));    
  33.     
  34.   // Delphi 7 中文字符串参数要转换为UTF8编码    
  35.   writeln(UTF8ToAnsi(clientProxy.Hello(AnsiToUTF8('马秉尧'))));    
  36.     
  37.   // Delphi 2009 中文字符串参数默认为UnicodeString    
  38.   // writeln(UTF8ToAnsi(clientProxy.Hello('马秉尧')));    
  39.     
  40.   // 中文的服务器输出的也是UTF8编码,本地打印要转换为本地编码    
  41.   writeln(UTF8ToAnsi(client.Output));    
  42.     
  43.   // 传递动态数组参数    
  44.   setLength(intArray, 10);    
  45.   // 主要不要越界    
  46.   for i := 0 to 9 do intArray[i] := Random(100000);    
  47.   // 返回值是 Variant 包装的 THashMap    
  48.   vhashmap := clientProxy.sort(Variant(intArray));    
  49.   // 这里不能用索引方式访问    
  50.   for i := 0 to 9 do writeln(vhashmap.get(i));    
  51.   writeln;    
  52.   // 使用完之后释放掉, 以免造成内存泄漏    
  53.   // 但动态数组不需要显式释放,它会在程序运行结束后自动释放    
  54.   vhashmap.Free;    
  55.     
  56.   // 传递数组列表参数    
  57.   // 注意这里初始化了10个元素的空间,但是实际用了11个元素    
  58.   // 因为数组列表有自增长的特性,因此不会发生越界访问的情况    
  59.   // 但上面的动态数组则不可以越界访问,否则会发生意想不到的错误    
  60.   arraylist := TArrayList.Create(10);    
  61.   for i := 0 to 10 do arraylist[i] := Random(100000);    
  62.     
  63.   vhashmap := clientProxy.sort(arraylist.ToVariant);    
  64.   // 这里不能用索引方式访问    
  65.   for i := 0 to 10 do writeln(vhashmap.get(i));    
  66.   writeln;    
  67.     
  68.   // 可以显式通过 FromVariant 方法转换成 THashMap 对象    
  69.   ohashmap := THashMap(THashMap.FromVariant(vhashmap));    
  70.   // 这里不能用get方法访问    
  71.   for i := 0 to 10 do writeln(ohashmap[i]);    
  72.   writeln;    
  73.     
  74.   // 释放掉原来的未排序数组    
  75.   arraylist.Free;    
  76.     
  77.   // 将返回结果转换为 TArrayList 类型    
  78.   arraylist := ohashmap.ToArrayList;    
  79.   for i := 0 to 10 do writeln(arraylist[i]);    
  80.   writeln;    
  81.     
  82.   vhashmap.Free;    
  83.   // vhashmap 释放后 ohashmap 也一起释放,反之亦然    
  84.   // 因此下面注释掉的语句如果执行的话会出错(或让程序崩溃)    
  85.   // for i := 0 to 10 do writeln(ohashmap[i]);    
  86.     
  87.   // 但通过 ToArrayList 转换得到的是新对象,所以仍然可以使用    
  88.   for i := 0 to 10 do writeln(arraylist[i]);    
  89.     
  90.   // 因此不要忘记释放资源,以免造成内存泄漏    
  91.   arraylist.Free;    
  92.   client.Free;    
  93.   readln;  
  94. end.    

因为 Delphi 2009 中引入了 UnicodeString,所以不需要 AnsiToUTF8 就可以正确按照 UTF-8 编码来传输 string 了。上面的程序中关于中文字符串部分已经给出了在不同版本下的解决方案,但是 Delphi 7 的写法在 Delphi 2009 上也可以继续使用。不过目前的 PHP 4/5 还不支持 Delphi 中传输的 WideString 字符串,但将来的 PHP 6 会支持。目前其它所有语言的 PHPRPC 服务器都已经支持 Delphi 中传输的 WideString 字符串。

其它需要注意的问题都已经在注释中写清楚了,这里就不再重复了。

» 阅读全文

这个版本的更新是对反序列化效率的优化,优化后的效率提升有 100 倍之多。

在优化过程中发现最耗时的操作居然是 try/catch 操作。而且差别不是一点点,使用 try/catch 所耗费的时间是不是用 try/catch 所耗费时间的 100 倍还多,以至于我在效率对比测试时发现,PHP 反序列化居然比 SOAP 还慢,经过优化后,将不必要的 try/catch 替换为 if/else 后,反序列化的效率远远的超过了 SOAP,甚至接近或超过 .NET 二进制序列化的速度。

新版本下载地址不变,仍然是:http://www.phprpc.org/download/

» 阅读全文

尽管 Delphi 的 VCL/CLX 中提供了一些容器类型(比如 TList,TObjectList,TStringList 等),但是这些容器要么是针对指针的(无所不包),要么是针对对象的(基本类型就无法存了),甚至是针对某种特殊类型的(这样就限制就更大了)。所以这些 容器类型与其它语言中的列表或字典类容器进行直接交换就存在一些问题了,不是包括的范围太大就是范围太小。

所以,在 PHPRPC for Delphi 中,专门提供了一组用于跟其它语言交换数据的又方便操作的容器类型。下面就对这些类型做一下详细的介绍。

首先第一个要介绍的是 Delphi 的动态数组类型,这个不是 PHPRPC for Delphi 中提供的,而是 Delphi 自己提供的。比如 TIntegerDynArray,TWordDynArray,TDoubleDynArray 等,这些是在 Delphi 的 Types 单元中定义的,如果需要直接引用这个单元就可以使用这些类型。

不过有两点要注意,第一是 TStringDynArray(array of string),它在通过 PHPRPC 传输的时候可能并不像你希望的那样工作,因为 Delphi 本身会将其中的字符串转换成 OLEStr 来进行传递,如果你的 String 是二进制字符串,而不是本地编码的文本,最后的结果可能就不是你期望的了。所以在跟其它语言进行数据交互时,不推荐使用动态字符串数组类型。

第二是 TByteDynArray(array of Byte),为了优化该类型传输,该类型会以 AnsiString(RawByteString)方式序列化传输。在被反序列化时,也会被作为 AnsiString 类型被反序列化。如果希望结果被直接反序列化为 TByteDynArray 类型,则可以将 TPHPRPC_Client 对象的 StringAsByteArray 属性设置为 True 即可。但对于自定义类型的属性,不论是 AnsiString 还是 TByteDynArray 类型,都无需设置 StringAsByteArray,PHPRPC 会自动按照正确的类型反序列化。

PHPRPC for Delphi 除了支持动态数组外,还提供了更加高级的容器类型,它们是 TArrayList,THashedArrayList 和 THashMap。另外,还有一个字符串操作的帮助类 TStringBuffer。

TArrayList 类似于动态数组 TVariantDynArray,可以保存的元素也是 Variant 类型,但是 TArrayList 提供了一些方法允许你方便的添加、删除、查找、修改、移动元素,并且容器大小是可自动增长的。

THashedArrayList 是 TArrayList 的一个子类,它跟 TArrayList 实现的操作是一样的,不过它在存取下标不连续的数组时,效率更高。IndexOf 操作效率也更高,THashedArrayList 效率为 O(1),而 TArrayList 为 O(n)。

THashMap 可以让你通过一个 Variant 变量来索引另一个 Variant 变量,不过通常用来做索引的是整数或者字符串。如果要通过 PHPRPC 传递的话,那么索引必须是整数或字符串类型,其他类型的索引会被忽略。

通过 PHPRPC 调用其它语言发布的服务时,如果其它语言返回的类型是数组、列表或者字典等类型的数据,在 Delphi 中接收到之后都会作为 THashMap 返回,如果接收到的是一个对象类型,当这个对象类型在 Delphi 中没有对应的定义时,也将作为 THashMap 返回。如果你希望以 TArrayList 来操作返回值,可以用 THashMap 的 ToArrayList 方法将结果转换为 TArrayList 类型,这个 ToArrayList 返回的是一个独立的 TArrayList 对象,使用后注意要用 Free 方法将它释放,否则会产生内存泄漏。

TStringBuffer 就不用详细介绍了,它没有什么特别的,就是一个用来可以方便修改二进制字符串的帮助类,通过它的 ToString 方法可以得到最后的字符串。

这些类型都是 TPHPObject 的子类,都可以转化成 Variant 类型。了解了这些容器类型以后,用 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 接口的说明,它们的意义和实现的功能是完全相同的,这里就不再详细叙述了,因为你可能一辈子都不会用到它。

» 阅读全文

SilverLight 2.0、异步调用和泛型支持是 PHPRPC 3.0.1 for .NET 中增加的新特征,下面我们通过一个小程序来演示一下这三个特征。首先 SilverLight 2.0 程序的建立我就不详细说明了,我用的是 Microsoft Expression Blend 2 + Microsoft Visual Web Developer 2008 Express Edition +Microsoft Silverlight 2 SDK。这个演示程序很简单,首先建立一个 SilverLight 2.0 程序,然后拖一个文本块和一个按钮到界面上,然后进入代码编辑区,下面是程序的主要代码:

 

C#代码
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Net;  
  5. using System.Windows;  
  6. using System.Windows.Controls;  
  7. using System.Windows.Documents;  
  8. using System.Windows.Input;  
  9. using System.Windows.Media;  
  10. using System.Windows.Media.Animation;  
  11. using System.Windows.Shapes;  
  12. using org.phprpc;  
  13. using org.phprpc.util;  
  14.   
  15. namespace SilverlightPHPRPCExample  
  16. {  
  17.     public interface ITest  
  18.     {  
  19.         void hi(string name, PHPRPC_Callback callback);  
  20.         void hi(string name, PHPRPC_Callback<String> callback);  
  21.         void array_sort(List<int> list, PHPRPC_Callback<List<int>> callback);  
  22.     }  
  23.     public partial class Page : UserControl  
  24.     {  
  25.         PHPRPC_Client client;  
  26.         ITest it;  
  27.         public Page()  
  28.         {  
  29.             InitializeComponent();  
  30.             client = new PHPRPC_Client();  
  31.             it = (ITest)client.UseService("http://localhost/server.php"typeof(ITest));  
  32.         }  
  33.         private void callback1(Object result, Object[] args, String output, PHPRPC_Error warning) {  
  34.             textBlock.Text += "\r\n" + PHPConvert.ToString(result);  
  35.         }  
  36.         private void callback2(String result, Object[] args, String output, PHPRPC_Error warning) {  
  37.             textBlock.Text += "\r\n" + result + " 泛型";  
  38.         }  
  39.         private void callback3(List<int> result, Object[] args, String output, PHPRPC_Error warning) {  
  40.             foreach (int i in result) {  
  41.                 textBlock.Text += "\r\n" + i.ToString();  
  42.             }  
  43.         }  
  44.         private void Button_Click(object sender, System.Windows.RoutedEventArgs e)  
  45.         {  
  46.             client.KeyLength = 256;  
  47.             client.EncryptMode = 1;  
  48.             it.hi("Ma Bingyao"new PHPRPC_Callback(callback1));  
  49.             it.hi("马秉尧", callback2);  
  50.             List<int> list = new List<int>(10);  
  51.             Random r = new Random();  
  52.             for (int i = 0; i < 10; i++) {  
  53.                 list.Add(r.Next());  
  54.             }  
  55.             it.array_sort(list, callback3);  
  56.         }  
  57.     }  
  58. }  

这里面 hi,array_soft 都是用 PHP 发布的 PHPRPC 方法,因为很简单,大家不看也都能看懂这个程序的意思,所以 PHP 部分的具体代码我就不写了。

首先来说一下异步调用,异步调用的接口方法最后有一个 PHPRPC_Callback 类型的参数,而且这个 PHPRPC_Callback 类型还可以是泛型化的。PHPRPC_Callback 实际上是一个委托类型。该委托类型中,第一个参数是表示返回结果,第二个参数表示传递的参数,第三个参数表示服务器端重定向输出的字符串,第四个参数表示 服务器端产生的警告错误。如果使用的是非泛型化的 PHPRPC_Callback 委托,则返回结果以 Object 类型返回,如果要转换为你需要的类型,需要自己调用 PHPConvert 类中的类型转换方法,如果发生调用发生错误,则返回结果就是 PHPRPC_Error 类型的一个错误对象。如果使用泛型化的 PHPRPC_Callback 委托,则返回结果可以直接转换为第一个参数所指定的类型,不需要自己使用 PHPConvert 来进行转换了。但如果调用发生错误,则会抛出 PHPRPC_Error 类型的异常,而不是作为第一个参数传递给回调方法。

不论是调用的参数,还是返回结果都可以是泛型容器类型,当然现在支持的泛型容器类型只有 List<T> 和 Dictionary<K,V>,不过对于大多数应用来说已经足够了。

最后要说明的是,在 SilverLight 2.0 中只能使用异步调用,不能使用同步调用。但是在 ASP.NET 或者WinForm 程序中(.NET 2.0 及其以上版本),既可以使用异步调用,也可以使用同步调用,而且即使同时使用也不会有冲突。当然,在 WinForm 程序中推荐使用异步调用,这样可以避免远程调用造成界面卡死的现象,而在 ASP.NET 中则推荐使用同步调用,这样可以保证调用在页面执行完之前结束。

» 阅读全文