Delphi и COM


         

Реализация интерфейсов


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

type TMyClass = class(TComponent, IMyInterface, IDropTarget)     // Реализация методов   end;

Класс TMyClass реализует интерфейсы IMyInterface и IDropTarget. Необходимо понимать, что реализация классом нескольких интерфейсов не означает множественного наследования и вообще наследования класса от интерфейса. Указание интерфейсов в описании класса означает только то, что в данном классе реализованы все эти интерфейсы.

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

Рассмотрим более подробный пример.

type   ITest = interface   ['{61F26D40-5CE9-11D4-84DD-F1B8E3A70313}']     procedure Beep;   end;   TTest = class(TInterfacedObject, ITest)     procedure Beep;     destructor Destroy; override;   end;   …

  procedure TTest.Beep; begin   Windows.Beep(0,0); end;   destructor TTest.Destroy; begin   inherited;   MessageBox(0, 'TTest.Destroy', NIL, 0); end;

Здесь класс TTest реализует интерфейс ITest. Рассмотрим использование интерфейса из программы.

procedure TForm1.Button1Click(Sender: TObject); var   Test: ITest; begin   Test := TTest.Create;   Test.Beep; end;

Поскольку данный код выглядит довольно странно, остановимся на нем подробнее.

Во-первых, оператор присваивания при приведении типа данных к интерфейсу неявно вызывает метод _AddRef. При этом количество ссылок на интерфейс увеличивается на единицу.

Во-вторых, код не содержит никаких попыток освободить память, выделенную под объект TTest. Тем не менее, если выполнить эту программу, на экран будет выведено сообщение о том, что деструктор был вызван. Это происходит потому, что при выходе переменной, ссылающейся на интерфейс, за область видимости (либо при присвоении ей другого значения) компилятор Delphi генерирует код для вызова метода _Release, информируя реализацию о том, что ссылка на нее больше не нужна.

Внимание! Если у класса запрошен хотя бы один интерфейс — не вызывайте его метод Free (или Destroy). Класс будет освобожден тогда, когда отпадет необходимость в последней ссылке на его интерфейсы. Если вы к этому моменту уничтожили экземпляр класса вручную — произойдет ошибка доступа к памяти.

Так, следующий код приведет к ошибке в момент выхода из функции:

var   Test: ITest;   T: TTest; begin   T := TTest.Create;   Test := T;   Test.Beep;   T.Free; end;   // в этот момент произойдет ошибка

Если вы хотите уничтожить реализацию интерфейса немедленно, не дожидаясь выхода переменной за область видимости, – просто присвойте ей значение NIL:

var   Test: ITest;   T: TTest; begin   T := TTest.Create;   Test := T;   Test.Beep;   Test := NIL;  // Неявно вызывается IUnknown._Release; end;

Обратите особое внимание, что вызовы методов интерфейса IUnknown осуществляются Delphi неявно и автоматически. Поэтому не вызывайте методы интерфейса IUnknown самостоятельно. Это может нарушить нормальную работу автоматического подсчета ссылок и привести к неосвобождению памяти либо к нарушениям защиты памяти при работе с интерфейсами. Во избежание этого  необходимо просто помнить следующее.

  1. При приведении типа объекта к интерфейсу вызывается метод _AddRef.
  2. При выходе переменной, ссылающейся на интерфейс, за область видимости либо при присвоении ей другого значения вызывается метод _Release.
  3. Единожды запросив у объекта интерфейс, в дальнейшем вы не должны освобождать объект вручную. Вообще начиная с этого момента лучше работать с объектом только через интерфейсные ссылки.

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

Например, следующий код будет успешно откомпилирован, но при выполнении вызовет ошибку «Interface not supported»:

var   Test: ITest; begin   Test := TInterfacedObject.Create as ITest;   Test.Beep; end;

В то же время код

var   Test: ITest; begin   Test := TTest.Create as ITest;   Test.Beep; end;

будет успешно компилироваться и выполняться.



Содержание раздела