Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bug with RTTI TRttiMethod.Invoke, stdcall and const parameters

I have a problem with RTTI TRttiMethod.Invoke, stdcall and const parameters:

    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', @a, @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', @a, @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', @a, @b));
      writeln('test4 r.a='+IntToStr(r.a)+' r.b='+IntToStr(r.b));

    finally
      obj.Destroy;
    end;

RTTICall it is:

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;

and functions 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;

The result of this is:

>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                                                                                                                                                                                                                                                                                    

This error occurs only when used as parameters const and stdcall.

If i change Test3 and 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;

The result is:

>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

It turns out that TRttiMethod.Invoke const passes by value, although it was necessary to pass the address

like image 358
Mielofon Avatar asked Jun 21 '11 07:06

Mielofon


1 Answers

You ran into the same problem like me. Let me quote what Barry said:

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.

So in order to pass const, out and var arguments you need to use TValue.From<Pointer>()

like image 193
Stefan Glienke Avatar answered Sep 22 '22 01:09

Stefan Glienke