Шаг 21.
Назначение и принципы COM-технологии. Реализация интерфейсов

    На этом шаге мы рассмотрим реализацию интерфейсов в Delphi.

    На этом шаге речь пойдет о реализации интерфейсов. Интерфейсы, как правило, реализуются в COM-серверах. В COM-клиентах иногда реализуются только потомки интерфейса IDispatch для приема нотификационных сообщений с сервера и IAdviceSink. Что касается других интерфейсов, то СОМ-клиент получает их указатели с сервера и для работы с ними достаточно только объявить интерфейс.

    Интерфейсы в Delphi реализуются через классы. На уровне класса TObject определен метод GetInterface, который возвратит указатель на интерфейс (если он поддерживается). На уровне класса TComponent реализована поддержка IUnknown - определены методы AddRef, Release и QueryInterface. Любой компонент Delphi поддерживает IUnknown. Прямой потомок TObject - класс TInterfacedObject - поддерживает IUnknown. Реализацию интерфейса обычно производят в TInterfacedObject или в других объектах, где отчасти реализованы методы интерфейсов. Реализация методов определенных в IMyDataExport выглядит следующим образом:

interface
.   .   .   .   .
type 
  TMyData=class(TInterfacedObject, IMyDataExport)
  FData:integer;
  procedure GetMyData(var N:integer);
  procedure SetMyData(N:integer);
 end;
.   .   .   .   .
implementation
.   .   .   .   .
procedure TMyData.GetMyData(var N:integer);
begin 
   N:=FData;
end;

procedure TMyData.SetMyData(N:integer);
begin
   FData:=N;
end;

    Определение класса TMyData напоминает двойное наследование в C++. Запись TMyData=class(TInterfacedObject, IMyDataExport) означает, что в классе TMyData обязаны быть реализованы методы IMyDataExport (помимо любых других методов, которые разработчик решит поместить в этот класс). Если попытаться при определении класса TMyData убрать определение процедур GetMyData или SetMyData, то компилятор Delphi сообщит об ошибке.

    В этом примере нигде не реализуются методы IUnknown. Причина - они уже были реализованы в классе TInterfacedObject. Если бы за основу реализации IMyDataExport был взят не TInterfacedObject, а, например, TObject, то мы были бы обязаны реализовать методы IUnknown - AddRef, Release и QueryInterface.

    Разрешается указывать несколько интерфейсов в списке при определении класса, где реализуются интерфейсы. Например, запись

    TMySecond=class(TInterfacedObject, IMyDataExport, IDispatch)

означает, что в данном классе реализуются два интерфейса - IMyDataExport и IDispatch (помимо IUnknown). Если два интерфейса имеют общие методы, то их надо реализовать всего один раз. Если же необходимо для двух интерфейсов по-разному реализовать общие методы, то используется следующая конструкция:

type
   IMySameMethod = interface(IUnknown)
     ['{28359D80-7CBD-11D2-AC75-00605207DE1D}']
   procedure GetMyData(var N:integer); 
end;

TDifferentMethod = class(TInterfacedObject,IMyDataExport,IMySameMethod)
 FData:integer;
 procedure IMySameMethod.GetMyData=SameMethodGetData;
 procedure GetMyData(var N:integer);
 procedure SetMyData(N:integer);
 procedure SameMethodGetData(var N:integer);
end;

то есть процедура с новым именем относится к методу интерфейса. Соответственно в определении этой процедуры списки параметров обязаны быть теми же самыми, что и в методе интерфейса.

    Директива implements позволяет использовать интерфейс, реализованный в каком-либо классе, для реализации интерфейса в другом классе. Например, при реализации класса TDifferentMethod можно было бы воспользоваться уже готовой реализацией класса TMyData:

type
  TDifferentMethod = class(TInterfacedObject,IMyDataExport,IMySameMethod)
  FData:integer;
  FDataExport:TMyData;
  property DataExport:TMyData read FDataExport implements MyDataExport;
  procedure IMySameMethod.GetMyData=SameMethodGetData;
  procedure SameMethodGetData(var N:integer);
 end;

    В этом случае уже не требуется реализовывать методы IMyDataExport: их реализация берется из объекта FDataExport.

    В Delphi уже имеется ряд классов, реализующих интерфейсы, необходимые для создания стандартных СОМ-объектов - серверов автоматизации элементов управления ActiveX:

    TComObject  -> TTypedComObject->
    TAutoObject -> TActiveXControl

    Поскольку интерфейсы, реализуемые в этих объектах, могут быть затребованы другими модулями, каждому из них необходима своя фабрика класса. Эти фабрики класса тоже определены и реализованы в классах:

    TComObjectFactory  -> TTypedComObjectFactory ->
    TAutoObjectFactory -> TActiveXControlFactory

    Класс TComObject реализует два интерфейса - IUnknown и ISupportErrorInfo. Последний применяется для передачи информации об ошибках COM-клиенту. Этот класс используется экспертом Delphi, когда разработчик выбирает команду File | New | Other... | ActiveX | COM Object. В классе TTypedComObject добавляется поддержка интерфейса IProvideClassInfo, который имеет единственный добавленный метод (по сравнению с IUnknown) - GetClassInfo. Этот метод возвращает указатель на интерфейс ITypeInfo, который, как уже говорилось, используется для получения информации о библиотеке типов. Начиная с этого класса уже можно описывать COM-объекты с библиотеками типов. В следующем классе TAutoObject - добавляется реализация IDispatch. Именно этот класс используется экспертом Delphi при выборе команды File | New | Other... | ActiveX | Automation Object. И, наконец, в классе TActiveXControl добавляется реализация интерфейсов IConnectionPointContainer, IDataObject, IObjectSafety, IOleControl, IOleInPlaceActiveObject, IOleInPlaceObject, IOleObject, IPerPropertyBrowsing, IPersistPropertyBag, IPersistStorage, IPersistStreamInit, IQuickActivate, ISimpleFrameSite, ISpecifyPropertyPages, IViewObject, IViewObject2, необходимых для поддержки элементов управления ActiveX.

    И, наконец, следует добавить, что интерфейс можно реализовывать в любом классе Delphi. Это дает потрясающую гибкость в реализации кода. Часто встречается ситуация, когда в потомках различных классов требуете выполнить одинаковые действия. Наличие в каком-нибудь общем классе предке подходящего виртуального метода с подходящим списком параметров - заветная мечта практически каждого разработчика. Так вот, в Delphi эта мечта осуществляется.

    Приведем пример приложения, иллюстрирующего использование интерфейсов.

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls;

type
  IColor=interface
    ['{74867740-7C9D-11D2-AC75-00605207DE1D}']
    procedure SetColor(C:TColor);
  end;
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

TMyLabel=class(TLabel,IColor)
 procedure SetColor(C:TColor);
end;

TMyEdit=class(TEdit,IColor)
 procedure SetColor(C:TColor);
end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

{TMyLabel}
procedure TMyLabel.SetColor(C:TColor);
begin
  Color:=C;
end;

{TMyEdit}
procedure TMyEdit.SetColor(C:TColor);
begin
  Color:=C;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  with TMyLabel.Create(Self) do
      begin
        Parent:=Self;
        Left:=5;
        Top:=5;
        Width:=100;
        Height:=25;
        Caption:='Label';
      end;
  with TMyEdit.Create(Self) do
      begin
        Parent:=Self;
        Left:=5;
        Top:=45;
        Width:=100;
        Height:=25;
        Text:='Edit';
      end;
end;

procedure TForm1.Button1Click(Sender: TObject);
var I:Integer;
    IC:IColor;
begin
  for I:=0 to ComponentCount-1 do
     begin
       Components[I].GetInterface(IColor,IC);
       if Assigned(IC) then IC.SetColor(clGreen);
     end;
end;

end.
Текст этого приложения можно взять здесь (6,7 Кб).

    Результат работы приложения изображен на рисунке 1:


Рис.1. Результат работы приложения

    В данной программе в двух различных компонентах VCL реализуется один и тот же интерфейс IColor. В обработчике события Button1Click эти компоненты используются так, как если бы у них был общий метод SetColor. Программа в данном виде неработоспособена в более ранних версиях Delphi, так как она требует, чтобы каждый интерфейс, который имеет свой IID, имел бы фабрику класса и был бы зарегистрирован в системном реестре. При попытке вызвать метод GetInterface произойдет исключение.

    Однажды реализованные и выпущенные в свет в составе какого-либо COM-сервера интерфейсы не могут быть изменены ни при каких обстоятельствах! Если кто-то где-то создаст клиента, который использует реализованные в данном COM-сервере интерфейсы, то такие изменения моментально приведут к потере его работоспособности. Для добавления новых методов следует создать потомка данного интерфейса с другим IID и добавить к нему методы. Переопределять уже имеющиеся методы категорически воспрещается! Такие "расширенные" интерфейсы принято называть тем же именем, которое имеет и основной интерфейс, с добавлением номера версии как окончания имени. Именно благодаря добавлениям новых методов появились интерфейсы IClassFactory2, ICreateTypeInfo2 и др.

    Со следующего шага мы начнем рассматривать использование OLE-документов в приложениях.




Предыдущий шаг Содержание Следующий шаг