Часть 1. Разработка простого внутрипроцессного сервера СОМ, реализованного в виде библиотеки DLL
Методические указания.Последовательность создания простого COM сервера в MVS-2008 с использованием ATL (Active Template Library – библиотека активных шаблонов) состоит из следующих основных этапов:
· создание каркаса приложения-библиотеки с помощью мастера ATL Project;
· создание СОМ объекта с фабрикой классов с помощью мастера ATL Simple Object;
· добавление в СОМ объект интерфейсов, объявлений методов и их реализаций;
· построение и регистрация (в реестре Windows) СОМ объекта, реализованного в виде внутрипроцессного сервера, т.е. DLL-библиотеки.
Шаг 1.Создание каркаса внутрипроцессного сервера.
Так как в этой работе нам надо будет создать два приложения – внутрипроцессный сервер и клиентское приложение – рекомендую создать для них общую папку с именем, например, Lab5 и в этой папке создавать проекты, каждый из которых, в свою очередь, будет размещаться в отдельной папке.
Выполнить тему меню FileèNewèProject. Как и при разработке любого другого приложения, в появившемся окне необходимо задать путь к проекту в поле ввода Location (…\Lab5) и имя проекта DLLMathServer в поле ввода Name. Выберите Project types=ATL и Templates=ATL Project, нажмите ОК.
На следующей странице Application Settings выберите только переключатель Server type=Dynamic-link library и не выбирайте никаких опций в группе Additional options, хотя это и не существенно для данного приложения. Впрочем, это только рекомендация: вы можете делать все что угодно, если любите преодолевать трудности. Нажмите кнопарек Finish – хотя это еще отнюдь не финиш, а пока просто завершение создания каркаса приложения.
Вам разрешается, совершенно безвозмездно, т.е. даром, посмотреть на файлы, сгенерированные мастером: DLLMathServer.cpp и DLLMathServer.def. Во втором файле (DLLMathServer.def) вы обнаружите, что библиотека экспортирует – по собственной инициативе – четыре важные функции:
· DllCanUnloadNow();
· DllGetClassObject();
· DllRegisterServer() и
· DllUnregisterServer().
Назначение этих функций подсказывают их имена. Интерес представляет также файл DLLMathServer.idl, к содержимому которого мы еще будем обращаться. В этом файле будет содержаться описание СОМ объекта и его методов на языке описания интерфейсов (IDL – interface definition language).
Шаг 2. Включение в библиотеку COM-объекта.
Выполните команду ProjectèAdd class. В появившемся окне надо выбрать Categories==ATL(не выбирайте WMI) и Templates==ATL Simple Object и нажать кнопку Add. В следующем окне на вкладке Names достаточно ввести только короткое имя объекта в поле Short name, а остальные имена мастер сгенерирует сам и их можно не изменять. В данном примере для объекта выбрано имя Mathem(рис. 1).
Рис.1. Задание свойств СОМ объекта
Естественно, вы обратили внимание на то, что мастер все остальные имена сформировал по короткому (Mathem) имени СОМ объекта. В частности, обратите внимание на поле ProgID: это так называемое внешнее имя СОМ сервера, под которым он будет зарегистрирован в реестре и которое можно будет использовать в клиентских приложениях для идентификации СОМ сервера. Естественно, что вы можете изменить имена, сформированные мастером, но … стоит ли это делать?
Нажмите кнопку Next или, что-то же самое, перейдите к вкладке Options. На этой вкладке выберите Threading model==Single и Interface==Custom. Оставьте остальные установки мастера без изменения и натисните Finish. Наберитесь терпения и дождитесь, когда мастер завершит создание заготовки СОМ объекта.
После работы этого мастера появятся два (основных) новых файла: Mathem.h и Mathem.cpp. Файл Mathem.h содержит объявление сокласса CMathem, включающего интерфейс «по умолчанию» IMathem и пустой конструктор класса. Файл реализации класса Mathem.cpp вообще пока будет пустым. Обратите внимание на «родителей» класса CMathem (файл Mathem.h): вам понятно, кто они по происхождению? Что указано в угловых скобках?
Таким образом, мы имеем заготовку сервера и (одного) СОМ-объекта в нем. Проект можно компилировать и собрать, получить библиотеку DLL (внутрипроцессный сервер), но никакого полезного кода он пока не содержит. Надо собрать волю в кулак, напрячь (или напрясть?) свой могучий интеллект, объявить и реализовать какие-либо методы (и/или свойства) СОМ объекта.
Шаг 3. Добавление метода в СОМ объект.
Для добавления метода надо выбрать вкладку ClassView в окне решений, в ней выбрать ветвь DLLMathServer (а не DLLMathServerPS) и в ней первую подветвь IMathem, помеченную значком интерфейса . Вызвав контекстное меню для IMathem, выберите команду AddèAdd method для добавления метода к интерфейсу IMathem. В появившемся окне (рис. 2) надо объявить метод. Допустим, что мы хотим реализовать такой ценный (или бесценный?) метод:
HRESULT Cube(double Arg, double *Res)
{
*Res=Arg*Arg*Arg;
return S_OK;
}
Метод может возвращать значение только типа HRESULT (целое 32-битовое число, содержащее результат выполнения метода – код ошибки), а практический результат, в данном случае куб аргумента, мы будем возвращать посредством параметра-указателя Res.
Рис. 2. Окно мастера добавления метода в интерфейс
Тогда в окне мастера добавления метода мы должны выполнить такие действия:
· в поле Method name ввести имя метода Cube;
· включить флажок in (входной параметр) в Parameter attributes;
· в списке Parameter type выбрать имя типа DOUBLE;
· в поле Parameter name ввести имя первого параметра функции Arg;
· нажать кнопку Add, завершив тем самым ввод объявления первого параметра функции;
· в списке Parameter type выбрать имя типа второго параметра DOUBLE*;
· включить флажок out (выходной параметр) в Parameter attributes;
· в поле Parameter name ввести имя второго параметра функции Res;
· шлепнуть по кнопке Add.
В результате этих манипуляций окно мастера должно приобрести вид, который и представлен на рис. 2. На вкладке IDL Attributes ничего изменять не нужно. Конечно, не мешало бы ввести информативное описание метода в поле helpstring перед ударом по кнопке Finish (описание можно и не вводить, если у вас есть чудодейственное зелье от проклятий пользователя метода). Подробное описание атрибутов метода имеется в MSDN и вы сможете его улицезреть, если нажмете клавишу F1 или символ ? в правом верхнем углу окна мастера.
Объявление метода будет шустренько добавлено в тело класса CMathem (файл Mathem.h), а заготовка для его определения – в файл реализации Mathem.cpp, куда вы и должны добавить тело метода, приведенное выше. В результате определение метода должно принять такой законченный вид:
STDMETHODIMP CMathem::Cube(DOUBLE Arg, DOUBLE* Res)
{
*Res=Arg*Arg*Arg;
return S_OK;
}
Если побродить по заголовочным файлам, то можно обнаружить, что макрос STDMETHODIMP в конечном итоге приводит заголовок метода к такому виду:
long __stdcall CMathem::Cube(DOUBLE Arg, DOUBLE* Res)
Настоятельно рекомендую ознакомиться с содержимым файла DLLMathServer.idl, который содержит практически полное описание нашего СОМ объекта на Interface Definition Language (IDL) – языке описания интерфейсов. Сие значит, что там объявлен интерфейс IMathem и предоставляемый им пока единственный метод Cube().
Выполните компиляцию и сборку приложения, в результате чего в каталоге Debug (при установке отладочной конфигурации в качестве активной) должен появиться файл DLLMathServer.dll, который и будет содержать земную материализацию внутрипроцессного сервера.
Замечание 1. Ежели при компиляции здесь и далее пред Вашим ясным взором вдруг возникнет окно с сообщением, содержащим текст «This file has been modified outside of the source editor. Do you want to reload it?» – поверьте ИС и шмякните по кнопке «Yes to All»
Если вы обратитесь к анализу содержимого реестра Windows, то уже на этом этапе обнаружите там информацию о вашем сервере (например, поищите имя DLLMathServer.Mathem).
Замечание 2. Для регистрации сервера в реестре у вас должны быть права на запись в разделы реестра HKEY_CLASSES_ROOT и HKEY_LOCAL_MACHINE. Зарегистрировать сервер в реестре можно также с помощью команды «RegSvr32.exe имя_сервера», где имя_сервера должно быть полным именем приложения, включающим путь. Разрегистрировать приложение можно с помощью команды «RegSvr32.exe /u имя_сервера».
Итак, на сием этапе мы получили СОМ объект с пока еще одним интерфейсом IMathem. Обратите внимание также на внешнее имя программы (библиотеки) DLLMathServer.Mathem, указанное в окне Prog ID (см. рис.1), которое нам понадобится при разработке клиентского приложения. Это имя, к слову сказать, указывается в реестре Windows как значение ключа VersionIndependentProgID, а значением ключа ProgID будет DLLMathServer.Mathem.1. Более подробные сведения о том, какие записи в реестре появляются при регистрации сервера, см. в конспекте (подразд. «О регистрации внутрипроцессного сервера СОМ» раздела «Приложения»).
Подведем промежуточные итоги. На этой стадии разработки проекта создана DLL-библиотека, которая является «домом» для нашего СОМ-объекта, и сам СОМ-объект, который имеет один интерфейс и один метод. В файле определения модуля DLLMathServer.def перечислены функции, которые экспортирует наша библиотека. Расслабтесь: вам не придется заниматься реализацией этих пяти стандартных для СОМ-серверов функций, хотя делать это и не запрещено.
Шаг 4.Добавление в СОМ-объект еще одного интерфейса.
Расслабились? Теперь сосредоточтесь. Для добавления нового интерфейса к существующему СОМ объекту необходимо открыть файл библиотеки типов (DLLMathServer.idl) и скопировать описание первого интерфейса, т.е. создать копию следующих строк файла DLLMathServer.idl:
[
Object,
Uuid(2E4FB046-98D8-4E74-8572-84D7133FFEF4),
pointer_default(unique)
]
interface IMathem : IUnknown{
[] HRESULT Cube([in] DOUBLE Arg, [out] DOUBLE* Res);
};
Эти скопированные строки надо разместить непосредственно ниже оригинальных.
Далее необходимо изменить имя интерфейса (в данном примере я выбрал имя IMathem2), удалить строку с описанием метода Cube и заменить идентификатор uuid на новый, воспользовавшись командой ToolsèCreate Guid. В окне мастера генерации uuidпроще всего выбрать четвертый формат представления уникального идентификатора (Registry uuid). Кроме того, новый интерфейс необходимо добавить в описание сокласса (coclass Mathem), но уже без спецификатора default, так как только один интерфейс может быть интерфейсом по умолчанию. В результате этих действий содержимое файла библиотеки DLLMathServer.idl должно стать таким (комментарии в начале файла опущены, а добавленные строки выделены цветом и комментариями. Копировать текст нельзя, так как идентификаторы uuid должны быть уникальными):
import "oaidl.idl";
import "ocidl.idl";
[
Object,
Uuid(2E4FB046-98D8-4E74-8572-84D7133FFEF4),
pointer_default(unique)
]
interface IMathem : IUnknown{
[] HRESULT Cube([in] DOUBLE Arg, [out] DOUBLE* Res);
};
// начало добавлений
[
Object,