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
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>()
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With