Скачиваний:
100
Добавлен:
01.05.2014
Размер:
1.56 Mб
Скачать

Именование com-объектов

COM-объекты являются объектами только тогда, когда они развёрнуты в памяти. До того нет объектов, а есть статические типы.

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

Проблема в том, что мнемонически значимых идентификаторов в языке очень немного. А пространство имён в котором они должны быть применимы, в отличие от пространства имен проекта C++, - никак даже и не замкнуто. И работают в этом пространстве многие десятки тысяч независимых разработчиков, которые вольны давать своим поделкам произвольные имена.

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

При этом, если немного подумать, нам вовсе не нужно "имя" в его человеческом понимании. Нас вполне устроит и номер, лишь бы среди всего множества перенумерованных объектов эти номера были уникальными. Но "единая нумерация", очевидно, требует и "единого нумератора" - организации, которая бы выдавала несовпадающие номера. А кто будет содержать эту организацию? И сколько будет стоить получение одного уникального номера?

Выход нашла Open Software Foundation- ее программисты придумалиUUID-universally unique identifier, вселенски уникальный идентификатор. ИдеяUUIDсостоит в следующем - для числа достаточной разрядности диапазон различных представимых значений значительно больше числа объектов, которые требуют перенумерования. Например, число 10100настолько велико, что им невозможно выразить никакого известного физического понятия. Даже число электронов во Вселенной измеряется порядком 1087, стало быть, если для такого числа определить случайнозначную хэш-функцию, которая бы более-менее равномерно выдавала значения из диапазона, то из-за огромности общего числа значений вероятность их совпадения будет невелика.

UUIDи есть такое длинное двоичное 128-разрядное число и определённый к нему алгоритм вычисления хэш-функции. Алгоритм этот для большей надёжности использует ряд действительно случайных значений. Например, в подсчете используется показание внутреннего таймера машины, некоторые параметрыBIOSa, уникальный идентификатор сетевой карты. Математически доказано, что вероятность того, что на достаточно большом интервале времени вычисления этой хэш-функции дадут совпадающие значения практически равна нулю. А это именно то, что и требовалось - где бы и кто бы ни вычислялUUIDон обязательно получит уникальное значение среди вообще всех, могущих быть вычисленными,UUID.

Длина UUIDв 128 битов не имеет особого смысла для обеспечения его уникальности и могла бы быть выбрана и чуть короче и чуть длиннее, но 128 бит = 16 байтов = 4 двойных слова, т.е. длинаUUIDкратна слову процессора, что старого, 16-ти разрядного, что нового 64-разрядного. Т.е. такой выбор все-таки обусловлен удобством обработки и обращения.

Компания Microsoftвзяла на вооружение эту идею и этот объект без изменения, только назвала его немного по другому -GUID, т.е.globally unique identifier, глобально уникальный идентификатор.

GUIDи применяется там, где в моделиCOMтребуется уникально обозначить некое понятие. Обычно, обозначаемых ими понятий два - идентификатор класса, называемыйCLSIDи идентификатор интерфейса, называемыйIID.

Нужно отметить - вычисление GUIDвсякий раз дает уникальное значение, неважно будет оно потом присвоеноCOM-объекту или нет. Поэтому, если в практике программиста встречается случай, когда ему нужно глобально уникально перенумеровать свои собственные объекты он с успехом может использоватьGUIDи для этой цели.

Для вычисления UUID/GUIDплатформаWin32имеет функцииAPIUuidCreate и CoCreateGuid, которые вычисляют значение и предоставляют его вызвавшей программе, т.е. этим сервисом может воспользоваться любая программа исполняющаяся в операционной системе.

GUID(и, соответственно,CLSID,IIDи т.д) имеет несколько нотаций записи и форм представления, поскольку длинное 128-битовое число в существующих системах не может быть представлено как одна простая сущность.

Итак, внутри программы GUIDканонически определяется как структура:

struct _GUID

{

unsigned long Data1;

unsigned short Data2;

unsigned short Data3;

unsigned char Data4[8];

} GUID;

т.е. последовательность long-short-short-char-char-char-char-char-char-char-char.

Эта последовательность в символьной шестнадцатиричной записи (каждая шестнадцатиричная цифра заменена символом X) будет выглядеть так:

XXXXXXXX-XXXX-XXXX-XX XX XX XX XX XX XX XX

По некоторым структурным причинам последние восемь байтов делятся на два плюс шесть и последние шесть байтов GUIDвычисляются как функция от идентификатора сетевой карты ethernet/tokenring установленной в машине. Поэтому в символьном виде "настоящийGUID" записывается так:

XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX,

При этом у всех GUIDсгенерированных на данной машине последние шесть байтов будут одинаковыми, если на машине действительно обнаруживается сетевая карта. Примеры символьной записи GUID:

22F55881-280B-11d0-A8A9-00A0C90C2004

0c733a60-2a1c-11ce-ade5-00aa0044773d

B502D1BE-9A57-11d0-8FDE-00C04FD9189D

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

Важно понимать - GUIDесть простое длинное 128-битное численное значение. Его структурированность существует для удобства человеческого восприятия. ПоэтомуGUIDв двоичном представлении можно уместить в байтовый массив длиной в 16 ячеек, либо в массивshortдлиной 8 ячеек и т.д. Можно представлятьGUIDв символьном виде как последовательность символов - шестнадцатеричных цифр, а можно представить его просто длинным десятичным числом. Но структурированная дефисами символьная форма записи шестнадцатиричными цифрами считается стандартной формой записиGUIDи отступать от нее не рекомендуется. Дело в том, чтоWin32реализует ряд функцийAPIкоторые преобразуютGUIDиз одной формы представления в другую, а эти функции считают "символьной формой записиGUID" именно ту, что показана выше. ДлинаGUIDв символьной нотации - 37 символов.

Существует и еще одна символьная форма GUID, в которой подчеркнуто единство всех групп цифр:

{B502D1BE-9A57-11d0-8FDE-00C04FD9189D},

в ней значение GUIDзаключено в фигурные скобки. Собственно, только эта форма записи и может считаться символьным представлениемGUID, т.к. скобки и обозначают формат. Запись без фигурных скобок может быть просто интерпретирована как строковое значение. Но путаницы не возникает, поскольку случаи употребления этих нотаций различаются.

Программисту необходимо знать, что в стандартной поставке средства разработчика Visual Studioсуществуют две программыuuidgen.exeиguidgen.exe, которые представляют собой программную оболочку на функциюAPICoCreateGuid.

Располагаются обе в каталоге "...Program Files\Microsoft Visual Studio\Common\Tools\", первая из них управляется интерфейсом командной строки, а вторая снабжена графическим интерфейсом. Поскольку GUIDгенерируется системной функциейAPI, то и качество их работы тоже одинаково. На данном этапе пока достаточно знать, что такие программы существуют. Используются они теперь редко, поскольку интегрированные средства разработки (наподобиеVisual Studioи других) и сами умеют обращаться к функцииCoCreateGuidпри необходимости.

GUIDобъекту присваивает его разработчик, а не операционная система, поэтому на всех пользовательских машинахGUIDобъекта будет один и тот же, иGUIDразных объектов разных разработчиков не пересекутся. Это позволяет, теоретически, рассматривать случай такого распределённого приложения, часть которого работает на одном континенте, а часть - на другом. И при этом это приложение "ничего не почувствует". Слово "теоретически" здесь употреблено не напрасно - хотя концептуально это возможно уже сейчас, технически это пока возможно едва ли - надежность такого распределенного приложения и его быстродействие будут пока значительно ниже среднего, но причина этого совсем неCOM, а существующие технологии связи.

Также, нужно знать, что UUIDиGUIDиспользуются в одинаковом качестве и вCOMи вCORBA, являясь, в сущности, совершенно одним и тем же понятием одного назначения, вида и алгоритма вычисления.

Мы рассмотрели генерацию уникального идентификатора статического типа COM-объекта. Но это только половина всей проблемы - эти идентификаторы нужно где-то помнить. Для этой цели вMS Windowsиспользуется специальныйсистемный сервис, называемый системный реестр.

Надобность возникновения системного реестра в эволюции операционной системы возникла очень давно. Даже программы в такой примитивной системе как DOSнуждались в каком-то средстве, которое позволяло сохранять им хотя бы настроечную информацию в промежутках между сеансами. Традиционно это реализовывалось как особый файл, который программа прочитывала при старте и записывала при завершении. Те, кто работал сWindows 3.x, может быть, еще помнят, что этот файл имел расширение*.iniи хранился в каталогеWindows. Каждая программа вела этот файл самостоятельно и кто во что горазд. Всё возрастающая сложность программ и большая избыточность таких индивидуальных файлов сподвигла разработчиков операционной системы призадуматься над какой-то централизацией и упорядочением этого разрозненного хозяйства. Так появилась концепция особой системной базы данных,которая бы, если можно так выразиться, содержала в себе информацию о "самоосознании данной копии операционной системы" работающей на данном компьютере.

Эта концепция и была реализована в виде системного реестра, который представляет собой базу данных такого назначения и средства доступа к ней. Средства эти глубоко интегрированы в операционную систему, поэтому всякая программа может воспользоваться этим сервисом для межсеансного (постоянного, энергонезависимого) хранения своей информации о состоянии. Разумеется, что первым пользователем этой базы является сама операционная система - без нее система оказывается хуже малого ребёнка и не может даже загрузиться, поскольку не помнит какими ресурсами располагает. Точнее говоря, все свои знания о том, что она - система, операционная система хранит именно там.

Прежде чем рассмотреть организацию этого хранилища, наверное, стоит сделать и специальное примечание. Понятие "база данных" (БД) в таком бытовом понимании в последнее время прочно связалось с одной частной реализацией - реляционной базой данных, хотя на самом деле является не чем иным, как только концепцией. Поэтому вполне уместно называть базой данных и вообще любое хранилище данных вместе с унифицированными методами доступа к нему. В этом смысле системный реестр - самая настоящаяБДнесколько непривычной в наше время иерархической организации.

Иерархическая организация означает, что единицы хранения в этой БДнаходятся между собой в отношении "родитель-потомок" и однозначный доступ к потомку обеспечивается перечислением всех его предков. Самым непосредственным примером этого является организация файловой системы - каталоги файловой системы могут содержать файлы, а могут - и другие каталоги. А для доступа к файлу необходимо перечислить все предшествующие каталоги...

Системный реестр организован так же. Единицами хранения в нем являются "разделы" (keys) и "параметры" (values), которые соотносятся с "каталогами" и "файлами" привычной файловой системы - разделы могут содержать в себе другие разделы и параметры, а параметры - нет. И разделы, и параметры могут иметь значения - числа или строки, так что эти числа или строки адресуемы при помощи указания цепочки разделов и параметра.

Существуют установленные правила о структурировании информации реестра, т.е. какого сорта информация должна присутствовать в определенном разделе реестра, существует набор функций Win32API, позволяющий читать, искать, записывать и удалять информацию и существует специальная программаregedit.exe, которая представляет собой графический редактор реестра. Располагается эта программа в каталоге "...\WinNT", но инсталлятор не делает к ней ярлыка во время инсталляции операционной системы. Так что запустить ее возможно только непосредственно из каталога.

Дальнейший рассказ о реестре лучше читать имея эту программу запущенной в качестве наглядной иллюстрации. Итак, в меню кнопки "Пуск" выбираем пункт "Выполнить/Run" и вводим туда "С:\WinNT\regedit.exe " (если у васWindowsобитает в каталогеC:\WinNT..., а иначе - надо указывать тот каталог). Запущенная программа показывает окно, разделенное по вертикали на две неравные части. В левой части окна показывается пиктограмма "Мой компьютер" и значок "плюс", означающий, что на самом деле мы имеем внутри скрытое дерево. Развернув дерево мы увидим строки:

HKEY_CLASSES_ROOT HKEY_CURRENT_USER HKEY_LOCAL_MACHINE ...

которые тоже содержат в себе деревья. Эти строки именуют части (parts) системного реестра, каждая часть содержит в себе информацию определенного назначения. Например, часть HKEY_LOCAL_MACHINE содержит в себе информацию, относящуюся к конфигурации данного локального компьютера, а раздел HKEY_CLASSES_ROOT является корневым для информации о классах (статических типах) объектов, известных операционной системе.

Раскройте раздел HKEY_LOCAL_MACHINE, внутри вы увидите:

HARDWARE ... SOFTWARE

которые так же являются деревьями. Далее раскройте раздел HARDWARE, а внутри него - раздел DESCRIPTION, а в нём - раздел System. Правое окно изменится - оно покажет строки типа Identifier, VideoBiosDate и другие. Строкам сопоставлены какие-то значения, сейчас для нас совершенно неважно - какие.

Разделы HKEY_... HARDWARE и т.д. являются разделами реестра, а строки Identifier и VideoBiosDate - параметрами. Внимательный читатель так же мог заметить, что когда показывается раздел, не имеющий в себе ни одного параметра, правое окно все-таки показывает параметр с именем "(По умолчанию/Default)", что означает, что некое значение можно связать не только с параметром, но и с самим разделом.

Естественно, что редактор реестра умеет и операции выполнять, которые перечислены в пунктах его меню - создавать, удалять, искать... И вот здесь самое время сделать очень важное замечание! Системный реестр - очень значимая и ответственная в операционной системе сущность. Повреждение системного реестра практически всегда означает, что какая-то часть функциональности операционной системы "отсыхает" - система перестает о ней что либо знать и "держать во внимании". Конечно, системный реестр специально сконструирован со способностью противостоять сбоям и повреждениям его целостности. Но системный реестр не может противостоять попыткам что-то в нём удалить или переместить из одного места в другое с помощью редактора реестра. Ведь они же, с точки зрения операционной системы, - корректны.

А вот с точки зрения пользователя, cистемный реестр - примитивная база данных. У неё нет ни откатов, ни транзакций. То, что вы "наворотили" прямым ходом записывается на диск и немедленно на нём сохраняется. Поэтому, если вы что-то "не то" удалите или измените в системном реестре - в лучшем случае отделаетесь потерей функциональности какой-либо программы, а в худшем - операционную систему придётся переустанавливать заново. Помните об этом - смотреть в реестре можно все (это же - ваша машина!), а вот что-то удалять или записывать в него - стоит хорошо подумать и твёрдо понимать что именно вы делаете. Попытки феноменологического исследования в реестре "что будет, если снести вот это..." могут очень плохо кончиться.

В современном программировании системный реестр, начиная с какого-то уровня опытности программиста - знакомое понятие. Хоть раз и хоть где-то программист о нём слышал, а может быть - уже и лазал, да не просто из любопытства, но по делу.

Поэтому для большинства не будет новостью, что и COMхранит свою конфигурационную информацию там же. Вопрос в другом - что он там хранит?

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

Итак, в системном реестре (иллюстрирующая наше объяснение программа вызывается на исполнение командой С:\WinNT\regedit.exe, если операционная система у вас расположена по адресуС:\WinNT) можно обнаружить несколько сущностей вида:

HKEY_CLASSES_ROOT HKEY_CURRENT_USER HKEY_LOCAL_MACHINE ...

и других аналогичных.

Они называются "части", являются корнями соответствующих отрастающих от них деревьев и являются предустановленными. Это означает - никакой "своей" части пользователь в реестре завести не может, не может также и переименовать или удалить существующую часть. Назначение частей предопределено, а их имена в написании часто сокращают. Программист COMдолжен знать, что аббревиатура HKCR обозначает часть HKEY_CLASSES_ROOT, а аббревиатура HKLM - HKEY_LOCAL_MACHINE. Думаю, что произвести все другие возможные здесь аббревиатуры не составит никакого труда и самостоятельно.

Адресация в реестре производится аналогично указанию имени файла в файловой системе, но всегда - указанием полного пути. Например, строка:

HKEY_LOCAL_MACHINE\HARDWARE\DESCRIPTION\System\Identifier="abcd"

ссылается на значение "abcd", располагающееся по приведённому пути.

Чем в данном случае является Identifier - разделом или параметром? В такой нотации это не видно да и установить невозможно - раздел, подобно параметру, может иметь значение, с ним ассоциированное. Но расположение этого значения - такой нотацией указывается точно.

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

Для "использования технологией COM" в системном реестре зарезервировано несколько подразделов. Главный, с точки зренияCOM, подраздел это - HKEY_CLASSES_ROOT\CLSID. Если вы откроете его, то увидите, что весь он состоит изGUID, т.е. составляющие его разделы это -GUID. Каждый раздел обозначенныйGUIDсодержит в себе информацию, относящуюся к одномуCOM-объекту, т.к.GUIDи есть "официальное имя объекта". Посмотрим, что это за информация, которая собрана под именемGUID.

На моей машине первым по порядку следования (а порядок следования разделов и параметров в окне редактора реестра - по алфавиту) находится такой раздел:

HKEY_CLASSES_ROOT\CLSID\{00000010-0000-0010-8000-00AA006D2EA4},

которому присвоено значение DAO.DBEngine.35, т.е.

HKCR\CLSID\{00000010-0000-0010-8000-00AA006D2EA4} = DAO.DBEngine.35

Это - объект системы DAOдоступа к данным машиныMicrosoft Jet Database Engine, версии3.5. Его "идентификатор навсегда" на всех машинах установлен разработчиком этого объекта - 00000010-0000-0010-8000-00AA006D2EA4. А вот то, что это объектDAOя просто как-то догадался... :)

Посмотрим также, что скрывается в разделе обозначенном этим GUID. Там располагаются еще два раздела:

HKCR\CLSID\{00000010-0000-0010-8000-00AA006D2EA4}\ProgID

HKCR\CLSID\{00000010-0000-0010-8000-00AA006D2EA4}\InprocServer32

Раздел ProgID пока лежит в стороне от нашего изложения, а вот раздел InprocServer32 очень любопытен, потому, что его значением (параметр Default) является строка:

C:\Program Files\Common Files\Microsoft Shared\DAO\DAO350.DLL

которая есть не что иное, как ссылка на исполняемый модуль, в котором обитает означенный COM-объект. Иными словами, если мы знаемGUIDименующий требуемый нам объект, то нам достаточно посмотреть в системном реестре раздел:

HKCR\CLSID\<наш GUID>\InprocServer32,

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

Рассмотрите еще несколько любых разделов - везде значением параметра InprocServer32 является строка с указанием имени DLL. Но ведь ранее говорилось, чтоCOM-объект может жить и внутриEXE-модуля? И это тоже наблюдается - внутри некоторыхGUID-разделов можно не обнаружить раздела InprocServer32, но обнаруживается раздел с именем LocalServer32. Например, на моей машине таковой первым нашелся уGUID00020800-0000-0000-C000-000000000046:

HKCR\CLSID\{00020800-0000-0000-C000-000000000046}\LocalServer32

А его значением оказалась ссылка на EXE-модуль:

D:\Office\GRAPH9.EXE /automation

который, очевидно, должна запустить система когда от нее клиент потребует предоставить ссылку на объект 00020800-0000-0000-C000-000000000046.

Нет ничего странного в том, что разделы, описывающие DLLиEXE-серверы называются по разному. Ведь и модули этих типов на исполнение загружаются тоже совершенно различно, а система должна иметь возможность различать что есть что.

Кроме того, в разделе LocalServer32 обнаруживается еще и параметр ThreadingModel, значением которого является слово Apartment. Этот параметр описывает потоковую модель в которой может работать компонент (и подробное рассмотрение его случится в нашей же рассылке, но - значительно позднее). Смысл параметра - если компонент вызывается из программы, владеющей несколькими потоками, то как следует его вызывать, а точнее - что "умеет компонент" по управлению конкурентным исполнением внутри себя, а что должна делать вызывающая сторона.

И в заключение - что такое раздел ProgID связанный со статическим типом? Это - "человеческое имя" данного статического типа. Хотя GUIDсовершенно однозначно идентифицирует статический тип в некоторых случаях бывает удобнее пользоваться всё-таки мнемонически значимым именем. Раздел ProgID и связывает это мнемоническое имя сGUID. Внешнее (в реестре), а не внутреннее (вCOM-сервере) связывание в данном случае совершенно оправданно - если на данной конкретной машине и возникнет конфликт мнемонических имён, то ведь мнемоническое имя можно легко и сменить, в то время, какGUIDникогда и нигде сменить невозможно.

Для завершения подробной иллюстрации как 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.exe /EXPORTS C:\Program Files\Common Files\Microsoft Shared\DAO\DAO350.DLL > tttt.txt

Переназначение вывода 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. Она - та самая функция, вызывая которую система сообщает серверу ссылку на какой объект он должен изготовить и предоставить системе. Вот прототип этой функции:

STDAPI DllGetClassObject(REFCLSID rclsid, //CLSID объекта класса

REFIID riid, //Ссылка на идентификатор интерфейса,

//который взаимодействует с объектом

LPVOID * ppv //Адрес выходной переменной, которая принимает

//указатель на интерфейс, указанный riid

);

т.е. система при вызове данной функции готовит список аргументов <CLSID> - <ссылка на интерфейс> - <адрес переменной, куда вернуть результат>. ФункцияDllGetClassObjectпринадлежитDLL, т.е., видимо, как-то умеет производить объекты запрошенного при помощиCLSIDимени. Адрес произведенного объекта система ожидает получить посредством указателяppv. Напомним, чтоCLSID- не что иное, как имяCOM-объекта, это -CLaSsIDentifier.

Откуда приходят все эти параметры? Они приходят от COM-клиента, который захотел получить экземпляр объекта, именуемого даннымCLSID. Для того, чтобы система выяснила какой сервер необходимо активизировать (просмотрев системный реестр), загрузила его, и вызвала у этого сервера функциюDllGetClassObjectклиент на своей стороне вызывает "широко известную в узких кругах" функциюWin32 APICoCreateInstance- создать экземпляр объекта.MSDNдает о ней следующую справку:

STDAPI CoCreateInstance(REFCLSID rclsid, //CLSID объекта класса

LPUNKNOWN pUnkOuter, DWORD dwClsContext,

REFIID riid, //Ссылка на идентификатор интерфейса,

//который взаимодействует с объектом

LPVOID *ppv //Адрес выходной переменной, которая принимает

//указатель на интерфейс, указанный riid

);

Аргументы pUnkOuterиdwClsContextв данный момент нас не интересуют, они указывают системе средиDLLилиEXE-серверов искать интересующий нас объект и агрегировать ли его. А вот три других аргумента - в точности те, которые передаются системой функции сервераDllGetClassObject. И - функциюCoCreateInstanceклиент вызывает из своего кода. Иными словами - вызов клиентом функцииCoCreateInstance"транслируется" системой в вызов функцииDllGetClassObjectна стороне сервера.DllGetClassObjectвозвращает адрес объекта, система передает егоCoCreateInstance, которая возвращает его клиенту. Всё, наша экскурсия в философию закончена - пункты с первого по четвёртый проиллюстрированы сущностями операционной системы.

Но вот откуда берется аргумент riidи что он означает? И сам этот вопрос и ответ на него старательно обходились с начала нашего изложения, а между тем компонентное программирование вообще-то начинается с ответа на этот вопрос.riidесть идентификаторинтерфейса. В пункте пятом нашего философского изложения стояло - "как взаимодействовать между собой объекты и сами знают...". На самом деле, вCOMобъекты взаимодействуют друг с другом посредством интерфейсов и - никак иначе.