Delphi: Как установить значение поля универсального с использованием RTTI? - PullRequest
2 голосов
/ 10 ноября 2011

Я хотел бы заполнить поле универсального объекта во время выполнения, используя D2010.

program generic_rtti_1;
{$APPTYPE CONSOLE}
uses
  SysUtils, rtti;
type
  TMyObject = class
    FField1: string;
  end;
  TGeneric<TElement: class> = class
    procedure FillFields(Element: TElement);
  end;
procedure TGeneric<TElement>.FillFields(Element: TElement);
var
  ctx:  TRttiContext;
begin
  ctx := TRttiContext.Create();
  ctx.GetType(TypeInfo(TElement)).GetField('FField1').
    SetValue(@Element, TValue.FromVariant('Some string'));
  ctx.Free();
end;

Когда выполняется строка ctx.Free();, я получаю AV по строке 21986 в System.pas (функция _IntfClear ()). Это называется с FContextToken := nil в rtti.pas. (На самом деле, SetValue -индуцированный AV всплывает, если я вступаю в SetValue, однако, если перешагнуть через него, сообщается только о ctx.Free -индуцированном. См. Ниже.)

Если я удаляю ctx.Free();, при вызове SetValue(@Element, TValue.FromVariant('Some string')); появляется AV. Это тоже в строке 21986 в System.pas.

Пытаясь разобраться в этом беспорядке, я заменил

ctx.GetType(TypeInfo(TElement)).GetField('FField1').
  SetValue(@Element, TValue.FromVariant('Field 1 is set'));

с этим:

rType := ctx.GetType(TypeInfo(TElement));
rField := rType.GetField('FField1');
Val := TValue.FromVariant('Field 1 is set');
rField.SetValue(@Element, Val);

На этот раз я не получил ошибку, однако WriteLn(MyObject.FField1) напечатал пустую строку. (AV снова появляется, если я комбинирую SetValue и TValue.FromVariant, т.е. пишу rField.SetValue(@Element, TValue.FromVariant('Field 1 is set'));.

Чтобы точно определить виновную строку, я закомментировал строку за строкой, заменив закомментированный код составным оператором. Случайно я забыл закомментировать вышеприведенную строку Val := TValue.FromVariant('Field 1 is set');, из-за которой AV снова исчезает (все еще вызывая rField.SetValue(@Element, TValue.FromVariant('Field 1 is set'));). (Обратите внимание, что я на самом деле не использую Val в проблемном вызове, но AV исчезает.)

Я немного потерян в этот момент.

Для полноты картины вот как я хотел бы использовать приведенный выше код:

var
  Generic:  TGeneric<TMyObject>;
  MyObject: TMyObject;
begin
  MyObject := TMyObject.Create();
  Generic := TGeneric<TMyObject>.Create();
  Generic.FillFields();
  WriteLn(MyObject.FField1);
  Generic.Free();
  MyObject.Free();
  ReadLn;
end;
end.

Кто-нибудь знает, что я делаю не так? (Это вообще возможно? Есть ли лучшие способы сделать это с помощью дженериков?)

1 Ответ

6 голосов
/ 10 ноября 2011

Ну, я не знаю, имеет ли это смысл для вас, ребята, но вот как я это решил.Твердое приведение к TObject в procedure TGeneric<TElement>.FillFields работает как шарм.Вот так:

ctx.GetType(TypeInfo(TElement)).GetField('FField1').
  SetValue(TObject(Element), TValue.FromVariant('Field 1 is set'));

Надеюсь, что это пригодится кому-то еще.

...