Главная » Статьи » Компы! » Програмирование! |
Введение COM (Component Object Model) — модель объектных компонентов — одна из основных технологий, на которых основывается Windows. Более того, все новые технологии в Windows (Shell, Scripting, поддержка HTML и т.п.) реализуют свои API именно в виде COM-интерфейсов. Таким образом, в настоящее время профессиональное программирование требует понимания модели COM и умения с ней работать. В этой главе мы рассмотрим основные понятия COM и особенности их поддержки в Delphi.
Базовые понятия
Интерфейс Программист обязуется реализовать все методы, описанные в интерфейсе, и следовать требованиям, предъявляемым к реализации некоторых их них. Компилятор обязуется создать в программе внутренние структуры, позволяющие обращаться к методам этого интерфейса из любого поддерживающего те же соглашения средства программирования. Таким образом, COM является языково-независимой технологией и может использоваться в качестве «клея», соединяющего программы, написанные на разных языках. Объявление интерфейса включает в себя описание методов и их параметров, но не включает их реализации. Кроме того, в объявлении может указываться идентификатор интерфейса — уникальное 16-байтовое число, сгенерированное по специальным правилам, гарантирующим его статистическую уникальность (GUID — Global Unique Identifier). Интерфейсы могут наследоваться. Наследование интерфейсов — это декларация, указывающая, что унаследованный интерфейс должен включать в себя все методы предка. Таким образом, необходимо понимать следующее: Интерфейс не является классом. Класс может выступать реализацией интерфейса, но класс содержит код методов на конкретном языке программирования, а интерфейс — нет. Реализация интерфейса — это код, который реализует эти методы. При этом, за несколькими исключениями, не накладывается никаких ограничений на то, каким образом будет выглядеть реализация. Физически реализация представляет собой массив указателей на методы, адрес которого и используется в клиенте для доступа к COM-объекту. Любая реализация интерфейса имеет метод QueryInterface, позволяющий запросить ссылку на конкретный интерфейс из числа реализуемых.
Автоматическое управление памятью и подсчет ссылок
Объявление интерфейсов type
IUnknown type Последние два метода предназначены для реализации механизма подсчета ссылок. function _AddRef: Integer; stdcall; function _Release: Integer; stdcall; Первый метод позволяет получить ссылку на реализуемый классом интерфейс. function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall; возвращает ссылку на него в параметре Obj; В принципе, конкретная реализация может наполнить эти методы какой-либо другой, отличающейся от стандартной функциональностью, однако в этом случае интерфейс будет несовместим с моделью COM, поэтому делать этого не рекомендуется. В модуле System.pas объявлен класс TInterfacedObject, реализующий IUnknown и его методы. Рекомендуется использовать этот класс для создания реализаций своих интерфейсов. Кроме того, поддержка интерфейсов реализована в базовом классе TObject. Он имеет метод function TObject.GetInterface(const IID: TGUID; out Obj): Boolean; возвращает ссылку на него в параметре Obj; Таким образом, имеется возможность запросить у любого класса Delphi реализуемый им интерфейс. Подробнее использование этой функции рассмотрено ниже.
Реализация интерфейсов type Класс должен иметь методы, точно соответствующие по именам и спискам параметров всем методам всех объявленных в его заголовке интерфейсов. Рассмотрим более подробный пример. type procedure TForm1.Button1Click(Sender: TObject); Во-первых, оператор присваивания при приведении типа данных к интерфейсу неявно вызывает метод _AddRef. При этом количество ссылок на интерфейс увеличивается на единицу. Во-вторых, код не содержит никаких попыток освободить память, выделенную под объект TTest. Тем не менее, если выполнить эту программу, на экран будет выведено сообщение о том, что деструктор был вызван. Это происходит потому, что при выходе переменной, ссылающейся на интерфейс, за область видимости (либо при присвоении ей другого значения) компилятор Delphi генерирует код для вызова метода _Release, информируя реализацию о том, что ссылка на нее больше не нужна.
Внимание! Если у класса запрошен хотя бы один интерфейс — не вызывайте его метод Free (или Destroy). Класс будет освобожден тогда, когда отпадет необходимость в последней ссылке на его интерфейсы. Если вы к этому моменту уничтожили экземпляр класса вручную — произойдет ошибка доступа к памяти.
Так, следующий код приведет к ошибке в момент выхода из функции: var var При приведении типа объекта к интерфейсу вызывается метод _AddRef. Например, следующий код будет успешно откомпилирован, но при выполнении вызовет ошибку «Interface not supported»: var var
Реализация интерфейсов (расширенное рассмотрение) Объявим два интерфейса: type TTest2 = class(TInterfacedObject, ITest, ITest2) Если реализация методов TTest2.Beep1 и TTest2.Beep2 идентична, то можно не создавать два разных метода, а объявить класс следующим образом: TTest2 = class(TInterfacedObject, ITest, ITest2) type { TBeeper } Обращаться к полученному классу можно точно так же, как и к любому классу, реализующему интерфейсы: var Интерфейсы и TComponent type
Использование интерфейсов внутри программы В качестве примера рассмотрим MDI-приложение, имеющее много различных форм и единую панель инструментов. Предположим, что на этой панели инструментов имеются команды «Сохранить», «Загрузить» и «Очистить», однако каждое из окон реагирует на эти команды по-разному. Создадим модуль с объявлениями интерфейсов: unit ToolbarInterface; Создадим три дочерние формы — Form2, Form3 и Form4 — и установим им свойство FormStyle = fsMDIChild. Form2 умеет выполнять все три команды: type type На главной форме приложения поместим ActionList и создадим три компонента TAction. Кроме того, разместим на ней TToolBar и назначим ее кнопкам соответствующие TAction. type procedure TForm1.ActionList1Update(Action: TBasicAction; procedure TForm1.acLoadExecute(Sender: TObject); Того же эффекта можно добиться и другими методами (например, унаследовав все дочерние формы от единого предка либо обмениваясь с ними сообщениями), однако эти методы имеют ряд существенных недостатков. Так, при обмене сообщениями мы теряем строгую типизацию и вынуждены передавать параметры через целые числа, а при визуальном наследовании мы привязываем себя к родительскому классу, что не всегда удобно. К тому же можно определить множество интерфейсов и реализовывать в каждой из дочерних форм лишь необходимые, а в случае наследования все формы должны будут реализовывать все общие методы.
Использование интерфейсов для реализации Plug-In В качестве примера реализуем несложную программу, использующую Plug-In для загрузки данных. Объявим интерфейсы модуля расширения и внутреннего API программы. unit PluginInterface; Plug-In представляет собой DLL, экспортирующую функцию CreateFilter, возвращающую ссылку на интерфейс ILoadFilter. Главный модуль сначала должен вызвать метод Init, передав в него имя файла и ссылку на интерфейс внутреннего API, а затем вызывать метод GetNextLine до тех пор, пока он не вернет FALSE. Рассмотрим код модуля расширения: library ImpTxt; function TTextFilter.GetNextLine(var S: String): Boolean; destructor TTextFilter.Destroy; function CreateFilter: ILoadFilter; exports type type Memo1.Lines.Clear; <расширение> = <имя модуля>, например: [Filters] hPlugIn := LoadLibrary(PChar(PluginName)); Filter := CreateFilter; while Filter.GetNextLine(S) do finally finally Преимущества данного способа становятся особенно очевидными при реализации сложных модулей расширения, интерфейс с которыми состоит из многих методов.
Внимание! Поскольку в EXE и DLL используются длинные строки, не забудьте включить в список uses обоих проектов модуль ShareMem. Другим вариантом решения проблемы передачи строк является использование типа данных WideString. Для них распределением памяти занимается OLE, причем делает это независимо от модуля, из которого была создана строка. | |
Просмотров: 806 | Рейтинг: 4.0/1 |
Всего комментариев: 0 | |
Наш опрос |
Статистика |
Онлайн всего: 1 Гостей: 1 Пользователей: 0 |
Поиск |
Друзья сайта |
|