В комментарии к последнему посту Huseyn спросил:
В Delphi все интерфейсы являются наследникамиДа, это верно, что в Delphi создание интерфейса всегда подразумевает неявное наследование отIUnknown
(илиIInterface
- Hallvard Vassbotn), но в мире C есть ещё более базовая сущность - интерфейсы, не являющиеся наследникамиIUnknown
. Я встретил такой интерфейс в одной DLL, которую мне надо было использовать. Но я не могу это сделать в 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, для которой мы обеспечиваем реализацию интерфейсов.
> Этот маленький пример кода сначала объявляет чистый абстрактный класс
ОтветитьУдалитьЭтот маленький пример кода НЕ объявляет чистый абстрактный класс, потому что все классы неявно наследуются от TObject, который имеет неабстрактные виртуальные методы.
Не-COM интерфейс в стиле C++ -- это не class, а старый паскалевский object.
Это полезно и работает только до тех пор, пока вам не понадобится реализация нескольких интерфейсов одним классом.
ОтветитьУдалить>>> Этот маленький пример кода НЕ объявляет чистый абстрактный класс, потому что все классы неявно наследуются от TObject, который имеет неабстрактные виртуальные методы.
ОтветитьУдалитьНеверно. Все встроенные методы TObject - "волшебные", они лежат по отрицательным смещениям. Первый виртуальный метод в наследнике будет иметь индекс 0.
>>> работает только до тех пор, пока вам не понадобится реализация нескольких интерфейсов одним классом.
Ну, есть делегация, но да, не слишком удобно. Об этом в статье тоже сказано.