3

DelphiXEでRTTIを使用してレコードの値を設定しようとしています。GetValueメソッドを使用してレコードから値を取得できますが、SetValueメソッドを使用して値を設定することはできません。

誰かがこれを行う方法を知っていますか/なぜそれが機能しないのですか?

前もって感謝します!

私のコンテキスト:最終的な目標は、任意のXMLファイルを読み取り、アプリケーションのデータモデルにXMLからのデータを自動的に入力するコンポーネントを作成することです。データモデルには、すべての要素のXPathを決定するための注釈が付けられます。オブジェクトと基本的なデータ型については、すでに稼働しています。

  TSize = record
    X, Y: double;
  end;

  TMyTest = class
  protected
    FSize: TSize;
  public
    constructor Create;
    procedure DoStuff;
  end;

constructor TMyTest.Create;
begin
  FSize.X := 2.7;
  FSize.Y := 3.1;
end;

procedure TMyTest.DoStuff;
var
  MyContext: TRttiContext;
  MyField: TRttiField;
  MySizeField: TRttiField;
  MyVal: TValue;
  MyRecord: TRttiRecordType;
  NewVal: TValue;
begin 
  // Explicit Create of MyContext does not help (as expected)
  for MyField in MyContext.GetType(ClassType).GetFields do
    if MyField.Name = 'FSize' then //For debugging
    begin
      MyRecord := MyField.FieldType.AsRecord;
      MyVal := MyField.GetValue(Self);
      for MySizeField in MyRecord.GetFields do
      begin
        //This works
        NewVal := MySizeField.GetValue(MyVal.GetReferenceToRawData).AsExtended;
        NewVal := NewVal.AsExtended + 5.0;
        try
          // This does not work. (no feedback)
          MySizeField.SetValue(MyVal.GetReferenceToRawData, NewVal);
          // This however does work. Now to find out what the difference between the two is.
          MySizeField.SetValue(@FSize, NewVal);
        except
          on e: Exception do //Never happens
            ShowMessage('Oops!' + sLineBreak + e.Message);
        end;
      end;
    end;
  // Shows 'X=2.7 Y=3.1'
  // Expected 'X=7.7 Y=8.1'
  ShowMessage(Format('X=%f Y=%f', [FSize.X, FSize.Y]));
end;

TSizeがクラスとして宣言されている場合はMyVal.GetReferenceToRawData、コード内で。に置き換える必要がありますTObject(MyVal.GetReferenceToRawData^)。これを行うと、すべて期待どおりに機能します。(はい、MyVal.AsObjectその場合もトリックを実行します)これにより、考えられる解決策が導き出されますMyVal.GetReferenceToRawData^。正しいレコードタイプに型キャストします。しかし、これはどのように行うことができますか?

SetValueで直接@FSizeを使用してみました。ご想像のとおり、これは機能します。これにより、次の質問がトリガーされます。@FSizeとMyVal.GetReferenceToRawDataの違いは何ですか。


さらに調査を重ねた結果、MyValは実際にはレコードのコピーであることがわかりました。実際、Sergが最初の回答で述べたように値は正しく設定されていましたが、コピーで設定されていました。これを早く気づかなかったのはとてもばかげています...

とにかく、以下は機能するコードサンプルです。また、「フィールド名パス」を使用する場合は、この投稿でバリーケリーのアプローチを確認できます。これにより、私は順調に進んでいます。Followルーチンに必要なパスが気に入らなかっただけです。

procedure TMyTest.DoStuff;
var
  MyContext: TRttiContext;
  MyField: TRttiField;
  MySizeField: TRttiField;
  NewVal: TValue;
  dMyVal: double;
begin
  for MyField in MyContext.GetType(ClassType).GetFields do
    if MyField.Name = 'FSize' then
    begin
      for MySizeField in MyField.FieldType.GetFields do
      begin
        dMyVal := MySizeField.GetValue(PByte(Self) + MyField.Offset).AsExtended;
        NewVal := TValue.From(dMyVal + 5.1);
        try
          MySizeField.SetValue(PByte(Self) + MyField.Offset, NewVal);
        except
          on e: Exception do
            ShowMessage('Oops!' + sLineBreak + e.Message);
        end;
      end;
    end;
  if FSize.X > 5.0 then
    ShowMessage(Format('X=%f Y=%f', [FSize.X, FSize.Y]));
end;
4

3 に答える 3

1

MyVal.GetReferenceToRawDataFSizeフィールドへのポインタを返さないため、FSizeフィールド値は変更されません。

私のコードがあなたが書き込もうとしているものであるかどうかはわかりませんが、それが役立つことを願っています:

uses rtti, typinfo;

type
  TSize = record
    X, Y: double;
  end;

  TMyTest = class
  protected
    FSize: TSize;
  public
    constructor Create;
    procedure DoStuff;
  end;

constructor TMyTest.Create;
begin
  FSize.X := 2.7;
  FSize.Y := 3.1;
end;

procedure TMyTest.DoStuff;
var
  MyContext: TRttiContext;
  MyField: TRttiField;
  MyVal: TValue;
  NewSize: TSize;

begin
  // Explicit Create of MyContext does not help (as expected)
  for MyField in MyContext.GetType(ClassType).GetFields do
    if MyField.Name = 'FSize' then //For debugging
    begin
      MyVal := MyField.GetValue(Self);
      NewSize:= MyVal.AsType<TSize>;
      NewSize.X:= NewSize.X + 5;
      NewSize.Y:= NewSize.Y + 5;
      TValue.Make(@NewSize, TypeInfo(TSize), MyVal);
      MyField.SetValue(Self, MyVal);
    end;
  ShowMessage(Format('X=%f Y=%f', [FSize.X, FSize.Y]));
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  Test: TMyTest;

begin
  Test:= TMyTest.Create;
  Test.DoStuff;
  Test.Free;
end;
于 2012-04-25T10:28:50.767 に答える
1

同じ問題がありましたが、私の場合、フィールドではなくプロパティを使用していました。TRttiPropertyには、ロットを検索していくつかのテストを実行した後、問題を解決するためのプロパティオフセットがないため、非常に複雑でした。解決策を見つけて、ここで共有しています。

procedure TMyTest.DoStuff;
var
  MyContext: TRttiContext;
  MyProp: TRttiProperty;
  MySizeField: TRttiField;
  NewVal: TValue;
  dMyVal: double;
  MyPointer:Pointer;
begin
  for MyProp in MyContext.GetType(ClassType).GetProperties do
    if MyProp.Name = 'Size' then
    begin
      MyPointer := TRttiInstanceProperty(MyProp).PropInfo^.GetProc;
      for MySizeField in MyProp.PropertyType.GetFields do
      begin
        dMyVal := MySizeField.GetValue(PByte(Self) + Smallint(MyPointer)).AsExtended;
        NewVal := TValue.From(dMyVal + 5.1);
        try
          MySizeField.SetValue(PByte(Self) + Smallint(MyPointer), NewVal);
        except
          on e: Exception do
            ShowMessage('Oops!' + sLineBreak + e.Message);
        end;
      end;
    end;
  if FSize.X > 5.0 then
    ShowMessage(Format('X=%f Y=%f', [FSize.X, FSize.Y]));
end;
于 2015-10-23T19:33:58.670 に答える
-2

RTTIは、公開されたプロパティで最適に機能します。だからあなたが追加した場合

公開されたプロパティサイズ:TSize読み取りfSize書き込みfSize;

あなたのクラスにとって、私はあなたがより良い結果をもたらすと思います。

于 2012-04-24T16:48:14.803 に答える