我有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>()