Подтвердить что ты не робот

Получение подинтерфейса из интерфейса

Это немного особый случай интерфейсов, где класс реализует несколько версий одного и того же интерфейса, т.е. что-то вроде следующего

IBase = interface
   procedure Foo;
end;

ISub = interface (IBase)
   procedure Bar;
end;

ISpecialBase = interface (IBase) end;

ISpecialSub = interface (ISub) end;

TMyClass = class(TInterfacedObject, ISpecialBase, ISpecialSub)

   procedure SpecialFoo1;
   procedure SpecialFoo2;
   procedure SpecialBar;

   procedure ISpecialBase.Foo = SpecialFoo1;

   procedure ISpecialSub.Foo = SpecialFoo2;
   procedure ISpecialSub.Bar = SpecialBar;

   function GetTheRightOne(parameters) : IBase;

end;

...

function TMyClass.GetTheRightOne(parameters) : IBase;
begin
   if (something complex depending on parameters) then
      Result := ISpecialBase(Self)
   else Result := ISpecialSub(Self)
end;

конечно, в реальном случае имеется около десятка ISpecialXxxx.

Очень важно иметь только один экземпляр, т.е. Я хочу, чтобы избежать необходимости создавать адаптеры или фиктивные экземпляры только для того, чтобы отложить реализацию ISpecialXxxx, поскольку единственная цель предыдущего проекта - именно то, чтобы один экземпляр обработал много выдающихся интерфейсов (т.е. RefCount TMyClass может попасть в тысячные).

Теперь проблема в том, что GetTheRightOne() возвращает IBase, но в какой-то момент я хочу проверить, может ли этот IBase быть передан в ISub.

Есть ли способ сделать это с помощью указанной выше формы объявления?

В один из способов можно добавить

function GetSub : ISub;

для IBase, но это действительно делает дизайн намного более тяжелым, так как это должно быть реализовано для каждого ISpecialXxxx и будет избыточным с "наследованием" ISpecialXxxx, поэтому я ищу более элегантное решение (при условии, что он существует).

(У меня есть другие "раздутые" решения, поэтому я действительно хочу подчеркнуть, что я ищу решение без вздутия)

изменить: несколько подробнее

  • В исходном коде существуют GUID (но их недостаток не является тем, что вызывает трудности).
  • Поддержка и QueryInterface не работают, поскольку ISpecialXxx необходимо иметь несколько версий интерфейса для каждого класса, ISub не указан явно, и поэтому не найден. Тем не менее, оба работают с адаптером/фиктивным классом для отсрочки интерфейса (так как ISub может быть явно указан)

edit2: если вы хотите детали gory

Отметьте https://code.google.com/p/dwscript/source/browse/trunk/Source/dwsJSONConnector.pas (r2492), класс TdwsJSONConnectorType и интерфейс IJSONLow, цель состоит в том, чтобы обнаружить IConnectorFastCall от него, когда он прошел как IConnectorCall и, таким образом, имел возможность использовать LowFastCall, а не LowCall.

Обнаружение должно происходить в TConnectorCallExpr.AssignConnectorSym, строка 294, где в настоящее время имеется QueryInterface.

Обратите внимание, что QueryInterface работает в случае TdwsJSONIndexReadCall и TdwsJSONIndexWriteCall, поскольку они реализуют IConnectorCall и IConnectorFastCall из разных классов и экземпляров. Но этого я бы хотел избежать.

Конечно, целью было бы свернуть все в класс ConnectorType (один класс, один экземпляр), и для каждого интерфейса конкретный класс ConnectorType должен быть свободен для реализации IConnectorCall или IConnectorFastCall.

4b9b3361

Ответ 1

Один хакерский подход основывается на том, как компилятор хранит данные VTable интерфейса. Компилятор хранит отдельные VTables для каждого интерфейса, который реализует объект. После каждого VTable он сохраняет количество интерфейсов, которые реализует объект.

Итак, мы можем использовать это, чтобы определить, получили ли мы VTable интерфейса предка или потомка.

По крайней мере, так оно работает в XE3 и XE5, я должен признать, что я немного n00b, когда речь заходит о том, как реализуются интерфейсы.

Недостаток этого, помимо того, что он опирается на детали реализации, заключается в том, что вам нужно будет синхронизировать функцию GetSub, если вы добавляете методы в интерфейс IBase. Кроме того, если у вас есть два разных, не связанных между собой, ISub, то этот код не может определить, что вы получили. Вы можете взломать это, но я бы предпочел не туда...

{$APPTYPE CONSOLE}

uses
  System.SysUtils;

type
  IBase = interface
    procedure Foo;
  end;

  ISub = interface (IBase)
    procedure Bar;
  end;

  ISpecialBase = interface (IBase)
  end;

  ISpecialSub = interface (ISub)
  end;

  TMyClass = class(TInterfacedObject, ISpecialBase, ISpecialSub)

    procedure SpecialFoo1;
    procedure SpecialFoo2;
    procedure SpecialBar;

    procedure ISpecialBase.Foo = SpecialFoo1;

    procedure ISpecialSub.Foo = SpecialFoo2;
    procedure ISpecialSub.Bar = SpecialBar;

    function GetTheRightOne(const Param: boolean) : IBase;
  end;


{ TMyClass }

function TMyClass.GetTheRightOne(const Param: boolean): IBase;
begin
  if Param then
    Result := ISpecialBase(Self)
  else
    Result := ISpecialSub(Self);
end;

procedure TMyClass.SpecialBar;
begin
  WriteLn('SubBar');
end;

procedure TMyClass.SpecialFoo1;
begin
  WriteLn('BaseFoo');
end;

procedure TMyClass.SpecialFoo2;
begin
  WriteLn('SubFoo');
end;

function GetSub(const Intf: IInterface): ISub;
type
  PPVtable = ^PVtable;
  PVtable = ^TVtable;
  TVtable = array[0..MaxInt div SizeOf(Pointer) - 1] of Pointer;
var
  intfVTable: PPVtable;
  caddr: NativeUInt;
begin
  result := nil;
  intfVTable := PPVTable(Intf);
  // 3 is offset to user methods
  // +0 = first user method, +1 = second user method etc
  // get the "address" of the first method in ISub
  caddr := NativeUInt(intfVTable^[3+1]);
  // compiler stores number of interface entries the
  // implementing object implements right after the interface vtable
  // so if we get a low number here, it means Intf is the IBase interface
  // and not the ISub
  if caddr > $100 then
    result := ISub(Intf);
end;

procedure CallIt(const b: IBase);
var
  s: ISub;
begin
  b.Foo;

  s := GetSub(b);
  if Assigned(s) then
    s.Bar;
end;

var
  c: TMyClass;
  b: IBase;
begin
  try
    c := TMyClass.Create;

    b := c.GetTheRightOne(True);
    CallIt(b);

    WriteLn('---');

    b := c.GetTheRightOne(False);
    CallIt(b);

    WriteLn('...');

  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
  ReadLn;
end.

Выводит

BaseFoo
---
SubFoo
SubBar
...

как мы хотим.

Ответ 2

Чтобы узнать, реализует ли интерфейс интерфейса другой интерфейс, Supports или QueryInterface, как в следующем псевдокоде:

var
  Base: IBase;
  Sub: ISub;
begin
  Base := X.GetTheRightOne(Params);  
  if Supports(Base, ISub, Sub) then
    Sub.Bar;
end;

Изменить: для работы выше вы должны добавить IIDs в объявления интерфейсов.

Ответ 3

Вот мое текущее "лучшее" решение:

Я отказался от предложения о разрешении метода и перешел в фиктивные классы, привязанные к основному классу, и которые создаются только один раз.

Таким образом, GetInterface и Supports могут использоваться, поскольку ISub снова явственен.

Однако это вызывает проблему с круговой ссылкой: основной класс должен ссылаться на специальные предложения (только для того, чтобы возвращать их в GetTheRightOne()), а специальные предложения должны ссылаться на основной класс (для доступа к хранимым там параметрам или перенаправлению к методам основного класса).

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

Однако, учитывая, что специальные символы являются фиктивными классами, используемыми только для разрешения интерфейса основного класса, мы можем переопределить их _AddRef и _Release, чтобы иметь ссылочный счет, централизованный по основному классу (то есть _AddRef и _Release просто перенаправляют на основной класс _AddRef и _Release, и больше не поддерживают собственный счетчик ссылок.)

Ответ 4

Наследование интерфейсов не соответствует тем же принципам, что и наследование классов. Поэтому, чтобы проверить, поддерживает ли IBase ISub, класс реализации должен явно объявить ISub:

TMyClass = class(TInterfacedObject, ISub, ISpecialBase, ISpecialSub)

Запрос для интерфейса не проверяет наследуемые интерфейсы. AFAIR, когда интерфейсы, введенные в Delphi 2 (?), Один из компиляторов однажды заметил, что наследование интерфейса было не более чем синтаксическим сахаром.