(原创)Delphi2009初体验 - 语言篇 - 反射单元ObjAuto的加强

Delphi反射机制解析
本文详细介绍了如何在Delphi中通过反射机制获取方法参数信息。作者通过对Delphi对象内存布局和VMT表的研究,实现了自定义函数GetParams和GetReturnInfo,用以获取指定方法的参数信息和返回值信息。

一、提出问题

在将json-rpc中JSONObject翻译成Delphi代码的时候,我碰到以下语句:

 1  Method[] methods  =  klass.getMethods();
 2 
 3  Method method  =  methods[i];
 4 
 5  if  (key.length()  >   0   &&
 6      Character.isUpperCase(key.charAt( 0 ))  &&
 7      method.getParameterTypes().length  ==   0 ) {
 8 
 9  }
10 

很明显,这里是通过反射得到类中包含的函数的信息及函数所包含的参数信息。当我在Delphi2009中兴奋的引入ObjAuto文件时,我沮丧的发现,ObjAuto中只提供了GetMethods方法,没有提供类似于Java中getParameterTypes方法的GetParams方法。没关系,Delphi的SDK不提供,我们就根据VMT表,自己写一个GetParams函数出来!

二、分析问题

我们知道,在Delphi中对象是在堆中存放的。而对象在堆中存放的前四个字节组成一个地址,这个地址指向的是此对象所对应的VMT所在堆中的地址。VMT可以理解为Delphi对象所对应的类在堆中存放的组成形式的描述,它是类的结构,不包含对象的数据。有关VMT的更多信息,请百度一下、Google一下,或查看以下两篇文章:

1、    Delphi中类的运行期TypeInfo信息结构说明
2、    DELPHI的原子世界

类中的函数及函数的参数信息在VMT中也有存放,我们只要知道这些信息是如何存放的,所有事情都变得简单了。下面我画出在VMT中表示函数信息的那一块结构:

 从上图我们可以看到,在VMT中每个函数结构都包含了一个TMethodInfoHeader头,一个TReturnInfo返回值结构,若干个TParamInfo参数结构。参数的个数我们是没有办法直接获取的,但是我们可以通过指针往下遍历,直到指针的值大于TMethodInfoHeader.Len为止,累加参数的个数。

*1:为什么是SizeOf(TMethodInfoHeader) – 255 + Length(mi1.Name)字节呢?

首先我们来看TMethodInfoHeader结构体:

1    TMethodInfoHeader  =   record
2      Len: Word;
3      Addr: Pointer;
4      Name: ShortString;
5     end ;

我们来分析一下,结构体TMethodInfoHeader所占的字节(SizeOf(TMethodInfoHeader))为SizeOf(Word) + SizeOf(Pointer) + SizeOf(ShortString) = 2 + 4 + 256 = 262。如果Name字段只占了3个字节,SizeOf(TMethodInfoHeader)仍然是262,不受Name字段长度的影响,但是下一个数据是紧挨着Name的3个字节存的,中间不会留空格。
所以,我们必须使用SizeOf(TMethodInfoHeader) – 256 + Length(Name)。另外,由于字符串第0个字节保存的是字符串的长度,我们-256把保存字符串长度的那一位也减掉了,所以得+1:
SizeOf(TMethodInfoHeader) – 256 + Length(Name) + 1 = SizeOf(TMethodInfoHeader) – 255 + Length(Name)

*2:mi1: TMethodInfoHeader的信息我们可以通过ObjAuto.GetMethodInfo方法获取,我们只要关注如何得到参数信息就可以了。

三、解决问题

通过以上问题的分析,我们可以很容易的写出两个函数

1、GetParams:获取方法所包含的参数信息集合

2、GetReturnInfo:获取方法的返回参数信息

 代码如下:

uses
    SysUtils,
    StrUtils,
    TypInfo,
    ObjAuto;

type
    TParamInfoArray 
=   array   of  PParamInfo;

function  GetParams(aObj: TObject; aMethodName:  string ): TParamInfoArray;
function  GetReturnInfo(aObj: TObject; aMethodName:  string ): PReturnInfo;

implementation

const
    SHORT_LEN 
=  SizeOf(ShortString)  -   1 ;

function  GetReturnInfo(aObj: TObject; aMethodName:  string ): PReturnInfo;
var
    mi: PMethodInfoHeader;
begin
    
//  获取函数头指针并判断是否合法
    mi :
=  ObjAuto.GetMethodInfo(aObj, ShortString(aMethodName));
    
if  mi.Len  <=  SizeOf(TMethodInfoHeader)  +  Length(mi.Name)  -  SHORT_LEN  then
        Exit(
nil );

    Result :
=  PReturnInfo(Integer(mi)  +  SizeOf(TMethodInfoHeader)  +
        Length(mi.Name) 
-  SHORT_LEN);
end ;

function  GetParams(aObj: TObject; aMethodName:  string ): TParamInfoArray;
var
    mi: PMethodInfoHeader;
    miEnd: Pointer;
    param: PParamInfo;
    count: Integer;
begin
    
//  初始化返回值
    SetLength(Result, 
0 );

    
//  获取函数头指针并判断是否合法
    mi :
=  ObjAuto.GetMethodInfo(aObj, ShortString(aMethodName));
    
if  mi.Len  <=  SizeOf(TMethodInfoHeader)  +  Length(mi.Name)  -  SHORT_LEN  then
        Exit;

    
//  获取函数尾地址用于遍历
    miEnd :
=  Pointer(Integer(mi)  +  mi.Len);

    
//  第一个参数的地址根据以下算法得来
    param :
=  PParamInfo(Integer(mi)  +  SizeOf(TMethodInfoHeader)  +
        Length(mi.Name) 
-  SHORT_LEN  +  SizeOf(TReturnInfo));
    count :
=   0 ;

    
//  判断遍历是否超过了函数尾地址
    
while  Integer(param)  <  Integer(miEnd)  do
    
begin
        Inc(count);
        SetLength(Result, count);
        Result[count 
-   1 ] : =  param;

        
//  往后的参数地址算法由来
        param :
=  PParamInfo(Integer(param)  +  SizeOf(TParamInfo)  +
             Length(param.Name) 
-  SHORT_LEN);
    
end ;
end ;

 

以下是测试代码:

 1 program  TestChar;
 2
 3 { $APPTYPE CONSOLE }
 4
 5 uses
 6   SysUtils,
 7   ObjAuto,
 8   TypInfo,
 9   AutoPtr  in   ' ..\..\Djson\common\AutoPtr.pas ' ,
10   Utils  in   ' ..\..\Djson\common\Utils.pas ' ;
11
12 type
13 { $METHODINFO ON }
14     TTestClass  =   class
15      public
16          function  Test3: Integer;
17          procedure  Test2(a:  string );
18          function  Test1(a:  string ; b: Single): Single;
19      end ;
20 { $METHODINFO OFF }
21
22 var
23     t: TTestClass;
24
25 {  TTestClass  }
26
27 function  TTestClass.Test1(a:  string ; b: Single): Single;
28 begin
29
30 end ;
31
32 procedure  TTestClass.Test2(a:  string );
33 begin
34
35 end ;
36
37 function  TTestClass.Test3: Integer;
38 begin
39
40 end ;
41
42 procedure  TestIt;
43 var
44     miArr: TMethodInfoArray;
45     mi: PMethodInfoHeader;
46     t: TTestClass;
47     retInfo: PReturnInfo;
48     piArr: TParamInfoArray;
49     pi: PParamInfo;
50     i: Integer;
51 begin
52     t : =  TTestClass.Create;
53
54     miArr : =  GetMethods(TTestClass);
55      for  mi  in  miArr  do
56      begin
57         Writeln( ' Method:  '   +  mi.Name);
58
59         retInfo : =  GetReturnInfo(t, mi.Name);
60          if  retInfo.ReturnType  <>   nil   then
61          begin
62             Writeln( ' ReturnType:  '   +  retInfo.ReturnType^.Name);
63          end ;
64
65         piArr : =  GetParams(t, mi.Name);
66          if  piArr  <>   nil   then
67          begin
68              for  pi  in  piArr  do
69                 Writeln( ' Param Name:  '   +  pi.Name  +   '  Param Type:  '   +  pi.ParamType^.Name);
70          end ;
71      end ;
72
73     t.Free;
74 end ;
75
76 begin
77     TestIt;
78     Readln;
79 end .

 

 代码运行结果:

 

 

转载于:https://www.cnblogs.com/felixYeou/archive/2008/09/11/1288785.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值