четверг, 21 июля 2011 г.

Чистые интерфейсы в Delphi

Это перевод Pure interfaces in Delphi. Автор: Hallvard Vassbotn.

В комментарии к последнему посту Huseyn спросил:
В Delphi все интерфейсы являются наследниками IUnknown (или IInterface - Hallvard Vassbotn), но в мире C есть ещё более базовая сущность - интерфейсы, не являющиеся наследниками IUnknown. Я встретил такой интерфейс в одной DLL, которую мне надо было использовать. Но я не могу это сделать в Delphi.
Да, это верно, что в Delphi создание интерфейса всегда подразумевает неявное наследование от IUnknown или IInterface (два имени для отличий COM и Delphi интерфейсов). И это означает, что все интерфейсы в Delphi будут иметь три метода QueryInterface, _AddRef и _Release. Это, конечно, делает сложным (или невозможным) реализацию чистого интерфейса из не-COM DLL с использованием синтаксиса объявления интерфейсов в Delphi.

В далёкие тёмные времена Delphi 2, у нас не было явных интерфейсов, а поддержка COM строилась на том факте, что структура таблицы VMT совпадает с бинарным контрактом COM, а "интерфейсы" были объявлены с использованием полностью абстрактных классов. К примеру, именно так до сих пор интерфейсы объявляются в C++. Класс, который хочет реализовать такой "интерфейс", просто наследуется от чистого абстрактного класса, замещая и реализуя все его абстрактные методы. Это достаточно ограниченный способ написания интерфейсов; поскольку Delphi не поддерживает множественное наследование классов, то вы точно захотите уметь поддерживать множество интерфейсов - и тогда вам придётся писать по одному новому классу на каждый интерфейс и вручную писать метод QueryInterface, чтобы он возвращал корректную ссылку. В C++ нет ограничения на множественное наследование и, быть может, поэтому в C++ нет отдельной языковой конструкции для интерфейсов - по сравнению с языками вроде Java, Delphi и C#, которые имеют модель единственности наследования.

Этот небольшой урок истории должен дать нам подсказку о том, как мы можем избежать методов IUnknown обычных интерфейсов - мы можем просто объявить и реализовать чисто абстрактный класс.

К примеру:
program TestPureInterface;

{$APPTYPE CONSOLE}

type
  TMyPureInterface = class
    procedure FirstMethod; stdcall; virtual; abstract; 
    procedure SecondMethod; stdcall; virtual; abstract; 
  end;
 
  TMyImplementation  = class(TMyPureInterface)
  public
    procedure FirstMethod; override; 
    procedure SecondMethod; override;
  end;
   
procedure TMyImplementation.FirstMethod;
begin
  Writeln('TMyImplementation.FirstMethod');
end;
 
procedure TMyImplementation.SecondMethod;
begin
  Writeln('TMyImplementation.SecondMethod');
end;
 
procedure TestClient(PureInterface: TMyPureInterface);
begin
  PureInterface.FirstMethod;
  PureInterface.SecondMethod;
end;  
 
var
  MyImplementation: TMyImplementation;
begin
  MyImplementation := TMyImplementation.Create;
  TestClient(MyImplementation);
  readln;
end.
Этот маленький пример кода сначала объявляет чистый абстрактный класс с виртуальными абстрактными методами. Это объявление не-COM интерфейса в стиле C++ для DLL из вопроса. Мы реализуем "интерфейс" созданием класса-наследника от этого базового полностью абстрактного класса, в котором реализуем все методы. Заметьте, что компилятор не требует повторно указывать соглашение вызова при замещении методов:
procedure FirstMethod; override;
И, наконец, я написал немного тестового кода, который осуществляет вызов реализованных методов через ссылку на "интерфейс". Это соответствует коду в DLL, для которой мы обеспечиваем реализацию интерфейсов.

3 комментария:

  1. > Этот маленький пример кода сначала объявляет чистый абстрактный класс

    Этот маленький пример кода НЕ объявляет чистый абстрактный класс, потому что все классы неявно наследуются от TObject, который имеет неабстрактные виртуальные методы.

    Не-COM интерфейс в стиле C++ -- это не class, а старый паскалевский object.

    ОтветитьУдалить
  2. Это полезно и работает только до тех пор, пока вам не понадобится реализация нескольких интерфейсов одним классом.

    ОтветитьУдалить
  3. >>> Этот маленький пример кода НЕ объявляет чистый абстрактный класс, потому что все классы неявно наследуются от TObject, который имеет неабстрактные виртуальные методы.

    Неверно. Все встроенные методы TObject - "волшебные", они лежат по отрицательным смещениям. Первый виртуальный метод в наследнике будет иметь индекс 0.

    >>> работает только до тех пор, пока вам не понадобится реализация нескольких интерфейсов одним классом.

    Ну, есть делегация, но да, не слишком удобно. Об этом в статье тоже сказано.

    ОтветитьУдалить

Можно использовать некоторые HTML-теги, например:

<b>Жирный</b>
<i>Курсив</i>
<a href="http://www.example.com/">Ссылка</a>

Вам необязательно регистрироваться для комментирования - для этого просто выберите из списка "Анонимный" (для анонимного комментария) или "Имя/URL" (для указания вашего имени и ссылки на сайт). Все прочие варианты потребуют от вас входа в вашу учётку.

Пожалуйста, по возможности используйте "Имя/URL" вместо "Анонимный". URL можно просто не указывать.

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

Примечание. Отправлять комментарии могут только участники этого блога.