Delphi Rtti для интерфейсов в общем контексте - PullRequest
6 голосов
/ 08 июня 2011

для фреймворка я написал оболочку, которая использует любой объект, интерфейс или тип записи для изучения его свойств или полей.Объявление класса выглядит следующим образом:

TWrapper<T> = class 
private
  FType : TRttiType;
  FInstance : Pointer;
  {...}
public
  constructor Create (var Data : T);
end;

В конструкторе я пытаюсь получить информацию о типе для дальнейших шагов обработки.

constructor TWrapper<T>.Create (var Data : T);
begin
FType := RttiCtx.GetType (TypeInfo (T));
if FType.TypeKind = tkClass then
  FInstance := TObject (Data)
else if FType.TypeKind = tkRecord then
  FInstance := @Data
else if FType.TypeKind = tkInterface then
  begin
  FType := RttiCtx.GetType (TObject (Data).ClassInfo); //<---access violation
  FInstance := TObject (Data);
  end
else
  raise Exception.Create ('Unsupported type');
end;

Интересно, является ли это нарушение доступа ошибкой вКомпилятор Delphi (я использую XE).После дальнейшего исследования я написал простую тестовую функцию, которая показывает, что запрос имени класса также вызывает это исключение:

procedure TestForm.FormShow (Sender : TObject);
var
  TestIntf : IInterface;
begin
TestIntf    := TInterfacedObject.Create;
OutputDebugString(PChar (TObject (TestIntf).ClassName)); //Output: TInterfacedObject
Test <IInterface> (TestIntf);
end;

procedure TestForm.Test <T> (var Data : T);
begin
OutputDebugString(PChar (TObject (Data).ClassName)); //access violation
end;

Может кто-нибудь объяснить мне, что не так?Я также попробовал процедуру без параметра var, который тоже не работал.При использовании не универсальной процедуры все работает нормально, но для упрощения использования обертки было бы неплохо универсальное решение, поскольку оно работает для объектов и записей одинаково.

С уважением,

Christian

Ответы [ 2 ]

8 голосов
/ 08 июня 2011

Ваш код содержит два неверных предположения:

  • что вы можете получить значимые RTTI из интерфейсов. Ой, вы можете получить RTTI из интерфейса типов .
  • То, что Интерфейс всегда реализуется объектом Delphi (следовательно, вы пытаетесь извлечь RTTI из вспомогательного объекта Delphi).

Оба предположения неверны. Интерфейсы - это очень простые таблицы VIRTUAL METHOD, очень мало волшебства для них. Поскольку интерфейс так узко определен, он не может иметь RTTI. Если, конечно, вы не реализуете свой собственный вариант RTTI, и вы не должны этого делать. LE: Сам интерфейс не может передавать информацию о типе так, как это делает объект TObject, но оператор TypeOf () может получить ВведитеInfo, если предоставляется IInterface

Ваше второе предположение также неверно, но не так. В мире Delphi большинство интерфейсов будут реализованы объектами Delphi, если, конечно, вы не получите интерфейс из DLL, написанной на другом языке программирования: интерфейсы Delphi совместимы с COM, поэтому его реализации можно использовать из любого другого COM-совместимого языка и наоборот. Но поскольку мы говорим здесь о Delphi XE, вы можете использовать этот синтаксис для приведения интерфейса к реализующему его объекту интуитивно понятным и читабельным способом:

TObject := IInterface as TObject;

, то есть используйте оператор as. Delphi XE время от времени автоматически конвертирует жесткое приведение этого типа:

TObject := TObject(IInterface);

к упомянутому "as" синтаксису, но мне не нравится это волшебство, потому что оно выглядит очень нелогичным и ведет себя по-разному в старых версиях Delphi.

Приведение Interface обратно к его реализующему объекту также неверно с другой точки зрения: он будет показывать all свойства реализующего объекта, а не только те, которые связаны с интерфейсом, и это очень неправильно потому что вы используете интерфейсы, чтобы скрыть эти детали реализации в первую очередь!

Пример: реализация интерфейса не поддерживается объектом Delphi

Просто для удовольствия, вот небольшая демонстрация интерфейса, который не , поддерживаемый объектом Delphi. Поскольку интерфейс - это не что иное, как указатель на таблицу виртуальных методов, я создам таблицу виртуальных методов, создам указатель на нее и приведу указатель на нужный тип интерфейса. Все указатели методов в моей поддельной таблице виртуальных методов реализованы с использованием глобальных функций и процедур. Только представьте, что вы пытаетесь извлечь RTTI из моего i2 интерфейса!

program Project26;

{$APPTYPE CONSOLE}

uses
  SysUtils;

type

  // This is the interface I will implement without using TObject
  ITestInterface = interface
  ['{CFC4942D-D8A3-4C81-BB5C-6127B569433A}']
    procedure WriteYourName;
  end;

  // This is a sample, sane implementation of the interface using an
  // TInterfacedObject method
  TSaneImplementation = class(TInterfacedObject, ITestInterface)
  public
    procedure WriteYourName;
  end;

  // I'll use this record to construct the Virtual Method Table. I could use a simple
  // array, but selected to use the record to make it easier to see. In other words,
  // the record is only used for grouping.
  TAbnormalImplementation_VMT = record
    QueryInterface: Pointer;
    AddRef: Pointer;
    ReleaseRef: Pointer;
    WriteYourName: Pointer;
  end;

// This is the object-based implementation of WriteYourName
procedure TSaneImplementation.WriteYourName;
begin
  Writeln('I am the sane interface implementation');
end;

// This will implement QueryInterfce for my fake IInterface implementation. All the code does
// is say the requested interface is not supported!
function FakeQueryInterface(const Self:Pointer; const IID: TGUID; out Obj): HResult; stdcall;
begin
  Result := S_FALSE;      
end;

// This will handle reference counting for my interface. I am not using true reference counting
// since there is no memory to be freed, si I am simply returning -1
function DummyRefCounting(const Self:Pointer): Integer; stdcall;
begin
  Result := -1;
end;

// This is the implementation of WriteYourName for my fake interface.
procedure FakeWriteYourName(const Self:Pointer);
begin
  WriteLn('I am the very FAKE interface implementation');
end;

var i1, i2: ITestInterface;
    R: TAbnormalImplementation_VMT;
    PR: Pointer;

begin
  // Instantiate the sane implementation
  i1 := TSaneImplementation.Create;

  // Instantiate the very wrong implementation
  R.QueryInterface := @FakeQueryInterface;
  R.AddRef := @DummyRefCounting;
  R.ReleaseRef := @DummyRefCounting;
  R.WriteYourName := @FakeWriteYourName;
  PR := @R;
  i2 := ITestInterface(@PR);

  // As far as all the code using ITestInterface is concerned, there is no difference
  // between "i1" and "i2": they are just two interface implementations.
  i1.WriteYourName; // Calls the sane implementation
  i2.WriteYourName; // Calls my special implementation of the interface

  WriteLn('Press ENTER to EXIT');
  ReadLn;
end.
2 голосов
/ 08 июня 2011

Два возможных ответа.

Если это всегда происходит, даже если T является объектом, это ошибка компилятора, и вам следует подать отчет о контроле качества.(С интерфейсами для преобразования типа «интерфейс к объекту» требуется некоторая черная магия от компилятора, и вполне возможно, что подсистема обобщений не реализует ее должным образом.)

Если выесли взять T, который не является объектом, например, типа записи, и получить эту ошибку, то все будет работать, как задумано;вы просто неправильно используете типы типов.

В любом случае, есть способ получить информацию RTTI любого произвольного типа.Вы знаете, как TRttiContext.GetType имеет две перегрузки?Используйте другой.Вместо вызова GetType (TObject (Data).ClassInfo), попробуйте calling GetType(TypeInfo(Data)).

Oh, и объявите FInstance как T вместо pointer.Это избавит вас от многих хлопот.

...