Решил разобраться для себя с тем, что такое интерфейсы, хотя бы в первом приближении. Много где они используются в современном Delphi – и FireMonkey и в COM и др. В данной статье – все самое простое – что такое интерфейс и как им пользоваться в самом простом случае.
Что такое интерфейс?
Интерфейс, это сущность, содержащая в себе уникальный номер (GUID) и список методов, но не содержащая в себе их реализацию. Для реализации создается отдельный класс, в родителях которого прописывается данный интерфейс. Интерфейс, также, не содержит свойств.
Итак, в секции type объявим интерфейс
1 2 3 4 5 6 |
type ... IMyInterface=interface(IUnknown) ['{CAD11BF9-BEFD-4838-A56E-BB6BF40EE513}'] procedure SayHello(); stdcall; end; |
Номер [‘{CAD11BF9-BEFD-4838-A56E-BB6BF40EE513}’] генерируется автоматически после нажатия на Ctrl+Shift+G. Он уникален и по расчету разработчиков алгоритма – никогда не повторится, ни на одном компьютере мира.
Объявим также в секции type класс, для реализации методов интерфейса
1 2 3 4 |
TMyClass=class (TInterfacedObject,IMyInterface) procedure SayHello(); stdcall; destructor Destroy(); override; // << Для демонстрации автоматизма удаления экземпляров после выполнения методов end; |
Реализуем методы класса, нажав на Ctrl+Shift+C
1 2 3 4 5 6 7 8 9 10 11 |
{ MyClass } destructor TMyClass.Destroy; begin showmessage('Automatical destroying instance MyClass'); inherited; end; procedure TMyClass.SayHello; begin showmessage('Hello from TMyClass'); end; |
И последний штрих – вызовем метод SayHello по клику на кнопку
1 2 3 4 5 6 7 |
procedure TForm1.Button1Click(Sender: TObject); var MyInterface:IMyInterface; begin MyInterface:=TMyClass.Create(); MyInterface.SayHello(); end; |
Обратите внимание ! Мы не удаляли экземпляр класса!!! Это особенность работы с интерфейсами. При создании экземпляра класса и присвоении его интерфейсной переменной MyInterface – так называемый счетчик ссылок увеличивается на единицу.
То есть, вот эта строка
1 |
MyInterface:=TMyClass.Create(); |
увеличила счетчик ссылок на единицу. Но об этом подробнее чуть позже. При определенных условиях счетчик уменьшается на единицу либо обнуляется.. При обнулении – экземпляр класса уничтожается.
Посмотрим, как это работает
При закрытии сообщения увидим вот это
Экземпляр класса удаляется у нас сам, после того, как отработал метод.
Как завладеть временем жизни экземпляра класса интерфейса?
Если не нужно, чтобы экземпляр класса создавался и удалялся по 100 раз при вызове методов, можно управлять временем его жизни. Создать его 1 раз и удалить тогда когда нужно.
Нужно сделать 2 изменения в предыдущем коде – при объявлении класса, поддерживающего интерфейс
Первое изменение
1 2 3 4 |
TMyClass=class (TComponent,IMyInterface) // << TComponent вместо TInterfacedObject procedure SayHello(); stdcall; destructor Destroy(); override; end; |
Второе изменение
1 2 3 4 5 6 7 |
procedure TForm1.Button1Click(Sender: TObject); var MyInterface:IMyInterface; begin MyInterface:=TMyClass.Create(Self); // << Прописать владельца экземпляра MyInterface.SayHello(); end; |
В данном случае удаление экземпляра произойдет вместе, с удалением владельца. Попробуем закрыть форму.
Как работают счетчики ссылок?
Выше мы объявляли свой интерфейс и поддерживающий его класс вот так…
1 2 3 4 |
IMyInterface=interface(IUnknown) ['{CAD11BF9-BEFD-4838-A56E-BB6BF40EE513}'] procedure SayHello(); stdcall; end; |
1 2 3 4 |
TMyClass=class (TInterfacedObject,IMyInterface) procedure SayHello(); stdcall; destructor Destroy(); override; // << Для демонстрации автоматизма удаления экземпляров после выполнения методов end; |
Если немного покопаться, то IUnknown находится в модуле System и имеет 3 метода
1 2 3 4 5 6 7 8 |
IInterface = interface ['{00000000-0000-0000-C000-000000000046}'] function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall; function _AddRef: Integer; stdcall; function _Release: Integer; stdcall; end; ... IUnknown = IInterface; // <<<<<< |
_AddRef – добавляет счетчик ссылок
_Release уменьшает счетчик ссылок
Querylnterface возвращает указатель на интерфейс объекта по его идентификатору
Немного поэкспериментируем
Если объявить 2 метода SayHello и SayHello2 и вызвать их подряд, то получим следующее
1 2 3 4 5 6 7 8 9 10 |
procedure TForm1.Button1Click(Sender: TObject); var MyInterface:IMyInterface; begin MyInterface:=TMyClass.Create(); MyInterface.SayHello(); MyInterface.SayHello2(); // <<После этого метода произойдет обнуление //счетчика ссылок и удаление экземпляра объекта end; |
Если вызвать методы в разных кнопках, то экземпляр не удаляется даже после вызова 2 метода, а ждет пока не отработает программа
Ну и так далее. Думаю это поле для экспериментов в реальных задачах.
Как уничтожить экземпляр класса, создаваемый для интерфейса?
Ранее, мы объявляли интерфейсную переменную
1 |
var MyInterface:IMyInterface; |
Чтобы уничтожить экземпляры класса, рожденные для этой переменной, достаточно занулить ссылку
1 |
MyInterface:=nil; |