developing.ru - клуб программистов Здесь может быть и ваша реклама.
developing.ru >технология COM >
Михаил Безверхов
aspid@developing.ru

Железный век на смену веку бронзовому

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

Ход конструирования. Берем предыдущий пример. То, что делает сам сервер "с содержательной стороны" сегодня нас вполне устраивает. Поэтому ни интерфейсы, ни объекты мы изменять не будем. Мы только добавим к серверу реализации DllRegisterServer, DllUnregisterServer и DllCanUnloadNow, а реализация DllGetClassObject у нас и так существует с самого начала.

В клиенте нас тоже всё устраивает, поэтому единственной заменой будет замена вызова CoGetClassObjectEmulator (который реализовывался самим клиентом) на вызов "настоящей системной" CoGetClassObject. Ещё, конечно, в клиент добавятся вызовы инициализации COM - CoInitialize, которая должна быть вызвана клиентом перед тем, как клиент намерен воспользоваться сервисом COM, и CoUninitialize, которая должна быть вызвана когда COM клиенту больше не нужен. Эти функции, в известном смысле, являются рудиментом - некогда COM, тогда ещё "носивший девичью фамилию" OLE, был совершенно отдельной, не встроенной в операционную систему подсистемой. Оформленной в виде модуля OLExxx.DLL, которую система загружала "по требованию". Сейчас COM встроен в операционную систему, но вызов этих функций по прежнему требуется.

Что мы получим? Мы получим возможность "по настоящему" зарегистрировать наш сервер в системе. Т.е. если во всех прошлых примерах сервер и клиент обязаны были обитать в одном каталоге - так был написан эмулятор - то сейчас сервер можно регистрировать из произвольного места файловой системы. И клиента можно располагать в произвольном месте файловой системы - клиент более не обязан ничего знать о том, где физически располагается сервер. А больше мы ничего не получим - наш нынешний сервер отличается от предыдущего действительно примерно так же, как железный топор от бронзового... оставаясь при этом топором.

Исходные тексты примера №3 находятся здесь. Они хорошо откомментированы, как обычно - проекты нужно развернуть и собрать в исполняемые модули. Внутри проекта сервера в ключевых точках расставлены вызовы функции MessageBox, которая выдаёт транспарант с надписью на экран. Таких точек несколько - в DllMain при вызове DLL_PROCESS_ATTACH и DLL_PROCESS_DETACH, в добавляемых нами экспортируемых функциях и там, где эти вызовы были в предыдущем примере. В совокупности вы должны увидеть последовательность взаимодействия частей на разных этапах жизни сервера в виде событий, "освещаемых" MessageBox.

DllMain - системой определенная процедура, реализуемая любой DLL, которая вызывается системой же в четырёх случаях:

  • при загрузке DLL в поток, инициировавший её загрузку - DLL_PROCESS_ATTACH
  • при создании нового потока в процессе, владеющем DLL - DLL_THREAD_ATTACH
  • при завершении не последнего потока в процессе - DLL_THREAD_DETACH
  • при завершении последнего потока в "жизни DLL" - DLL_PROCESS_DETACH

Поэтому, "отлавливая" событие DLL_PROCESS_ATTACH можно выяснить когда на самом деле данная DLL вызвается к жизни, а "отлавливая" DLL_PROCESS_DETACH - когда она выгружается. Поскольку в нашем примере используется только один поток, то события DLL_THREAD_ATTACH и DLL_THREAD_DETACH не возникают совсем.

Итак, первым шагом после сборки модуля сервера NeoSrv3.dll необходимо его зарегистрировать. Регистрация производится функцией DllRegisterServer, которая вписывает в системный реестр минимально необходимую информацию о двух статических типах - CBanzai и CHello. Эта информация состоит только из параметра InprocServer32, который сообщает системе в какой DLL эти статические типы реализованы.

Для вызова функций DllRegisterServer и DllUnregisterServer операционная система располагает специальной утилитой regsvr32.exe, которая обычно обитает в каталоге Windows\System32. Если эту утилиту запустить из командной строки (через меню "Пуск -> Выполнить", например) без параметров, то она выведет на экран транспарант с описанием списка своих ключей. Если её запускать с параметром, то она предполагает, что параметр этот - имя DLL, которую она должна загрузить чтобы вызвать у этой DLL какую-то экспортируемую функцию. Например, командная строка:

regsvr32    <имя DLL>

приводит к тому, что утилита загружает DLL с указанным именем и вызывает у неё экспортируемую функцию DllRegisterServer, если, конечно, такая функция у DLL обнаружится... А командная строка:

regsvr32 /u <имя DLL>

заставит вызвать у DLL экспортируемую функцию DllUnregisterServer.

Это воочию можно и увидеть - в ответ на команду с консоли:

regsvr32 NeoSrv3.dll

будет последовательно показано четыре транспаранта (на каждом нужно нажать кнопку "OK"): "DLL_PROCESS_ATTACH" -> "вызов DllRegisterServer" -> "DllRegisterServer in NeoSrv3.dll succeeded" -> "DLL_PROCESS_DETACH", из них первый, второй и четвёртый - наши, а третий вывешивает сама regsvr32.exe. Стоит только отметить, что команда эта должна быть выдана из того каталога, где обитает NeoSrv3.dll - в другом случае нужно указывать и спецификацию пути к модулю.

Сказанное можно не принимать на веру, а проверить - запустить программу regedit.exe и в разделе HKEY_CLASSES_ROOT\CLSID\ найти два параметра (они записаны в реестр подряд) {3F9D5DB0-3413-11d5-AE38-00E02944637A} и {3F9D5DB1-3413-11d5-AE38-00E02944637A} и убедиться, что значение (под)параметра InprocServer32 у них действительно - полная спецификация пути к модулю NeoSrv3.dll

Теперь наш сервер можно использовать. Можно запускать клиента - модуль NeoCln3.exe Правила обращения с ним - что можно и нужно делать - подробно изложены в описании предыдущего примера, здесь мы упомянем только интересные феномены.

Клиент запускается "в одиночку". Это и видно - никаких транспарантов не появляется. Т.е. наш сервер вызывается только по мере надобности - стоит нажать кнопку "Создать Banzai" или "Создать Hello", как немедленно появляется транспарант "DLL_PROCESS_ATTACH". Это система отыскала сервер и пытается загрузить его на выполнение. После нажатия кнопки "ОК" на транспаранте появляется сам заказанный объект - сервер нашёлся, загрузился и у него отработал вызов экспортируемой функции DllGetClassObject, который возвратил ссылку на заказанный объект. Далее у сервера можно попросить и другие ссылки - транспарант "DLL_PROCESS_ATTACH"  больше не появляется, ведь сервер уже загружен. Но вот мы избавились от всех ссылок, а сервер не выгружается... - верно, в предыдущей статье было описано такое поведение. Если мы вновь будем запрашивать у сервера ссылки, то сервер нам будет готов служить вновь - без всяких загрузок в процесс. Попробуем завершить клиента - нажмём "Закрыть панель". И тут мы увидим транспарант "вызов DllCanUnloadNow" - это система "наткнулась" на вызов UnInitialize и отрабатывает завершение COM. А после него последует событие DLL_PROCESS_DETACH - система выгружает сервер... Всё!

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

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

regsvr32 /u NeoSrv3.dll

вы увидите уже знакомую последовательность транспарантов "DLL_PROCESS_ATTACH" -> "вызов DllUnregisterServer" -> "DllUnregisterServer in NeoSrv3.dll succeeded" -> "DLL_PROCESS_DETACH", а после этого попробуйте поискать зарегистрированные нами параметры реестра...

Единственное маленькое замечание - regedit.exe не всегда корректно обновляет своё представление реестра, т.е. если вы сначала запустили regedit.exe, открыли раздел HKEY_CLASSES_ROOT\CLSID и только потом вы выдали команду regsvr32.exe NeoSrv3.dll , то велика вероятность, что зарегистрированных параметров вы не обнаружите. Аналогичное будет наблюдаться и при разрегистрации - вы будете продолжать видеть уже удалённые параметры. Это не означает, что изменения в реестр не вносятся, это означает только, что regedit.exe показывает статическую картину и его нужно перезапустить.

Почему так написан пример и что главное в нём - в следующей статье...

предыдущий выпускархив и оглавлениеследующий выпуск
Авторские права © 2001 - 2004, Михаил Безверхов
Публикация требует разрешения автора

разделы сайта

форум

технология COM

оптимизация сайтов


© 2000-2004 Клуб программистов developing.ru