Наследование интерфейса Delphi: Почему я не могу получить доступ к членам интерфейса предка? - PullRequest
11 голосов
/ 07 декабря 2010

Предположим, у вас есть следующее:

//Note the original example I posted didn't reproduce the problem so
//I created an clean example  
  type
    IParent = interface(IInterface)
    ['{85A340FA-D5E5-4F37-ABDD-A75A7B3B494C}']
      procedure DoSomething;
    end;

    IChild = interface(IParent)
    ['{15927C56-8CDA-4122-8ECB-920948027015}']
      procedure DoSomethingElse;
    end;

    TGrandParent = class(TInterfacedObject)
    end;

    TParent = class(TGrandParent)
    end;

    TChild = class(TParent, IChild)
    private
      FChildDelegate: IChild;
    public
      property ChildDelegate:IChild read FChildDelegate implements IChild;
    end;

    TChildDelegate = class(TInterfacedObject, IChild)
    public
      procedure DoSomething;
      procedure DoSomethingElse;
    end;

Я думаю, что это позволит вам позвонить DoSomething, но, похоже, это не так:

procedure CallDoSomething(Parent: TParent);
begin
  if Parent is TChild then
    TChild(Parent).DoSomething;
end;

Понятно, что компилятор обеспечивает наследование интерфейса, потому что ни один класс не скомпилируется, если не реализованы члены IParent. Несмотря на это, компилятор не может разрешить члены IParent, когда создается экземпляр класса и используется.

Я могу обойти это, явно включив IParent в объявление класса TMyClass

TMyClass = class(TInterfacedObject, IChild, IParent)

Неважно, это ничего не работает.

Ответы [ 4 ]

14 голосов
/ 07 декабря 2010

Если реализующий класс не объявляет, что он поддерживает унаследованный интерфейс, то этот класс не будет совместимым по присваиванию с переменными унаследованного интерфейса.Размещенный вами пример кода должен работать нормально (с использованием интерфейса IChild), но если вы попытаетесь назначить из экземпляра TMyClass переменную IParent, у вас возникнут проблемы.

Причина в том, чтоCOM и ActiveX позволяют реализации реализовать дочерний интерфейс (ваш IChild), но отрицают предка этого интерфейса (IParent).Поскольку Delphi-интерфейсы предназначены для совместимости с COM, отсюда и возникает этот тупой артефакт.

Я почти уверен, что написал статью об этом около 10 или 12 лет назад, но мой блог Borland не выдержалпереход на сервер Embarcadero.

Возможно, есть директива компилятора, чтобы изменить это поведение, я не помню.

8 голосов
/ 07 декабря 2010

Проблема не в объявлениях интерфейса или реализации классов, а в коде вашего потребителя:

procedure CallDoSomething(Parent: TParent);
begin
  if Parent is TChild then
    TChild(Parent).DoSomething;  // << This is wrong
end;

Не будет работать, потому что TChild не имеет метода " DoSomething ».Если бы TChild реализовал IChild напрямую , то это было бы обычно возможным, потому что TChild будет реализовывать метод напрямую И как часть интерфейса IChild .

Обратите внимание, однако, что если TChild реализовано DoSomething в PRIVATE область будет оставаться доступной через интерфейс, но обычные правила области видимости означают, что вы все равно не можете вызвать ее (из-за пределов класса / uni), используя ссылку TChild либо.

В вашем случае вам просто нужно получить соответствующий интерфейс и затем вызвать метод, который вам требуется через интерфейс:

  if Parent is TChild then
    (Parent as IChild).DoSomething;

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

  var
    parentAsChild: IChild;

  begin
    if Parent.GetInterface(IChild, parentAsChild) then
      parentAsChild.DoSomething;
  end;
0 голосов
/ 14 сентября 2016

Реализация Delphi QueryInterface не соответствует стандарту. В записи блога под названием Как люди портят IUnknown :: QueryInterface Раймонд Чен перечисляет типичные сбои в реализации. Наиболее заметным является третий пункт

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

 IShellView *psv = some object;
 IOleView *pow;
 psv->QueryInterface(IID_IOleView, (void**)&pow);

Некоторые объекты забываются, и QueryInterface завершается ошибкой с E_NOINTERFACE.

Если только унаследованный интерфейс явно не присоединен к классу или одному из его предков, Delphi не сможет его найти. Он просто просматривает таблицу интерфейсов объекта и его унаследованных типов и проверяет совпадение идентификаторов интерфейсов, не проверяет базовые интерфейсы.

0 голосов
/ 07 декабря 2010

edit: Этот ответ больше не актуален, так как был опубликован до изменения исходного вопроса.


Это скомпилировано в Delphi 2010:

type
  IParent = interface(IInterface)
    function DoSomething: String;
  end;

  IChild = interface(IParent)
    function DoSomethingElse: string;
  end;

  TMyClass = class(TInterfacedObject, IChild)
  private
  public
    function DoSomething: String;
    function DoSomethingElse: String;
  end;

// ... 

procedure Test;
var
  MyObject : IChild;
begin
  MyObject := TMyClass.Create;
  MyObject.DoSomething;
end;
...