Ошибка чтения / записи в свойствах типа generi c с использованием RTTI - PullRequest
1 голос
/ 22 апреля 2020

Я использую класс generi c, чтобы позволить мне получить доступ к именованному свойству типа generi c и прочитать / записать его значение. Я получаю ошибку EAccessViolation при попытке получить доступ к результату вызова GetValue из записи RTTIProperty, а также при установке значения с помощью SetValue. При запуске трассировки кажется, что обе ошибки выбрасываются при доступе к TValue. Ниже я включил пример консольного приложения, в котором освещается эта проблема.

program Project1;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils,
  System.RTTI;

Type
  TTestClass = class
  private
    FItem: string;
  public
    Property Item: string read FItem write FItem;
  end;

  TAccessData<T> = class
    Function GetTValue(AItem : T; AField : string) : TValue;
    Procedure SetTValue(AItem : T; Afield : string; AValue : TValue);
  end;

{ TAccessData<T> }

function TAccessData<T>.GetTValue(AItem: T; AField: string): TValue;
var
  LContext : TRTTIContext;
  LType : TRttiType;
  LProperty : TRttiProperty;

begin
  result := nil;
  LType := LContext.GetType(Typeinfo(T));
  LProperty := LType.GetProperty(Afield);
  if LProperty <> nil then
    Result := LProperty.GetValue(@AItem);
end;

var
  LTestObj : TTestClass;
  LAccessOBj : TAccessData<TTestClass>;
  AValue : TValue;

procedure TAccessData<T>.SetTValue(AItem: T; Afield: string; AValue: TValue);
var
  LContext : TRTTIContext;
  LType : TRttiType;
  LProperty : TRttiProperty;

begin
  LType := LContext.GetType(Typeinfo(T));
  LProperty := LType.GetProperty(Afield);
  if LProperty <> nil then
    LProperty.SetValue(@AItem, AValue);
end;

begin
  try
    LTestObj := TTestClass.Create;
    LTestObj.Item := 'Hello';
    Writeln(LTestObj.Item);
    LAccessOBj := TAccessData<TTestClass>.Create;
    AValue := LAccessObj.GetTValue(LTestObj, 'Item');
    Writeln(AValue.TypeInfo^.Name);
    if AValue.TypeInfo.Kind <> tkString then
      Writeln('Not string');
    Writeln(AValue.ToString); // <--- This results in a EAccessViolation
    LAccessOBj.SetTValue(LTestObj,'Item','World'); // <--- This results in a EAccessViolation
    Writeln(LTestObj.Item);
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
  Readln;
end.

Я подозреваю, что мне чего-то не хватает в способе доступа к свойствам типов generi c, но я столкнулся со стеной, чтобы почему у меня такое поведение. Я не налагал ограничений на параметр generi c, так как он необходим для работы и с типами записей.

Использование обновления Токио 1

1 Ответ

1 голос
/ 23 апреля 2020

Ваш код в GetTValue и SetTValue является дефектным, так как он передает @AItem в TRttiProperty.SetValue и GetValue. Это должно быть PPointer(@AItem)^ или ограничение T до class, чтобы вы могли напрямую использовать hardcast с Pointer(AItem).

Из-за неправильно переданного AInstance TValue содержит некоторую память, которую вы можно увидеть, если вы вводите строковую переменную и присваиваете ей результат вызова ToString, прежде чем пытаться передать ее в Writeln. И код в Writeln затем производит AV.

...