
- •Литература
- •Содержание
- •1. Основные понятия ооп
- •2. Программирование для Windows
- •3. Визуальное программирование и среда Delphi
- •Создание приложений в средеDelphi
- •Компоненты общего назначения tMainMenu- главное меню формы (программы)
- •TPopupMenu- вспомогательное (локальное) меню
- •TLabel- метка для отображения текста
- •TEdit-ввод и отображение строки
- •TMemo- ввод и отображение текста
- •TButton- кнопка
- •TBitBtn- кнопка с изображением
- •TSpeedButton- кнопка для инструментальных панелей
- •TCheckBox- независимый переключатель
- •TRadioButton- зависимые переключатели
- •TRadioGroup- группа зависимых переключателей
- •TListBox- список выбора
- •TComboBox- раскрывающийся список выбора
- •TPanel- панель
- •TTabControl- набор закладок
- •TPageControl- набор страниц с закладками
- •4. Особенности языка ооп Object Pascal
- •Процедуры и функции Выход из процедур и фукнций и возврат результата
- •Передача параметров
- •Параметры со значениями по умолчанию
- •Перегрузка функций
- •Динамическое распределение памяти
- •Указатели
- •Операции с указателями
- •Операция @
- •Самоадресуемые записи
- •Динамические массивы Одномерные динамические массивы
- •Многомерные динамические массивы
- •Исключения и их обработка
- •Защита кода зачистки в блокахtry...Finally
- •Защита кода зачистки на уровне модуля — разделfinalization
- •Обработка исключений в блокахtry...Except
- •Последовательность обработки исключений
- •5. Классы и объекты
- •Классы и объекты
- •Инкапсуляция
- •Наследование
- •Полиморфизм
- •Составляющие класса Поля
- •Одноименные методы
- •Свойства
- •События
- •Объявление класса
- •Операции с классами
- •Ссылки на классы
- •TObjectи информация о классах
- •Регистрация и обнаружение классов
- •6. Создание объектов во время выполнения, поиск компонентов
- •Клонирование объектов
- •Поиск компонента
- •Двукратное освобождение объекта
- •7. Построение собственных компонентов
- •Как и для чего следует строить компоненты
- •Общие руководящие принципы
- •Ключевые идеи
- •Компиляция компонентов
- •Отладка компонентов
- •Примечание
- •Примечание
- •Примечание
- •8. Работа с потоками
- •Классы потоковDelphi
- •Файловые потоки
- •Примечание
- •Методы потоков в действии: программаMemoStr
- •Потоки памяти
- •Пример программыMemoryS
- •Примечание
- •Написание заказного класса потока
- •9. Работа с com-объектами, использование серверов ole-автоматизации
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.