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

О сервере бедном замолвите слово...

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

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

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

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

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

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

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

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

Мы обязательно доберемся до этой, очень интересной, темы и всесторонне рассмотрим её во множестве аспектов (собственно, именно это и есть тема всей нашей рассылки), но сейчас предмет нашего рассмотрения значительно скромнее - формат модуля, который гордо именуется "COM-сервер".

Здесь нужно сказать, что канонически известно два типа COM-серверов - загружаемые в адресное пространство клиентского процесса, и работающие в адресном пространстве другого процесса. Последний тип сервера также рассматривается в двух разных качествах - работающий в параллельном процессе на той же самой машине (в одном и том же экземпляре операционной системы) и работающий на другой машине (предмет, реализация которого некогда называлась DCOM - DistributedCOM). Такая классификация имеет под собой смысл, поскольку построена на сложности преодоления границы между клиентом и сервером. Самый простой случай, когда граница фактически отсутствует - реализация сервера в виде DLL. Такой сервер называется inproc-server, при его использовании от системы в большинстве случаев требуется только его запуск. Именно его конструкцию мы и будем рассматривать подробно. Другие типы серверов (а фактически - типы клиент-серверного взаимодействия) требуют значительно бОльших усилий операционной системы по обслуживанию вызова клиентом метода сервера и, к сожалению, значительно бОльших знаний всего вместе о COM тоже.

Ранее мы обнаружили, что inproc-сервер не может совсем ничего не экспортировать. Хотя бы одну функцию (мы назвали её DllGetClassObject) сервер экспортировать должен. И связано это с системной спецификацией доступа к DLL. Затем, когда программа-клиент уже "пришла в соединение с сервером и получила ссылку на объект" клиент и сервер могут взаимодействовать и без посредничества механизма экспортируемых функций.

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

С чем они связаны? Одна уже известна - она связана с получением ссылок на объекты, реализуемые сервером. Две других связаны с регистрацией сервера в реестре и удалением регистрационной информации из реестра. Вспомните, в статьях С чего начинается COM? и ... и что именно COM хранит говорилось, что имя сервера клиенту знать необязательно. Клиенту достаточно знать GUID объекта, а система, просматривая системный реестр, в состоянии выяснить какой модуль для получения этого объекта надо запустить. Только вот информация эта в реестр как-то должна попадать. И одной из функций, возложенных на сам COM-сервер как раз и является внесение информации о реализуемых им объектах в системный реестр. Разумеется, раз есть инсталляция, то должна быть и деинсталляция - чтобы не вводить систему в заблуждение, при деинсталляции сервер должен удалить информацию о реализуемых им объектах. Это - очень разумное решение, поскольку сам сервер о себе уж точно всё знает. И как либо иначе побудить сервер зарегистрировать себя способа нет - пока сервер не зарегистрирован система не знает о нём ничего. Поэтому способ "вызвать специальную экспортируемую функцию DLL" для этого - единственное возможное решение.

А вот наличие четвёртой функции - неочевидно. Хотя её наличие совершенно необходимо, если немного подробнее представить себе взаимодействие "клиент - система - сервер". Итак, клиент обращается к системе. Система в реестре отыскивает путь к модулю сервера. Система выполняет функцию LoadLibrary и загружает DLL в процесс. Далее система обращается к функции DllGetClassObject, получает указатель и передаёт его клиенту. Клиент, получив указатель, начинает им распоряжаться совершенно самостоятельно, без вмешательства операционной системы... Всё хорошо, но! Когда клиент освободит последнюю ссылку на последний объект этого сервера это ведь будет означать, что и сервер больше не нужен и система может его выгрузить, освободив занятые им ресурсы? А как системе об этом узнать? Ведь в отличие от объекта, вызывающего delete this, сервер как раз не может в отношении самого себя вызвать FreeLibrary! Поэтому четвёртая функция сообщает системе, может ли сервер быть выгружен из памяти в данный момент. Это тоже разумно - если есть возможность сервер загружать только по требованию клиента, то ведь можно и выгружать его "при отсутствии требований". Но выгрузить его "при живых объектах" нельзя, а сам клиент сервер не загружает. Сервер загружает система. Поэтому и выгружать сервер должна система, а сообщать о том, может ли сервер быть выгружен, вынужден сам сервер. Что он и делает, реализуя данную функцию - система вызывает её и, если функция возвращает "да", - выгружает такой сервер.

Вот эти четыре функции, которые называются:

  1. DllGetClassObject
  2. DllRegisterServer
  3. DllUnregisterServer
  4. DllCanUnloadNow

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

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

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

форум

технология COM

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


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