Если постараться свести воедино все известные мнени о спецификации Component Object Model (COM, объектна модель программных компонентов) фирмы Microsoft, то обнаруживается одна явная закономерность: что бы вы не попытались сделать, все оказывается непросто. И этому выводу приходят практически все, кто приступает к разработке COM-объектов с помощью имеющихся на сегодняшний день инструментальных средств. Однако если вы лишь новичок на этом поприще, то бояться не стоит: уже появилось множество программных продуктов, которые смогут облегчить вашу жизнь.
Главная причина большинства затруднений при работе с COM заключается в необходимости выполнения множества рутинных операций по учету. Заниматься этим не любит ни один нормальный программист, но жизнь заставляет. В системном Реестре Windows должны отслеживаться сведени по каждому COM-объекту, и разработчику, как правило, самому приходится заботиться об этом.
Любому COM-объекту должен быть поставлен в соответствие некоторый уникальный идентификатор класса (CLSID). Если же дело касается нового интерфейса (не заданного в спецификации Microsoft), то для него также приходится выделять свой уникальный номер IID (interface identifier). Оба названных параметра входят в число идентификаторов GUID (Globally Unique Identifiers - идентификаторы, уникальные в масштабах системы). Все эти значения, а также текстовые комментарии должны храниться в системном Реестре, что увеличивает объем работы программиста.
К сожалению, на этом проблемы не заканчиваются. Ссылки на все COM-объекты должны быть учтены в системе. В принципе это полезно, поскольку таким образом обеспечивается учет выделяемой для COM-объектов памяти. Однако при нынешней реализации COM вся нагрузка возлагается на нашего несчастного программиста, работа которого чревата ошибками - ведь приходится учитывать счетчики ссылок на свои собственные объекты, не говор уже о ссылках на объекты, на которые его объекты ссылаются сами!
Фактически наибольшую сложность в применении COM-объектов вызывает необходимость разбираться в немыслимом объеме мелких деталей - без этого нельз организовать обработку даже простейшего COM-объекта. Крайне важно автоматизировать решение некоторых из этих задач. Поэтому сейчас - в эпоху быстродействующих компьютеров, интеллектуальных компиляторов и развитых интегрированных сред разработки ПО - можно ожидать появления программных средств для упрощения названной задачи.
В определенном смысле это уже произошло.
Речь идет о новом инструментарии фирмы Microsoft дл построения COM-объектов, который называется ActiveX Template Library (ATL). Для того чтобы в нем разобраться, мы в этой статье сначала расскажем немного о самой библиотеке Microsoft Foundation Classes (MFC), а также о процессе подготовки COM-объекта. Далее речь пойдет о шаблонах Си++, и, в завершения, будет дана общая характеристика возможностей ATL. В одном из следующих выпусков этой рубрики мы рассмотрим реальную процедуру построения COM-объекта на базе ATL.
Пакет ATL версии 1.1 можно бесплатно получить через Internet с Web-сервера фирмы Microsoft по адресу: http://www.microsoft.com/visualc/v42/atl. Для работы также потребуется пакет Visual C++ 4.1 или более поздней версии.
Вначале давайте разберемся, что нового дл программиста, формирующего COM-объекты, дает использование такой программной надстройки, как MFC. Дело в том, что здесь доступ к интерфейсу IUnknown происходит через базовый класс CCmdTarget. Все обращения к различным интерфейсам COM-объекта перенаправляются соответствующим функциям обработки, точно так же как в CCmdTarget при обработке сообщений Windows.
Однако за удобство приходится платить: все классы в иерархической архитектуре MFC допускают лишь однократное наследование. Каждый COM-объект должен иметь один или несколько интерфейсов, что по форме идентично набору v-таблиц Си++. Конечно, лучше иметь возможность сразу создать несколько v-таблиц, использу свойство многократного наследования как встроенной характеристики языка Си++, ++ а не какой-то внешней оболочки (в ATL применяется аналогичный подход, который мы подробно рассмотрим позже). Однако в MFC при создании нескольких v-таблиц для отдельного COM-объекта используются вложенные классы. Фактически происходит следующее: внутри головного объекта Си++ создаютс другие объекты Си++, а MFC экспортирует их v-таблицы; в итоге головной объект соответствует вашему COM-объекту, а его интерфейсы указываются в названных v-таблицах. Подобную технологию приходится учитывать в любых объектно-ориентированных языках, где не предусмотрено многократное наследование, например в Delphi. Именно это стало причиной возникновения многочисленных макрокоманд - необходимых при работе с MFC, - которые усложняют вашу вроде бы простую программу. Сравните, как выглядят совершенно одинаковые COM-объекты на лист. 1 и 2. Где больше ясности?
Лист. 1. Пример простого COM-объекта, построенного средствами MFC.
class CValidate : public CCmdTarget
{
protected:
DECLARE_INTERFACE_MAP()
// Интерфейс IValidate
BEGIN_INTERFACE_PART (Validate, IValidate)
STDMETHOD(Foo);
END_INTERFACE_PART(Validate)
};
// Интерфейс IValidate
BEGIN_INTERFACE_MAP (CValidate, CCmdTarget)
INTERFACE_PART(CValidate, IID_IValidate, Validate)
END_INTERFACE_MAP()
STDMETHODIMP CValidate::XValidate::Foo()
{
METHOD_PROLOGUE(CValidate, Validate);
::MessageBox(NULL, "Hello World", NULL, MB_OK);
return S_OK;
}
Лист. 2. Пример простого COM-объекта, построенного обычными средствами C++.
class CValidate: public IUnknown
{
public:
HRESULT QueryInterface( REFIID iid, void** ppvObject )
ULONG AddRef();
ULONG Release();
virtual HRESULT Foo() { MessageBox( 0, 'hello world', NULL, MB_OK ); };
};
Как уже было сказано, в MFC для обработки обращений к COM-интерфейсам используется механизм, который по принципу своей работы аналогичен применяемому в CCmdTarget для обслуживания сообщений Windows. Если вы захотите ввести в производный от CCmdTarget класс обработку некоторого сообщения Windows, то в интегрированной среде Visual C++ это делается очень просто с помощью "мастеров". Class Wizard возьмет на себя все заботы о деталях создания карты привязки сообщений и их драйверов и о построении скелета функциональной части вашей программы. К сожалению, дл упрощения процедуры создания COM-объектов здесь нет каких-либо средств, хотя и есть инструмент дл генерации объектов OLE-автоматизации.
Для ясности на наших листингах не показано внутреннее наполнение функций из интерфейса IUnknown. Тем не менее даже это служит прекрасной иллюстрацией тех сложностей, с которыми придется ежедневно сталкиваться новичкам, а также умудренным опытом программистам. На лист. 1, где показан пример для MFC, видно, что для ввода прототипов наших функций используются макрокоманды STDMETHOD и STDMETHOD_, размещаемые между макрокомандами BEGIN_INTERFACE_PART и END_INTERFACE_PART. Почему нельзя записать проще, как на лист. 2, где дается такое же описание функций, как для любого другого класса Си++? Ведь на лист. 2 смысл функций проявляется гораздо яснее, не так ли?
Теперь давайте посмотрим, как записана функция Foo в примере для MFC. Видите - она находится под двум операторами вложенности "::". Дело в том, что в MFC, как мы уже отмечали, для возможности иметь несколько интерфейсов у одного COM-объекта приходитс использовать вложенные классы.
STDMETHODIMP Cvalidate::XValidate::Foo()
Макрокоманды BEGIN_INTERFACE_PART, END_INTERFACE_PART, BEGIN_INTERFACE_MAP и END_INTERFACE_MAP фактически дают определение новому классу под именем XValidate, имеющему интерфейс IValidate в рамках класса CValidate. Здесь также приходится использовать макрокоманды METHOD_PROLOGUE для получения указателя на наш экземпляр объекта CValidate. Конечно, подобная процедура не отличаетс особой ясностью. Именно в этом заключается наибольша сложность при работе с программами на MFC - первое и наиболее важное отличие MFC-модуля от модуля на Си++. Умение читать и составлять программы на MFC и Си++ требуют совершенно разных навыков.
Таким образом, явно чувствуется потребность в более ясной реализации программ для создания COM объектов, чем обеспечивается одними лишь средствами MFC. Именно для разрешения этой проблемы, а также ряда других появилась библиотека ActiveX Template Library.
Прежде чем знакомиться с библиотекой шаблонов ActiveX-элементов, давайте коротко рассмотрим, что же это такое - шаблоны (template). Благодаря этим конструкциям, которые прежде называли параметрически задаваемыми типами, разработчик получает возможность при создании классов указывать типы данных как параметры. Сказанное означает, что теперь можно конструировать обобщенные классы с гарантией соблюдени типов объектов. Не кажется ли вам, что эти два определения - "обобщенные" (generic) и "с гарантией соблюдения типов" (type-safe) - противоречат друг другу. На заре Си++, до появления шаблонов, это было действительно так. Однако теперь допускаетс существование новых видов типизированных классов, когда за основу берется обобщенный шаблонный класс и ему передаются в качестве параметров сведения о типах. Отныне классы могут быть одновременно обобщенными и с гарантией соблюдения типов.
Вероятно, о шаблонах вспоминают прежде всего благодаря возможности с их помощью формировать обобщенные классы для набора объектов. Однако дл подготовки связанных списков, например и для int-, и для float-элементов, причем с гарантией проверки их типов, программистам пришлось бы создавать несколько отдельных классов. Если же необходимо иметь отдельный обобщенный класс для некоторого набора объектов (аналогичный CObList для предыдущих версий MFC), то пришлось бы сформировать эти объекты с их реальными типами в хипе, а в CObList сохранять void-указатели на них. Подобный метод трудно назвать "гарантирующим соблюдение типов", поскольку подобная гарантия ложитс тяжким бременем на плечи программиста, а не на компилятор.
А как было бы удобно создавать обобщенный класс дл набора объектов и одновременно указывать, какого типа объекты в нем будут содержатся. Теперь это стало возможным - с помощью ряда новых шаблонных классов из состава MFC. Вместо CObList и void-указателей можно применить шаблонный класс CList. Объявление списка int-объектов дается следующим образом:
CList< int, int& > myList;
Теперь компилятор нам будет заботиться о том, чтобы в этом списке содержались лишь int-объекты и никаких других.
При создании своего шаблонного класса вам потребуется для его определения новое ключевое слово template; список его аргументов заключается в угловые скобки. В них помимо обычных аргументов Си++ можно задавать типы, используя для этих целей ключевое слово class. Перед вами пример шаблонного класса для описани стека:
template< class TYPE, int SIZE >
class CStack
{
TYPE data[ SIZE ];
int end;
public:
CStack() { end = 0; }
void Push( const TYPE &c )
{ data[top++] = c; }
void Pop( TYPE &c )
{ c = data[-top]; }
};
В этом шаблоне задается обобщенный класс CStack, содержащий лишь объекты с типом TYPE. В аргументе SIZE указывается максимальное число элементов, которые можно будет сохранить в этом классе. Теперь вы можете построить сколько угодно CStack-объектов, элементы которых будут иметь любой нужный вам тип:
CStack< int, 30> intStack;
CStack< CPerson, 30> personStack;
CStack< CList< int, int& >, 30> CListStack;
Как видно в третьем примере, возможно даже вложение одного шаблона внутрь другого.
Однако все определения для конкретного шаблона должны записываться в пределах одного заголовочного файла. Скажем, нельзя разбросать определения шаблона по нескольким h- и cpp-файлам, как это допускается дл нормальных классов Си++. Дело в том, что при компиляции шаблон должен сразу разворачиваться в полную форму. Поэтому как только компилятор встречает строку типа
CStack< int, 30> intStack,
он тут же формирует полное определение. (Если представлять себе шаблоны как аналогию макрокоманд языка Си, то не следует упускать этой важной особенности при разработке своих шаблонных классов.)
Библиотека Standard Template Library (STL), вероятно, наиболее значительный пример используемых ныне шаблонов. Эта библиотека, олицетворяющая собой новый подход к программированию, служит важным инструментом в арсенале разработчиков на языке Си++. Коротко говоря, STL состоит из следующих пяти основных компонент:
Сейчас доступны многочисленные бесплатные и коммерческие версии библиотеки STL, которая являетс частью проекта стандарта ANSI C++. Если вы пользуетесь Visual C++ 4.1 или более ранней версией, то вариант STL, реализованный в Hewlett-Packard, можно найти на компакт-диске. Для Visual C++ 4.2 фирма Microsoft ввела STL в состав своей стандартной библиотеки.
Тема STL слишком обширна, чтобы раскрыть ее здесь полностью. Поэтому я рекомендую ряд прекрасных публикаций, перемещенных в конце статьи, где рассматриваются вопросы, касающиеся STL и программирования с использованием шаблонов.
Прежде всего остановимся на ограничениях ATL. Эта библиотека предназначена не для замены, а дл дополнения MFC. Она не реализует среду для разработки графических элементов управления ActiveX и документов на их основе. Для подобных конструкций необходимы многочисленные COM-интерфейсы, и подобную задачу лучше выполнять на базе библиотеки классов, а не шаблонов.
Достоинство ATL состоит в возможности создавать компактные COM-объекты, работающие на базе DLL. Благодаря использованию библиотек шаблонов удается при компиляции подключать лишь те классы, на которые имеются явные ссылки. Особенно удобно пользоваться ATL для создания библиотек объектов, способ обращения с которыми во многом напоминает работу с DLL. При этом полностью реализуются достоинства архитектуры COM и удается получить истинно объектно-ориентированный интерфейс, чего нельзя сказать об использовании обычного API на базе DLL.
Перечислим некоторые возможности ATL с последующими комментариями:
Использование COM-объектов с собственными интерфейсами значительно повышает их универсальность и быстродействие. Для построения объектов и работы с ними необходимо пользоваться языком программирования, который допускает применение указателей и наследование классов, например Си++ или Delphi. COM-объекты с расширенным интерфейсом несколько уступают объектам с собственным интерфейсом в универсальности и быстродействии, однако расширенный интерфейс отличаетс большей открытостью. К такому COM-объекту можно обращаться из программ, составленных на языке, в котором нет средств работы с указателями и наследованием классов, например на Visual Basic.
С помощью ATL можно без труда строить наборы объектов-автоматов. Поскольку ATL предоставляет возможность использовать интерфейс IEnumVariant (дл доступа к объектам, имеющим тип Variant), любая на языке Visual Basic программа может без каких-либо дополнительных средств обращаться к вашему COM-объекту через синтаксическую конструкцию FOR EACH ... IN.
Технология Connection Points позволяет организовывать связь между COM-объектами, чтобы последние могли оповещать друг друга о происходящих в них изменениях. Тогда как обычные COM-интерфейсы относятся к разряду "входных", протокол COM-технологии Connection Point позволяет создавать "выходные" интерфейсы, через которые можно отправлять уведомлени и даже обычные информационные сообщения к любым подключенным COM-объектам.
В концепции ATL появилось новое понятие - неявные (tear-off) интерфейсы. Дело в том, что v-таблицы с информацией об интерфейсах находящихся в памяти COM-объектов создаются одновременно с самими этими объектами независимо от того, будут ли эти объекты хоть раз запрошены из программы-клиента. Если же задать неявный интерфейс, то ATL обеспечит динамическое построение v-таблицы лишь при реальном запросе данного интерфейса. В определенных ситуациях это позволяет экономить объем запрашиваемой памяти.
В ATL имеется обширный набор средств для регистрации объектов. Все задачи, связанные с созданием и регистрацией всевозможных параметров GUID, необходимых для идентификации COM-объекта, возложены на инструмент COM AppWizard. Реальная генерация элементов в системном Реестре осуществляется через классы шаблонов библиотеки ATL.
Это - лишь краткий перечень возможностей ATL. В следующий раз мы перейдем к детальному рассмотрению процесса построения COM-объекта с использованием инструментария ATL и начнем с работы ATL COM Wizard.
Джон Лэм, президент фирмы Naleco Research (http://www.naleco.com), разработал RADFind96 - утилиту Windows 95 для эффективного поиска файлов.