• Delphi2009初体验 语言篇 反射单元ObjAuto的加强


    一、提出问题

    在将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;
    复制代码

     

    以下是测试代码:

    复制代码
     1program TestChar;
     2
     3{$APPTYPE CONSOLE}
     4
     5uses
     6  SysUtils,
     7  ObjAuto,
     8  TypInfo,
     9  AutoPtr in '..\..\Djson\common\AutoPtr.pas',
    10  Utils in '..\..\Djson\common\Utils.pas';
    11
    12type
    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
    22var
    23    t: TTestClass;
    24
    25{ TTestClass }
    26
    27function TTestClass.Test1(a: string; b: Single): Single;
    28begin
    29
    30end;
    31
    32procedure TTestClass.Test2(a: string);
    33begin
    34
    35end;
    36
    37function TTestClass.Test3: Integer;
    38begin
    39
    40end;
    41
    42procedure TestIt;
    43var
    44    miArr: TMethodInfoArray;
    45    mi: PMethodInfoHeader;
    46    t: TTestClass;
    47    retInfo: PReturnInfo;
    48    piArr: TParamInfoArray;
    49    pi: PParamInfo;
    50    i: Integer;
    51begin
    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;
    74end;
    75
    76begin
    77    TestIt;
    78    Readln;
    79end.
    复制代码

     

     代码运行结果:

     

  • 相关阅读:
    apio2018题解
    ynoi2018
    hdu2036
    Morley's Theorem
    计算几何
    luogu1355 神秘大三角
    poj2398
    洛谷---小L和小K的NOIP考后放松赛
    LibreOJ β Round #7
    python3
  • 原文地址:https://www.cnblogs.com/ljl_falcon/p/2550479.html
Copyright © 2020-2023  润新知