I have a problem with the RTTI parameters TRttiMethod.Invoke, stdcall and const:
obj := TClassRecordTest.Create; try ba := 10; bb := 100; aa := 1; ab := 2; writeln('ba='+IntToStr(ba)+' bb='+IntToStr(bb)); writeln; writeln('call test1'); writeln('aa='+IntToStr(aa)+' ab='+IntToStr(ab)); r := VToRec(RTTICall(obj, 'Test1', @a, @b)); writeln('test1 ra='+IntToStr(ra)+' rb='+IntToStr(rb)); aa := 2; ab := 3; writeln('call test2'); writeln('aa='+IntToStr(aa)+' ab='+IntToStr(ab)); r := VToRec(RTTICall(obj, 'Test2', @a, @b)); writeln('test3 ra='+IntToStr(ra)+' rb='+IntToStr(rb)); aa := 3; ab := 4; writeln('call test3'); writeln('aa='+IntToStr(aa)+' ab='+IntToStr(ab)); r := VToRec(RTTICall(obj, 'Test3', @a, @b)); writeln('test3 ra='+IntToStr(ra)+' rb='+IntToStr(rb)); aa := 4; ab := 5; writeln('call test4'); writeln('aa='+IntToStr(aa)+' ab='+IntToStr(ab)); r := VToRec(RTTICall(obj, 'Test4', @a, @b)); writeln('test4 ra='+IntToStr(ra)+' rb='+IntToStr(rb)); 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;
and TestN functions:
function TClassRecordTest.Test1(a, b: TRecordTest): TRecordTest; begin result.a := a.a+ba; result.b := a.b+bb; end; function TClassRecordTest.Test2(var a, b: TRecordTest): TRecordTest; begin result.a := a.a+ba; result.b := a.b+bb; end; function TClassRecordTest.Test3(const a, b: TRecordTest): TRecordTest; begin result.a := a.a+ba; result.b := a.b+bb; end; function TClassRecordTest.Test4(const a, b: TRecordTest): TRecordTest; begin result.a := a.a+ba; result.b := a.b+bb; end;
Result:
>Project7.exe ba=10 bb=100 call test1 aa=1 ab=2 test1 ra=11 rb=102 call test2 aa=2 ab=3 test3 ra=12 rb=103 call test3 aa=3 ab=4 test3 ra=13 rb=104 call test4 aa=4 ab=5 EAccessViolation: Access violation at address 0047A65A in module 'Project7.exe'. Read of address 00000004
This error occurs only when using const and stdcall as parameters.
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+ba; result.b := a.b+bb; end; function TClassRecordTest.Test4(const a, b: TRecordTest): TRecordTest; begin writeLn('@a='+IntToStr(integer(@a))+' @b='+IntToStr(integer(@a))); result.a := a.a+ba; result.b := a.b+bb; end;
Result:
>Project7.exe ba=10 bb=100 call test1 aa=1 ab=2 test1 ra=11 rb=102 call test2 aa=2 ab=3 test3 ra=12 rb=103 call test3 aa=3 ab=4 @a=31301448 @b=31301448 test3 ra=13 rb=104 call test4 aa=4 ab=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