Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
ООП / ООП_Лекции.doc
Скачиваний:
55
Добавлен:
08.06.2015
Размер:
1.03 Mб
Скачать

9. Работа с com-объектами, использование серверов ole-автоматизации

Интерфейсы играют главную роль в технологии COM (Component Object Model - компонентная модель объектов) и связанных с ней тех­нологиях удаленного доступа, т.е. технологиях доступа к объектам, рас­положенным (и выполняющимся) на другой машине. Их основная зада­ча - описать свойства, методы и события удаленного объекта в терми­нах машины клиента, т.е. на используемом при разработке клиентского приложения языке программирования. С помощью интерфейсов про­грамма клиента обращается к удаленному объекту так, как если бы он был ее собственным объектом.

Интерфейсы представляют собой частный случай описания типов. Они объявляются с помощью зарезервированного слова interface. На­пример:

type

IEdit = interface

procedure Copy; stdcall;

proced ure Cut; stdcall;

procedure Paste; stdcall;

function Undo: Boolean; stdcall;

end;

Такое объявление эквивалентно описанию абстрактного класса в том смысле, что провозглашение интерфейса не требует расшифровки объ­явленных в нем свойств и методов.

В отличие от классов интерфейс не может содержать поля и, следо­вательно, объявляемые в нем свойства в разделах readиwriteмогут ссылаться только на методы. Все объявляемые в интерфейсе члены размешаются в единственной секцииpublic. Методы не могут быть абст­рактными (abstract), виртуальными (virtual), динамическими (dynamic) или перекрываемыми (override). Интерфейсы не могут иметь конструкторов или деструкторов, т.к. описываемые в них методы реали­зуются только в рамках поддерживающих их классов, которые называ­ются интерфейсными.

Если какой-либо класс поддерживает интерфейс, имя этого интер­фейса указывается при объявлении класса в списке его родителей:

TEditor = class(TInterfacedObject, lEdit)

procedure Copy; stdcall;

procedure Cut; stdcall;

procedure Paste; stdcall;

function Undo: Boolean; stdcall;

end;

В отличие от обычного класса интерфейсный класс может иметь бо­лее одного родительского интерфейса:

type

IMylnterface = interface

procedure Delete; stdcall;

end;

TMyEditor = class(TInterfacedObiect, lEdit, IMylnterface)

procedure Copy; stdcall;

procedure Cut; stdcall;

procedure Paste; stdcall;

function Undo: Boolean; stdcall;

procedure Delete; stdcall;

end;

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

IPaint = interface

procedure CirclePaint(Canva: TCanvas; X,Y,R: Integer);

procedure RectPaint(Canva: TCanvas; X1,Y1,X2,Y2: Integer);

end;

и использующий его интерфейсный класс

TPainter = class(TInterfacedObject, IPaint)

procedure CirclePaint(Canva: TCanvas; X,Y,R: Integer);

procedure RectPaint(Canva: TCanvas; X1,Y1,X2,Y2: Integer);

end;

то в разделе implementation следует указать реализацию методов:

procedure TPainter.CirclePaint(Canva: TCanvas; X,Y,R: Integer);

begin

with Canva do

Ellipse(X, Y, X+2*R, Y+2*R);

end;

procedure TPainter.RectPaint(Canva: TCanvas; X1,Y1,X2,Y2: Integer);

begin

with Canva do

Rectangle(XI, Yl, X2, Y2)

end;

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

procedure TForml.PaintBoxlPaint(Sender: TObject);

var

Painter: IPaint;

begin

Painter := TPainter.Create;

Painter.CirclePaint (PaintBoxl .Canvas, 10, 0,10) ;

Painter.RectPaint(PaintBoxl.Canvas,40,0,60,20);

end;

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

type

TPainter = class(TInterfacedObject, IPaint)

end;

было бы ошибкой: компилятор потребовал бы вставить описание мето­дов CirclePaint и RectPaint.

Методы интерфейсного класса могут исполняться под именами, от­личными от имен методов интерфейса. Для этого при описании интер­фейсного класса за именем интерфейсного метода ставится знак равен­ства и имя метода, который будет вызываться вместо него:

TPainter = class(TInterfacedObject, IPaint)

procedure IPaint.CirclePaint = CPaint;

procedure IPaint.RectPaint = RPaint;

procedure CPaint(Canva: TCanvas; X,Y,R: Integer);

procedure RPaint(Canva: TCanvas; X1,Y1,X2,Y2: Integer);

end;

Подобно тому как все классы в Object Pascal порождены от единст­венного родителя TObject, все интерфейсные объекты порождены от общего предка TInterfacedObject. Этот предок умеет распределять память для интерфейсных объектов и использует глобальный интерфейс lUnknow:

type

TInterfacedObject = class(TObject, lUnknown)

private

FRefCount: Integer;

protected

function Querylnterface(const IID: TGUID; out Obj): Integer; stdcall;

function _AddRef: Integer; stdcall;

function _Release: Integer; stdcall;

public

property RefCount: Integer read FRefCount;

end;

Если бы в предыдущем примере класс TPainter был описан так:

TPainter = class(IPaint)

procedure CirclePaint (Canva: TCanvas; X,Y,R: Integer);

procedure RectPaint(Canva: TCanvas; X1,Y1,X2,Y2: Integer);

end;

компилятор потребовал бы описать недостающие методы Querylnterf асе, _Add и _Release класса TInterfacedObject. Поле FRefCount этого класса служит счетчиком вызовов интерфейсного объ­екта и используется по принятой в Windows схеме: при каждом обраще­нии к методу Add интерфейса lUnknow счетчик наращивается на едини­цу, при каждом обращении к Release - на единицу сбрасывается. Когда значение этого поля становится равно 0, интерфейсный объект уничто­жается и освобождается занимаемая им память.

На заметку. Если интерфейс предполагается использовать в технологиях COM/DCOM или CORBА, его методы должны опи­сываться с директивой stdcall или (для объектов Автоматиза­ции) safecall.

К интерфейсному объекту можно применить оператор приведения типов as чтобы использовать нужный интерфейс:

procedure PaintObjects(P: TInterfacedObject)

var

X: IPaint;

begin

try

X := P as IPaint;

X.CirclePaint(PaintBoxl.Canvas,0,0,20)

except

ShowMessage('Объект не поддерживает интерфейс IPaint')

end

end;

Встретив такое присваивание, компилятор создаст код, с помощью которого вызывается метод Querylnterface интерфейса lUnknow с требо­ванием вернуть ссылку на интерфейс IPaint. Если объект не поддержи­вает указанный интерфейс, возникает исключительная ситуация.

Интерфейсы, рассчитанные на использовании в удаленных объектах, должны снабжаться глобально-уникальным идентификатором (GUID). Например:

IPaint = interface

['{A4AFEB60-7705-11D2-8B41-444553540000} ' ]

procedure CirclePaint(Canva: TCanvas; X,Y,R: Integer);

procedure RectPaint(Canva: TCanvas; X1,Y1,X2,Y2: Integer);

end;

Глобально-уникальные идентификаторы создаются по специальной технологии, гарантирующей ничтожно малую вероятность того, что два GUID совпадут. Эта технология включена в Windows 32: чтобы полу­чить GUID для вновь созданного интерфейса в среде Delphi, достаточно нажать клавиши Ctrl+Shift+G. Для работы с GUID в модуле System объ­явлены следующие типы:

type

PGUID = ^TGUID;

TGUID = record

Dl: LongWord;

D2: Word;

D3: Word;

D4: array [0..7] of Byte;

end;

Программист может объявлять типизированные константы типа TGUID, например:

const IID_IPaint: TGUID=['{A4AFEB61-7705-11D2-8B41-444553540000} ' ];

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

procedure Paint(const IID: TGUID);

…..

Paint (IPaint);

Paint(IIDPaint);

С помощью зарезервированного слова implements программист мо­жет делегировать какому-либо свойству некоторого класса полномочия интерфейса. Это свойство должно иметь тип интерфейса или класса. Если свойство имеет тип интерфейса, имя этого интерфейса должно указываться в списке родителей класса, как если бы это был интерфейс­ный класс:

type

IMylnterface = interface

procedure P1;

procedure P2;

end;

TMyClass = class(TObject, IMylnterface)

FMylnterface: IMylnterface;

property Mylnterface: IMylnterface

read FMylnterface implements IMylnterface;

end;

Обратите внимание: в этом примере класс TMyClass не является ин­терфейсным классом, т.е. классом, в котором исполняются методы Р1 и Р2. Однако если из него убрать определение уполномоченного свойства MyInterface, он станет интерфейсным и в нем должны быть описаны ме­тоды интерфейса IMyInterface.

Уполномоченное свойство обязательно должно иметь часть read. Ес­ли оно имеет тип класса, класс, в котором оно объявлено, не может иметь других уполномоченных свойств.

ОБЪЕКТЫ АВТОМАТИЗАЦИИ И ИНТЕРФЕЙС IDISPATCH

В технологии OLE активно используются так называемые объекты Автоматизации (Automation objects}. Эти объекты представляют собой экземпляры интерфейсных классов, родительским интерфейсом кото­рых является специальный интерфейс IDispatch. Отличительной осо­бенностью IDispatch является то обстоятельство, что методы объекта Автоматизации никогда не вызываются напрямую, но всегда - с помо­щью метода Invoke интерфейса IDispatch. Управление объектами СОМ с помощью выполнения методов IDispatch называется маршализацией (marshaling).

Для объявления класса Автоматизации используется специальное за­резервированное слово dispinterface, а перечисляемые в нем методы и свойства должны снабжаться целочисленными идентификаторами, которые вставляются в конце описания методов (свойств) после заре­зервированных слов dispid:

type

IStringsDisp = dispinterface

['{EE05DFE2-5549-11DO-9EA9-0020AF3D82DA}']

property Control-Default [Index: Integer]: OleVariant dispid 0; default;

function Count: Integer; dispid 1;

property Item[Index: Integer]: OleVariant dispid 2;

procedure Remove(Index: Integer); dispid 3;

procedure Clear; dispid 4;

function Add(Item: OleVariant): Integer; dispid 5;

function _NewEnum: lUriknown; dispid -4;

end;

В отличие от обычного интерфейсного класса класс Автоматизации не может иметь родительского класса, и поэтому за словом dispinterface нельзя указать список родителей. Идентификаторы ме­тодов (свойств) должны быть уникальными в пределах объявления класса. Все возвращаемые функциями и свойствами результаты, а также все параметры обращения к методам должны иметь один из следующих типов: Byte, Currency, Real, Double, Longlnt, Integer, Single, Smallint, AnsiString, WideString, TDateTime, Variant, OleVariant, WordBool или любой интерфейсный тип. За исключением директивы default, кото­рую можно указать для свойства-массива, никакие другие директивы доступа в объявлении методов и свойств не допускаются.

Для доступа к объектам Автоматизации используются переменные типа вариант. Инициация такой переменной осуществляется вызовом функции CreateOleObject, определенной в модуле ComObj. Эта функция возвращает ссылку на интерфейс IDispatch, с помощью которой можно обращаться к методам и свойствам класса Автоматизации так, как если бы они были методами и свойствами варианта. Например:

Uses ComObj;

var

Word: Variant;

begin

Word := CreateOleObject('Word.Basic');

Word. FileNewC Normal');

Word.Insert('Первая строка'#13);

Word.Insert('Вторая строка'#13);

Word.FileSaveAs('с:\temp\test.txt', 3) ; // 1 – Doc-файл

end;

Параметром обращения к CreateOleObject является имя сервера Ав­томатизации, которое должно быть предварительно зарегистрировано в реестре Windows 32. Характерно, что методы сервера не известны на этапе компиляции программы, поэтому компилятор никак не контроли­рует правильность их вызовов. Названия методов не подчиняются пра­вилам построения идентификаторов Delphi, и в них могут использовать­ся символы национальных алфавитов.

Передаваемые методам параметры могут быть позиционными и именованными. Позиционные параметры являются обычными для под­программ Object Pascal параметрами-значениями. Именованные пара­метры записываются в виде

Имя_параметра := Значение

Например, при обращении к методу FileSaveAs (см. выше) были исполь­зованы два позиционных параметра. Это же обращение можно было бы записать с использованием именованных параметров следующим обра­зом:

Word.FileSaveAs(Format := 3, Name := 'c:\temp\test.txt');

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

Интерфейсы-наследники от IDispatch называются дуальными (двойственными), так как они описываются статически на этапе компи­ляции сервера, а связываются с клиентом динамически на этапе прогона программы. Методы дуальных интерфейсов (кроме унаследованных от lUnknow и IDispatch) должны компилироваться в режиме safecall.

83

Соседние файлы в папке ООП