2011-10-21 32 views
4

我試圖調用通過RTTI(運行D2010版本14.0.3593.25826)獲得的構造函數。構造函數將字符串和對象作爲參數混合使用,所有這些都應初始化爲''nil。 (聲明:我知道,所需的構造將是一個帶有參數的最大數量,因此看起來怪怪的,但次優設計)德爾福:調用構造函數引發EInvalidCast

的代碼去如下:

program sb_rtti; 
{$APPTYPE CONSOLE} 
uses RTTI, TypInfo, SysUtils; 

type 

TMyClass = class (TObject) 
    FField1: string; 
    FObject1: TObject; 
public 
    constructor Create(Field1: string = ''; Object1: TObject = nil); 
end; 

constructor TMyClass.Create(Field1: string; Object1: TObject); 
begin 
    FField1 := Field1; 
    FObject1 := Object1; 
end; 

function GetConstructor(rType: TRttiType) : TRttiMethod; 
var 
    MaxParams: integer; 
    Methods: TArray<TRttiMethod>; 
    Method:  TRttiMethod; 
    Params:  TArray<TRttiParameter>; 
begin 
    Methods := rType.GetMethods('Create'); 
    MaxParams := 0; 
    for Method in Methods do begin 
    Params := Method.GetParameters(); 
    if (Length(Params) > MaxParams) then begin 
     Result := Method; 
     MaxParams := Length(Params); 
    end; 
    end; 
end; 

procedure InitializeParam(Param: TRttiParameter; ActualParam: TValue); 
begin 
    if (Param.ParamType.TypeKind = TTypeKind.tkClass) then begin 
    ActualParam := TValue.From<TObject>(nil); 
    end else if (Param.ParamType.TypeKind = TTypeKind.tkString) then begin 
    ActualParam := TValue.From<string>(''); 
    end else if (Param.ParamType.TypeKind = TTypeKind.tkUString) then begin 
    ActualParam := TValue.From<UnicodeString>(''); 
    end else begin 
    // Other types goes here 
    end; 
end; 

var 
    Context:  TRttiContext; 
    Constr:  TRttiMethod; 
    Params:  TArray<TRttiParameter>; 
    ResultValue: TValue; 
    rType:  TRttiType; 
    ActualParams: array of TValue; 
    i:   integer; 
    CurrentParam: TRttiParameter; 
begin 
    Context := TRttiContext.Create(); 
    rType := Context.GetType(TypeInfo(TMyClass)); 
    Constr := GetConstructor(rType); 
    try 
    if (Constr <> nil) then begin 
     Params := Constr.GetParameters(); 
     SetLength(ActualParams, Length(Params)); 
     for i := 0 to Length(Params) - 1 do begin 
     CurrentParam := Params[i] as TRttiParameter; 
     InitializeParam(CurrentParam, ActualParams[i]); 
     end; 
     ResultValue := Constr.Invoke(rType.AsInstance.MetaclassType, ActualParams); 
    end; 
    except 
    on E : Exception do 
     WriteLn(E.ToString); 
    end; 
    ReadLn; 
end. 

現在,當行ResultValue := Constr.Invoke(rType.AsInstance.MetaclassType, ActualParams);被執行,引發EInvalidCast異常。異常可能在行追溯至TValue.Cast - 方法1336

然而,問題的肉似乎在調用堆棧中的前一個點被發現,更精確地在rtti.pas線4093(argList[currArg] := Args[i].Cast(parList[i].ParamType.Handle);) 。

我敢打賭,我以我不應該用的方式使用rtti,但是我找不到任何地方描述的「正確方法」。任何人都可以請我指出正確的方向嗎?謝謝!

回答

5

你,因爲在ActualParam參數的賦值必須在InitializeParam程序出了問題,要設置該參數的本地副本的價值 - 記住TValue(的ActualParam類型)是一個記錄。因此,要解決此問題,您必須將ActualParam作爲var參數傳遞。

procedure InitializeParam(Param: TRttiParameter; var ActualParam: TValue); 
+0

感謝的人,它只是想到了我...我一直在混合通過引用和價值傳遞的規則。 :)我想我的想法固定在「它必須與rtti相關」的想法上,我忘記了基本知識。 – conciliator

0

它只是發生在我身上硬編碼參數初始化與

ActualParams[0] := TValue.From<string>(''); 
ActualParams[1] := TValue.From<TObject>(nil); 

解決了問題更換

for i := 0 to Length(Params) - 1 do begin 
    CurrentParam := Params[i] as TRttiParameter; 
    InitializeParam(CurrentParam, ActualParams[i]); 
end;