2012-04-10 3 views
5

TRttiMethod.Invoke 함수를 사용하여 클래스의 인스턴스를 만들고 있지만 생성자 또는 메서드가 오버로드되면 rtti가 적절한 메서드를 호출하지 않습니다.TRttiMethod.Invoke 함수가 오버로드 된 메서드에서 작동하지 않습니까?

문제를 해결할 수있는 샘플 앱을 작성했습니다.

program ProjectFoo; 

{$APPTYPE CONSOLE} 

{$R *.res} 

uses 
    Rtti, 
    System.SysUtils; 

type 
    TFoo=class 
    public 
    constructor Create(Value : Integer);overload; 
    constructor Create(const Value : string);overload; 
    function Bar(value : integer) : Integer; overload; 
    function Bar(const value : string) : string; overload; 
    end; 

{ TFoo } 

constructor TFoo.Create(Value: Integer); 
begin 
    Writeln(Value); 
end; 

function TFoo.Bar(value: integer): Integer; 
begin 
    Writeln(Value); 
    Result:=value; 
end; 

function TFoo.Bar(const value: string): string; 
begin 
    Writeln(Value); 
    Result:=value; 
end; 


constructor TFoo.Create(const Value: string); 
begin 
    Writeln(Value); 
end; 

var 
c : TRttiContext; 
t : TRttiInstanceType; 
r : TValue; 
begin 
    try 
    c := TRttiContext.Create; 
    t := (c.GetType(TFoo) as TRttiInstanceType); 
    r := t.GetMethod('Create').Invoke(t.MetaclassType,[444]);//this works 
    //r := t.GetMethod('Create').Invoke(t.MetaclassType,['hello from constructor string']);//this fails : EInvalidCast: Invalid class typecast 
    t.GetMethod('Bar').Invoke(r,[1]);// this works 
    //t.GetMethod('Bar').Invoke(r,['Hello from bar']); //this fails : EInvalidCast: Invalid class typecast 
    except 
    on E: Exception do 
     Writeln(E.ClassName, ': ', E.Message); 
    end; 
    readln; 
end. 

이것은 RTTI 버그입니까? 또는 RTTI를 사용하여 클래스의 오버로드 된 메소드를 호출하는 다른 방법이 있습니까?

답변

12

TRttiMethod.Invoke 메서드에 아무런 문제가 없으며 사용자의 문제는 GetMethod에 있습니다. 이 함수는 내부적으로 TRttiType.GetMethods을 호출하고 매개 변수로 전달 된 이름과 일치하는 첫 번째 메서드에 대한 포인터를 검색합니다. 따라서이 코드 t.GetMethod('Create')을 실행할 때 항상 같은 메소드에 대한 포인터를 얻고 있습니다.

오버로드 된 버전의 생성자 또는 다른 메서드를 실행하려면 매개 변수를 기반으로 실행할 메서드 주소를 확인한 다음 TRttiMethod.Invoke 함수를 호출해야합니다.

이 샘플 기능을 확인하십시오.

function RttiMethodInvokeEx(const MethodName:string; RttiType : TRttiType; Instance: TValue; const Args: array of TValue): TValue; 
var 
Found : Boolean; 
LMethod : TRttiMethod; 
LIndex : Integer; 
LParams : TArray<TRttiParameter>; 
begin 
    Result:=nil; 
    LMethod:=nil; 
    Found:=False; 
    for LMethod in RttiType.GetMethods do 
    if SameText(LMethod.Name, MethodName) then 
    begin 
    LParams:=LMethod.GetParameters; 
    if Length(Args)=Length(LParams) then 
    begin 
     Found:=True; 
     for LIndex:=0 to Length(LParams)-1 do 
     if LParams[LIndex].ParamType.Handle<>Args[LIndex].TypeInfo then 
     begin 
     Found:=False; 
     Break; 
     end; 
    end; 

    if Found then Break; 
    end; 

    if (LMethod<>nil) and Found then 
    Result:=LMethod.Invoke(Instance, Args) 
    else 
    raise Exception.CreateFmt('method %s not found',[MethodName]); 
end; 

지금 당신은 다음 중 한 가지 방법으로 클래스의 생성자 또는 메소드를 호출 할 수

r := RttiMethodInvokeEx('Create', t, t.MetaclassType, [444]); 
    r := RttiMethodInvokeEx('Create', t, t.MetaclassType, ['hello from constructor string']); 
    r := RttiMethodInvokeEx('Create', t, t.MetaclassType, []); 
    RttiMethodInvokeEx('Bar', t, r.AsObject , ['this is a string']); 
    RttiMethodInvokeEx('Bar', t, r.AsObject , [9999]);