delphi – 带有RTTI TRttiMethod.Invoke,stdcall和const参数的Bug

我有RTTI TRttiMethod.Invoke,stdcall和const参数的问题:
obj := TClassRecordTest.Create;
    try
      b.a := 10; b.b := 100;

      a.a := 1;  a.b := 2;
      writeln('b.a='+IntToStr(b.a)+' b.b='+IntToStr(b.b));
      writeln;
      writeln('call test1');
      writeln('a.a='+IntToStr(a.a)+' a.b='+IntToStr(a.b));
      r := VToRec(RTTICall(obj,'Test1',@a,@b));
      writeln('test1 r.a='+IntToStr(r.a)+' r.b='+IntToStr(r.b));

      a.a := 2;  a.b := 3;
      writeln('call test2');
      writeln('a.a='+IntToStr(a.a)+' a.b='+IntToStr(a.b));
      r := VToRec(RTTICall(obj,'Test2',@b));
      writeln('test3 r.a='+IntToStr(r.a)+' r.b='+IntToStr(r.b));

      a.a := 3;  a.b := 4;
      writeln('call test3');
      writeln('a.a='+IntToStr(a.a)+' a.b='+IntToStr(a.b));
      r := VToRec(RTTICall(obj,'Test3',@b));
      writeln('test3 r.a='+IntToStr(r.a)+' r.b='+IntToStr(r.b));

      a.a := 4;  a.b := 5;
      writeln('call test4');
      writeln('a.a='+IntToStr(a.a)+' a.b='+IntToStr(a.b));
      r := VToRec(RTTICall(obj,'Test4',@b));
      writeln('test4 r.a='+IntToStr(r.a)+' r.b='+IntToStr(r.b));

    finally
      obj.Destroy;
    end;

RTTICall它是:

function RTTICall(aObj: TObject; MethodName: string; a,b: pointer): TValue;
var
  RttiContext: TRttiContext;
  ClassType: TRttiType;
  Methods: TMethodList;
  Method: TRttiMethod;
  Params: TParamList;
  Args: TArgList;
begin
  RttiContext := TRttiContext.Create;
  try
    ClassType := FindFirstClassTypeByName(RttiContext,aObj.ClassName);
    if ClassType <> nil then
    begin
      Methods := ClassType.GetDeclaredMethods;
      for Method in Methods
      do begin
        if SameText(Method.Name,MethodName) then
        begin
          Params := Method.GetParameters;
          SetLength(Args,Length(Params));
          TValue.Make(nil,Params[0].ParamType.Handle,Args[0]);
          move(a^,Args[0].GetReferenceToRawData^,Params[0].ParamType.TypeSize);
          TValue.Make(nil,Params[1].ParamType.Handle,Args[1]);
          move(b^,Args[1].GetReferenceToRawData^,Params[1].ParamType.TypeSize);

          Result := Method.Invoke(TObject(aObj),Args);
          exit;
        end;
      end;
    end;
  finally
//    FreeAndNil(aObj);
  end;
end;

和功能TestN:

function TClassRecordTest.Test1(a,b: TRecordTest): TRecordTest;
begin
  result.a := a.a+b.a;
  result.b := a.b+b.b;
end;

function TClassRecordTest.Test2(var a,b: TRecordTest): TRecordTest;
begin
  result.a := a.a+b.a;
  result.b := a.b+b.b;
end;

function TClassRecordTest.Test3(const a,b: TRecordTest): TRecordTest;
begin
  result.a := a.a+b.a;
  result.b := a.b+b.b;
end;

function TClassRecordTest.Test4(const  a,b: TRecordTest): TRecordTest;
begin
  result.a := a.a+b.a;
  result.b := a.b+b.b;
end;

结果是:

>Project7.exe
b.a=10 b.b=100

call test1
a.a=1 a.b=2
test1 r.a=11 r.b=102
call test2
a.a=2 a.b=3
test3 r.a=12 r.b=103
call test3
a.a=3 a.b=4
test3 r.a=13 r.b=104
call test4
a.a=4 a.b=5
EAccessViolation: Access violation at address 0047A65A in module 'Project7.exe'. Read of address 00000004

仅当用作参数const和stdcall时才会发生此错误.

如果我改变Test3和Test4:

function TClassRecordTest.Test3(const a,b: TRecordTest): TRecordTest;
begin
  writeLn('@a='+IntToStr(integer(@a))+' @b='+IntToStr(integer(@a)));
  result.a := a.a+b.a;
  result.b := a.b+b.b;
end;

function TClassRecordTest.Test4(const  a,b: TRecordTest): TRecordTest;
begin
  writeLn('@a='+IntToStr(integer(@a))+' @b='+IntToStr(integer(@a)));
  result.a := a.a+b.a;
  result.b := a.b+b.b;
end;

结果是:

>Project7.exe
b.a=10 b.b=100

call test1
a.a=1 a.b=2
test1 r.a=11 r.b=102
call test2
a.a=2 a.b=3
test3 r.a=12 r.b=103
call test3
a.a=3 a.b=4
@a=31301448 @b=31301448
test3 r.a=13 r.b=104
call test4
a.a=4 a.b=5
@a=4 @b=4
EAccessViolation: Access violation at address 0047A76C in module 'Project7.exe'. Read of address 00000004

事实证明,TRttiMethod.Invoke const按值传递,尽管有必要传递地址

解决方法

你和我一样遇到了同样的问题.让我引用巴里所说的话:

This is by design; the Rtti.Invoke function is at too low a level on the stack,and has no access to any typeinfo that could tell it whether to pass arguments by reference or by value. It expects all parameters to be converted to the correct type,including any by-ref parameters being converted to pointers as needed. All it does is stuff the values into registers and/or stack as required,invoke,and retrieve the return value (if any) from the appropriate location.

所以为了传递const,out和var参数,你需要使用TValue.From< Pointer>()

相关文章

 从网上看到《Delphi API HOOK完全说明》这篇文章,基本上都...
  从网上看到《Delphi API HOOK完全说明》这篇文章,基本上...
ffmpeg 是一套强大的开源的多媒体库 一般都是用 c/c+&#x...
32位CPU所含有的寄存器有:4个数据寄存器(EAX、EBX、ECX和ED...
1 mov dst, src dst是目的操作数,src是源操作数,指令实现的...