PC Magazine/RE logo
©СК Пресс 97-1s
e-mail: pcmagedt@aha.ru

PC Magazine, December 17, 1996, p. 233

Создание COM-объекта средствами ATL

Джон Лэм


Работа с мастером COM Wizard библиотеки ActiveX Template Library.

В этой статье мы покажем, как создать COM-объект с помощью ATL. Все упомянутые здесь файлы вы найдете в службе PC Magazine Online (http://www.pcmag.com). Версию 1.1 пакета ATL можно бесплатно переписать с Web-узла фирмы Microsoft по адресу http://www.microsoft.com/visualc/atl11. Для работы с ней также потребуется Visual C++ 4.1 или более поздн версия.

Знакомимся с мастером

Ваше знакомство с библиотекой ATL начнется с нового инструмента - мастера ATL COM AppWizard. Мы займемс подготовкой простого COM-объекта с двойным интерфейсом, организованного в виде DLL-модуля. Для начала сформируем рабочую область нового проекта в окне Developer Studio и выберем для него тип ATL COM AppWizard. (В нашем примере мы присвоили проекту им ATLTEST.) На экране появляется диалоговое окно, в котором можно задать исходные параметры для данного проекта.

Как вы можете убедиться, представленный в мастере выбор параметров для вашего проекта создани COM-объекта весьма обширен. Здесь в вашем распоряжении самые разные варианты, однако в данном случае, по-видимому, нам подойдут указанные параметры. Позднее мы обсудим вопрос о том, как в соответствии с вашими требованиями выбираются другие параметры. Однако сейчас давайте остановимся на данном варианте.

Теперь у нас есть возможность заменить имена, сформированные мастером. И снова мы предпочли предложенные по умолчанию, после чего мастер произвел на свет проект в виде каркаса нашего COM-объекта.

Получив необходимый для нашей ATL-программы минимум, посмотрим, что за файлы подготовил нам мастер.

Функции COM-объекта

В файле Atltest.cpp содержатся основные служебные функции COM-объекта, организованного как DLL-модуль. К ним относятся четыре экспортируемые функции, которые должны быть у любого такого объекта. Две из них - DllGetClassObject и DllCanUnloadNow - это обязательные функции. Именно через эти функции лежит путь к созданию средствами COM-библиотек новых COM-объектов, входящих в состав вашего DLL модуля, а также уточняетс возможность выгрузки данного модуля из памяти. Две другие функции - DllRegisterServer и DllUnregisterServer - необязательные. Однако они значительно обегчат вам жизнь с их помощью записываютс и удаляются сведения об этом DLL-модуле в системном реестре Windows, что избавит вас от необходимости подготовки отдельных REG-файлов для регистрации данного COM сервера. Мало вероятно, что когда-нибудь вам придется что-то менять в Atltest.cpp.

Описание COM-объекта в IDL-файле

Файл Atltest.IDL содержит описания вашего COM-объекта и его интерфейсов, составленные на языке Interface Definition Language (IDL). По мере того как вы добавляете, удаляете или вносите изменения в описания интерфейсов данного COM-объекта, обновляетс хранящаяся в нем информация. Перед вами - фрагмент из файла Atltest.IDL:

// После обработки файла компилятором MIDL // будут созданы библиотека типов // (файл Atltest.tlb) и объектный // код для организации межпроцессной связи. [ object, uuid(7D785DE3-07С0-11D0-896С-444553540000), dual, helpstring("Интерфейс IAtltest1"), pointer_default(unique) ] interface IAtltest1 : IDispatch { import "oaidl.idl"; HRESULT Bar( [in] BSTR s); // Пример рабочей функции };

Как можно заметить, здесь имеется идентификатор (IID) двойного интерфейса вашего COM-объекта. Я поместил в этот фрагмент и функцию Bar, чтобы показать, как выглядит типичный прототип рабочей функции на языке IDL. Он мало отличается от подобных примеров на языке Си++; разница лишь в том, что здесь есть директива [in], наличие которой означает, что компилятор MIDL должен подготовить подходящий объектный код дл организации межпроцессного обмена, т. е. модуля, обеспечивающего возможность обращений к интерфейсам из других процесов.

В соответствии с выбранными вами из представленных мастером COM AppWizard исходными параметрами в результате обращения ATL к компилятору MIDL (Microsoft IDL Compiler) будет получен ряд выходных файлов. С помощью этого компилятора можно создать файл двоичной библиотеки типов с описанием всех COM-объектов вашего DLL модуля. В таком файле возможны сотни различных групп размеров, для установки которых в файле Atltest.IDL задаются директивы.

Существуют различные способы применения созданной вами библиотеки типов. Например, любой разработчик может обратиться к ней с помощью программы OLE2VIEW (имеющейся также в Win32 SDK), чтобы уточнить, какими средствами обладает данный COM-объект. Либо, воспользовавшись средствами Visual Basic, можно просмотреть эту библиотеку для проверки типов некоторой VB-программы и еще до реального вызова COM-объекта проверить соответствие имен его функций и типов их параметров.

Если из предложенных мастером ATL COM AppWizard вы выбрали тип интерфейса Custom (индивидуальный), компилятор MIDL кроме уже упомянутых подготовит и следующие файлы с исходными текстами на языке Cи: Atltest_i.c, Atltest_p.c, Atltest1.h и dlldata.c. Дл их компиляции необходим файл ATLTEST1PS.MAK, сформированный мастером. Эти файлы содержат специальные программы, которые обеспечивают возможность доступа к интерфейсам вашего COM-объекта из программ в других процессах Win32. Для COM-объектов, организованных в виде DLL модулей, подобных программ не требуется, поскольку Windows автоматически отображает данный DLL-модуль в адресное пространство процесса той программы, которая обращается к функциям ваших COM-объектов. Если же вы оформили COM-объект как EXE-файл, придется скомпилировать подготовленные компилятором MIDL файлы, с тем чтобы образовалс объектный код для межпроцессного обмена. Дело в том, что для COM-объекта, облеченного в форму EXE-файла, гарантировано выделение отдельного процесса Win32. Поэтому, для того чтобы был возможен доступ к их интерфейсам из программ в других процессах, потребуетс соответствующий код.

Описание интерфейсов и функций

В файле Atltest1.h хранятся описания интерфейсов COM-объекта и принадлежащих им функций. При внесении в описания этих интерфейсов дополнений, удалений или исправлений нужно вручную отредактировать данный файл, а также Atltest.IDL.

Взглянув на следующий пример, вы можете увидеть значительное отличие в том, как обращаются с COM-объектами ATL и MFC:

class Ctltest1 : public CComDualImpl<IAtltest1, &IID_IAtltest1, &LIBID_ATLTESTLib>, public ISupportErrorInfo, public CComObjectRoot, public CComCoClass<CAtltest1, &CLSID_CAtltest1>

Как можно заметить, в ATL для реализации нескольких интерфейсов одного COM-объекта используется механизм множественного наследования, имеющийся в Си++. Такой подход проще и гораздо элегантнее, чем предусмотренное в MFC применение метода вложенных классов, когда речь идет о нескольких интерфейсах. А поскольку нет вложенных классов, значит. нет и потребности в макроконструкции METHOD_PROLOGUE для создани эквивалента указателю this.

Обратите внимание также на использование большого числа шаблонов при генерации программ обработчиков расширенного и IUnknown-интерфейсов вашего COM-объекта. В собственно объект войдут лишь модули, необходимые дл обработки выбранного вами в мастере COM AppWizard типа интерфейса. При компиляции никаких дополнительных модулей из внешних библиотек добавляться не будет, что способствует более строгому контролю над размером файла полученной программы.

Далее описание класса вы дополняете определениями принадлежащих функций интерфейса вашего COM-объекта:

// IAtltest1 public: STDMETHOD(Bar)(BSTR s); };

Придется внести изменения и в соответствующий раздел файла Atetest 1.h, с тем чтобы в него вошли эти описания функций. В нашем примере я воспользовалс макрокомандой STDMETHOD, чтобы объявить интерфейсную функцию Bar с единственным параметром типа BSTR (BASIC-строка). Аналогичным образом определяются и другие принадлежащие функции и их параметры вне зависимости от того, какого типа интерфейс дл COM-объекта вы хотите реализовать - заказной или расширенный.

Реализация методов

Файл Atltest1.cpp содержит программы, реализующие методы вашего COM-объекта. Мастер COM AppWizard уже подготовил принимаемые по умолчанию функции интерфейса ISupportErrorInfo.

При составлении программ для интерфейсных методов вашего COM-объекта, как правило, используетс макрокоманда STDMETHODIMP. Это показано на следующем примере:

STDMETHODIMP CAtltest1::Bar( BSTR s ) { CString str = s; AfxMessageBox( str ); return S_OK; }

Любой COM-объект должен передавать в вызывающую программу некоторый код стандартной ошибки HRESULT. Если передается S_OK, значит, ошибок нет. При возникновении ошибки return-значениям станет один из кодов стандартных ошибок, описания которых имеются в файле winerror.h, входящем в состав Win32 SDK, вы найдете его в каталоге \msdev\include.

Описание интерфейсов на языке C++

При создании COM-объекта компилятор MIDL автоматически подготовит файл Atltest.h, который содержит описание на языке Cи++ всех COM-интерфейсов вашего объекта. Так выглядит составленное компилятором MIDL-определение COM-интерфейса ISimpleObj:

interface IAtltest1 : public IDispatch { public: virtual HRESULT STDMETHODCALLTYPE Bar ( /* [in] */ BSTR s) = 0; }

При внесении в описание интерфейса вашего COM-объекта изменений нет необходимости редактировать этот файл вручную. Обновить следует файлы Atltest.IDL и Atltest1.h. Следует отметить очень важный момент: не забывайте приводить в соответствие содержимое этих файлов, поскольку именно в них задается описание интерфейса вашего COM-объекта.

Параметры GUID в стиле Си++

В файле Atltest_i.c представлены все идентификаторы GUID, в которых нуждается ваша программа. Их формирует мастер COM AppWizard с помощью функции CoCreateGuid, предусмотренной в интерфейсе COM API. Однако все эти GUID-значения сохраняются мастером в файле Atltest.IDL как различные параметры. К счастью, компилятор MIDL просматривает IDL файл и формирует идентификаторы GUID в стиле Си++:

const IID IID_IAtltest1 = {0x87E49D4B, 0x057A, 0x11D0, {0x89, 0x6C, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}; const IID LIBID_AtltestLib = {0x87E49D49, 0x057A, 0x11D0, {0x89, 0x6C, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}};

Выбор параметров

Теперь посмотрим, какие варианты параметров предлагаются мастером и какими соображениями руководствоваться при их выборе.

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

Короче говоря, если COM-объект организован в виде DLL-модуля, а не EXE-файла, операции обращений к нему выполняются значительно быстрее - на несколько порядков. Однако не следует забывать, что форма организации COM-объекта влияет лишь на скорость выполнения обращений к нему, а не на быстродействие программы самого COM-объекта. Поэтому оформлять COM-объект в виде DLL-модуля целесообразно в том случае, если предполагается, что он достаточно часто будет вызываться клиентскими программами. Другими словами, имеет смысл прибегнуть к такой форме, если операция вызова COM-объекта занимает значительную долю суммарного времени, необходимого для обращения к одному из его методов.

Если же ваше основное требование к COM-объекту - надежность, вполне разумно было бы организовать его как EXE-файл. Дело в том, что COM-объект, оформленный в виде EXE-файла, выполняется в своем собственном, защищенном адресном пространстве процесса Win32. Если же данный объект облечен в форму DLL-модуля, он будет выполняться в адресном пространстве процесса вызвавшей его программы. Для EXE-формы COM-объекта возможен доступ лишь к тем областям памяти, которые находятся в пределах адресного пространства его собственного процесса (для Windows 95 сказанное верно лишь частично, для Windows NT - абсолютно). Поэтому маловероятно, что имеющаяся в программе COM-объекта ошибка приведет к сбою обратившейся к нему программы-клиента.

Наконец, если ваш COM-объект должен управлять некоторым совместно используемым ресурсом, имеющимся в памяти, тогда вопрос о его форме вы решаете сами. COM-объект в виде EXE-файла свободно распоряжаетс памятью в рамках адресного пространства собственного процесса. А память, выделяемая COM-объектом в виде DLL-модуля, ограничена рамками пространства, в котором выполняется процесс вызвавшей его программы. Для того чтобы некоторый разделяемый ресурс в памяти стал доступен сразу нескольким программам-клиентам, DLL-модулю придется запросить блок памяти из глобального хипа. А реализация подобных возможностей потребует некоторых дополнительных модулей, не говор уже о том, что разарботчику придется разбираться в сложностях размещения файлов в памяти. В данном случае вы должны решить, что для вас важнее - быстродействие или надежность.

Выбор типа интерфейса

В ATL вы можете выбрать один из двух типов COM-интерфейса для своего объекта - индивидуальный (Custom) или двойной (Dual).

Индивидуальный COM-интерфейс создается как производный от IUnknown. Интерфейс такого типа отличается наивысшим быстродействием, причем в качестве аргументов для его функций пригодны любые типы данных. Если к COM-интерфейсу будут обращатьс программы-клиенты, выполняющиеся в адресных пространствах других процессов, компилятор MIDL автоматически сформирует модули, обеспечивающие межпроцессную связь. Благодаря применению ATL и MIDL весь процесс создания индивидуальных интерфейсов сводится просто к внесению элементов в IDL-файл и в файл заголовков нового COM-объекта.

Второй вариант COM-интерфейса - двойной интерфейс, который порождается из IDispatch, а не IUnknown (хот интерфейс IDispatch - производный от IUnknown). В этом случае вы получаете наилучшее решение двух проблем. Во-первых, к нему могут обращаться разнообразные прикладные программы (например, составленные на Visual Basic и не обладающие средствами для работы с указателями и механизмом наследования на уровне классов). Во-вторых, его быстродействие не отличаетс от аналогичного показателя индивидуального COM-интерфейса.

Однако гибкость, свойственная двойным интерфейсам, несколько ограничивает другие возможности. Передавать функциям двойного интерфейса можно параметры лишь тех типов, которые считаются совместимыми с требованиями механизма OLE-автоматизации. Это означает, что применение указателей на создаваемые пользователем структуры данных исключается, поскольку нельзя забывать о том, что к двойному интерфейсу будут обращатьс стандартные контроллеры автоматизации, которые воспринимают лишь данные типа Variant.

Что выбрать - ATL или MFC?

Решение вопроса о том, какой из инструментальных пакетов - ATL или MFC - выбрать, зависит главным образом от того, какого сорта COM-объект вы собираетесь создать. В узком смысле слова COM-объект - это любой объект с интерфейсом IUnknown. Если исходить из этого определения, любые серверы обработки документов с ActiveX-элементами и ActiveX-элементы на самом деле можно считать COM-объектами.

Если вы намерены заняться разработкой ActiveX-элементов, лучше избегать использования средств ATL. Реализовать полный перечень интерфейсов, необходимых для ActiveX-элементов, вам поможет MFC. Если же вы сочтете, что размер файлов, создаваемых средствами MFC, чрезмерно велик, тогда, может быть, вам попытаться добиться цели с помощью инструментов BaseCtl ActiveX, входящих в состав пакета ActiveX SDK. Примен эти средства, вы получите компактные ActiveX-элементы, избежав при этом непроизводительных излишеств, как в случае MFC.

Если ваша задача - разработка серверов обработки документов, содержащих ActiveX-элементы, и эти серверы будут работать в среде Web-браузера, например Internet Explorer 3.0, то не пытайтесь воспользоваться ATL. Только MFC - идеальный вариант для решени документо-ориентированных задач и выполнит основную часть рутинной работы.

Если же вы собираетесь создавать библиотеки объектно-ориентированных подпрограмм, предназначенных для использования многими программами, в этом случае непревзойденными считаются средства ATL. При обычной задаче формирования стандартной DLL или MFC Extension DLL (DLL-расширения библиотеки MFC) обратите внимание на простоту ее подготовки средствами ATL. В результате ваши объектно-ориентированные программы будут полностью разделяемыми, а также к вашим библиотекам смогут обращаться модули, составленные практически на любом языке программирования для Windows. Более того, эти программы-клиенты могут находиться на другом компьютере. С появлением библиотек распределенных COM-объектов Distributed COM фирмы Microsoft, предусмотренных в Windows NT 4.0, ваши COM-объекты становятся доступными по сети любой программе - причем без каких-бы то ни было изменений.

Джон Лэм - президент фирмы Naleco Research (http://www.naleco.com), разработчик RADFind96 - утилиты Windows 95 для эффективного поиска файлов.