Error with RTTI parameters TRttiMethod.Invoke, stdcall and const

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

+6
source share
2 answers

You are facing the same problem as me. Let me quote what Barry said:

It is by design; the Rtti.Invoke function is too low on the stack and does not have access to any type of info that could tell it whether to pass arguments by reference or by value. He expects that all parameters will be converted to the correct type, including any by-ref parameters that will be converted to pointers as needed. All he does is enter the values ​​into the registers and / or the stack as needed, call and retrieve the return value (if any) from the appropriate place.

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

+11
source

If you think you have found a bug, you should send it to the QC Embarcadero system:

http://qc.embarcadero.com/wc/qcmain.aspx

This is a good place to report errors, and it is also the only place where reporting an error has every chance of fixing the error.

0
source

Source: https://habr.com/ru/post/890986/


All Articles