Что такое "технология COM" и как с ней бороться?№10
У кого и как добиться
получения COM-объекта
Для
завершения подробной иллюстрации как COM-объекты
находят друг-друга при помощи операционной
системы нам осталось описать всего два
пункта - системную функцию, осуществляющую
разыскание, запуск и предоставление адреса
объекта запросившему его клиенту и то, как
сам COM-сервер обрабатывает запросы от этой
функции.
Возможно,
что на данном этапе это пока и не слишком
интересно, но я хочу повторить свои слова -
точное знание философии взаимодействия
объектов, знание средств и инструментов,
используемых на разных этапах этого
процесса неизбежно. В COM нет возможности
"перекомпилировать
всё" или запустить отладчик и наблюдать
любую часть вашей программы - часть
программного комплекса будут составлять
"чужие"
объекты, с которыми вы будете обращаться
так же, как и со своими. А выяснить "что у них
внутри" и что-то "подправить" вы не сможете,
вам останется полагаться только на точное
соблюдение протокола взаимодействия…
Мы
начнём с последнего
оставшегося пункта - как взаимодействуют
операционная система и COM-сервер, когда
клиент запросил адрес объекта. Поскольку
наше изложение - только иллюстрация
важнейших аспектов, мы будем рассматривать
лишь самый простой случай - взаимодействие
системы и DLL-сервера.
Для
этого нам понадобится знание некоторых
подробностей "DLLестроения". Ранее
говорилось, что операционная система неким
стандартным образом обращается к модулю,
именуемому COM-сервер, и побуждает его выдать
ссылку на COM-объект. Вопрос, на который
хочется получить ответ - как?
Если
обратиться к техническим подробностям, то
вы, вероятно, слышали, что DLL - не просто
кусок кода. Внутри DLL существуют
специальные таблицы, которые описывают
функции, "внешние" с точки зрения границы
этой DLL. Первая таблица называется IMPORTS - это
функции, которые требуются DLL, но которые в
самой DLL не реализованы. Вторая таблица
называется EXPORTS, она описывает функции,
которые реализованы в DLL и предлагаются
всем желающим.
Эти
таблицы - важная часть DLL, поскольку
системный загрузчик, производящий "позднее
связывание", использует их для правильной
настройки адресов вызывающей и вызываемой
процедур. Другими словами, все функции,
которые можно вызвать находясь снаружи DLL,
перечислены в её таблице EXPORTS. Поэтому, если
система как-то обращается к DLL, то она может
это сделать только "по правилам" - иного
способа вызвать какую-то функцию из DLL нет.
Следовательно, присутствие этой функции
должно обнаруживаться инструментально.
В
поставке Visual Studio существует очень полезный
инструмент исследования двоичных файлов -
программа dumpbin.exe. Она располагается в
каталоге "…\Program Files\Microsoft Visual Studio\VC98\Bin" и управляется
интерфейсом командной строки. Назначение этой программы - выдавать содержимое таблиц,
содержащихся в двоичных файлах в человекочитаемом виде. Используем её в
применении к заведомому COM-серверу, обнаруженному нами в прошлой статье - к
DAO350.DLL, который на моей машине располагается в каталоге
"C:\Program Files\Common Files\Microsoft Shared\DAO".
Запустим
dumpbin следующей командой из командной строки:
Переназначение
вывода dumpbin в файл tttt.txt сделано удобства
ради. По умолчанию программа выводит на консоль, а мне хочется получить вывод так,
чтобы его можно было исследовать и после её завершения. Посмотрим, что мы в этот файл
получили. А получили мы вот (с небольшими сокращениями) что:
Microsoft (R) COFF Binary File Dumper Version 6.00.8447
Copyright (C) Microsoft Corp 1992-1998. All rights reserved.
Dump of file DAO350.DLL
File Type: DLL
Section contains the following exports for DAO350.dll
ordinal
hint
RVA
name
2
0
00008D28
DllCanUnloadNow
1
1
00008A31
DllGetClassObject
3
2
00009199
DllRegisterServer
5
3
000090DB
DllRegisterServerEx
4
4
00008E51
DllUnregisterServer
Это
в точности то, что мы хотели - таблица EXPORTS. У
этой DLL всего пять экспортируемых функций!
Забегая вперёд скажу - все они используются
исключительно для функционирования COM, т.е.
перед нами чистый COM-сервер, не
предназначенный делать ничего больше.
И
из этих пяти функций в настоящий момент
речь идет только об одной - DllGetClassObject. Она -
та самая функция, вызывая которую система
сообщает серверу ссылку на какой объект
сервер должен изготовить и предоставить
системе. Обращение к MSDN дает следующий
прототип этой функции:
//Ссылка на идентификатор интерфейса, //который взаимодействует с объектом
LPVOID * ppv
//Адрес выходной переменной, которая принимает //указатель на интерфейс, указанный riid
);
т.е.
система при вызове данной функции готовит
список аргументов <CLSID>-<ссылка на
интерфейс>-<адрес переменной, куда
вернуть результат>. Функция DllGetClassObject
принадлежит DLL, т.е., видимо, как-то умеет
производить объекты запрошенного при
помощи CLSID имени. Адрес произведенного
объекта система ожидает получить
посредством указателя ppv. Напомним, что CLSID -
не что иное, как имя COM-объекта, это - CLaSs
IDentifier.
Откуда
приходят все эти параметры? Они приходят от COM-клиента, который захотел получить
экземпляр объекта, именуемого данным CLSID.
Для того, чтобы система выяснила какой
сервер необходимо активизировать (просмотрев
системный реестр), загрузила его, и вызвала
у этого сервера функцию DllGetClassObject клиент на
своей стороне вызывает "широко известную в
узких кругах" функцию Win32 API CoCreateInstance -
создать экземпляр объекта. MSDN дает о ней
следующую справку:
//Ссылка на идентификатор интерфейса, //который взаимодействует с объектом
LPVOID * ppv
//Адрес выходной переменной, которая принимает //указатель на интерфейс, указанный riid
);
Аргументы
pUnkOuter и dwClsContext в данный момент нас не интересуют, они указывают системе среди
DLL
или EXE-серверов искать интересующий нас объект и агрегировать ли его. А вот три
других аргумента - в точности те, которые передаются системой функции сервера
DllGetClassObject. И - функцию CoCreateInstance клиент вызывает из своего кода. Иными словами,
вызов клиентом функции CoCreateInstance
"транслируется"
системой в вызов функции DllGetClassObject
на
стороне сервера. DllGetClassObject возвращает адрес
объекта, система передает его CoCreateInstance,
которая возвращает его клиенту. Всё, наша
экскурсия в философию закончена - пункты с
первого по четвёртый проиллюстрированы
сущностями операционной системы.
Но
вот откуда берется аргумент riid и что он
означает? И сам этот вопрос и ответ на него
старательно обходились с начала нашего
изложения, а между тем компонентное
программирование вообще-то начинается с
ответа на этот вопрос. riid есть
идентификатор интерфейса. В пункте пятом
нашего философского изложения стояло - "как
взаимодействовать между собой объекты и
сами знают…". На самом деле, в COM объекты
взаимодействуют друг с другом посредством
интерфейсов и - никак иначе.