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

Как выгрузить то, что не загружал?

Тема данной статьи - функция inproc-сервера DllCanUnloadNow. Проблема, которую она решает состоит в следующем. Сервер - динамический ресурс. Система его загружает в процесс, к нему обратившийся. И система же должна иметь возможность его корректно выгрузить, когда сервер перестанет быть нужным.

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

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

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

Из этой статьи вы знаете, что владельцем объекта является сервер, а клиент является только его пользователем. Пользователь сообщает серверу о своих желаниях вызывая методы AddRef и Release, а сервер смотрит состояние счётчика ссылок и в какой-то момент принимает решение уничтожить объект, потому, что он больше никому не нужен. Подобное должно происходить и в данном случае, только владельцем сервера является уже операционная система (она вызывала LoadLibrary)... Интересно, а кто является в данном случае "пользователем сервера"? "Пользователем сервера" являются все его клиенты... Ведь всякий раз, когда для какого-то объекта, реализуемого данным сервером откуда-то кем-то вызывается AddRef это же означает и то, что "AddRef" вызывается и для всего сервера в целом. Поэтому в данном случае и нужен только один метод - метод, который бы информировал операционную систему о текущем состоянии сервера - нужен ещё он кому-то или уже нет. Я особо обращаю ваше внимание, что метод нужен только один - если объект при освобождении занятых им ресурсов может вызвать delete this (почему - подробно разбиралось ранее), то сервер в отношении себя вызвать FreeLibrary не может - если FreeLibrary вызывается из того же модуля, который удаляется, то, после того, как она успешно отработает, DLL перестанет существовать. И куда, в данном случае, должна привести инструкция процессора ret, которая возвращает управление в код уже несуществующей DLL?

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

Функция DllCanUnloadNow - функция, предназначенная для вызова только из операционной системы. Вызывать её из своего клиента бессмысленно - ведь функция ничего не делает...

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

Если сервер реализовывал однопоточную модель (STA, об этом в нашем же изложении, но от этой статьи ещё весьма далеко), то система вызывает в отношении него функцию CoFreeLibrary и действительно освобождает DLL. Если же сервер реализовывал модель свободных потоков (MTA), или модель нейтральных потоков (NA), то система помещает данную DLL в список "кандидатов на удаление" и устанавливает "таймер задержки" на 10 минут, откладывая вызов CoFreeLibrary. Это сделано по причинам, связанным исключительно с мультизадачностью (параллельный поток, который может повлиять на судьбу DLL может спать и не быть в состоянии себя проявить). Если будет сделан второй вызов CoFreeUnusedLibraries, то этот второй вызов освободит те DLL из списка кандидатов на удаление, чей интервал ожидания истёк, и поместит новые DLL в список кандидатов, если такие DLL есть к этому моменту. Величина интервала в 10 минут установлена волюнтаристски самой Microsoft и не может быть как-то настроена - это данность.

Естественно, что если в течение этого 10-тиминутного интервала последует обращение к серверу, находящемуся в списке кандидатов на удаление, то он удаляется из списка, вновь становится активным - он еще не был выгружен из памяти, поэтому все его функции работоспособны. Загружать такой сервер вновь системе не нужно. Но для клиента такой механизм совершенно прозрачен - он просто пытается "поднять объект" по имеющемуся у него CLSID. Явления связанные с фактической загрузкой/выгрузкой сервера можно заметить только косвенно - например, по скорости загрузки или по тому, что файл "выгруженного" сервера система не даст переписать...

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

Вот и почти все экспортируемые функции inproc-сервера, которые известны. Они - достаточны для всех случаев жизни сервера. Но, точности ради, нужно упомянуть и о пятой, "призрачной" функции, которую тоже может экспортировать сервер. Эта функция называется DllRegisterServerEx. Как можно заметить по названию она выполняет какие-то "расширенные" действия по регистрации сервера, по сравнению с функцией DllRegisterServer. Но какие? Тайна сия велика есть! В MSDN для этой функции не найти прототипа, ссылки на то, где она встречается приносят две статьи про скрипты в которых говорится, что "...регистрация сервера производится функциями DllRegisterServer или DllRegisterServerEx...". В Большой Сети есть несколько конференций где вопрос о том, что такое DllRegisterServerEx уже давно есть, а ответа на него пока нет... Я думаю, что изобретение этой функции было каким-то неудачным экспериментом Microsoft, но "в целях совместимости" эта функция продолжает экспортироваться некоторыми серверами и поныне... Во всяком случае, на практике обнаруживается, что и без неё всё хорошо работает.

А вот как оно работает - в следующей статье, где приведен пример уже "настоящего COM-сервера".

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

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

форум

технология COM

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


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