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

Как сфабриковать объект?

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

Я должен заметить, что с обоих флангов нечасто, но регулярно, получаю письма - одни недовольны наличием чрезмерного количества "теории", другие - тем, что "всё о ремесле, да о ремесле...". Сейчас мы рассматриваем интерфейс IClassFactory, потом - IClassFactory2, после этого мы немного осмотримся и сложим знания воедино, а затем - напишем пример уже самого настоящего и полноценного COM-сервера. А вот после этого - наступит кризис жанра. Если уподоблять наше изучение COM альпинистскому восхождению на вершину, то сейчас мы по очень пологому склону поднимаемся к учебно-тренировочному лагерю. И через несколько следующих статей достигнем его. Там мы сможем некоторое время "походить кругами по поляне" углубляя наши знания о тех или иных аспектах COM. Назовём некоторые - множественное наследование и интерфейсы, механизм proxy/stub, маршалинг, IDL и библиотеки типов, агрегирование, автоматизация OLE, передача ошибки... Совершенно нерассмотренным остаётся также и конструкция COM-клиента, т.е. наши представления о клиенте так и остаются представлениями, что он только лишь вызывает виртуальный метод.

Ну а сейчас об интерфейсе IClassFactory. Реализуется он "объектом типа", а назначение его - делать "объекты экземпляров" данного типа. Вот список его методов (в порядке vtbl):

  1. QueryInterface
  2. AddRef
  3. Release
  4. CreateInstance
  5. LockServer

Интерфейс состоит из двух "своих" методов - CreateInstance и LockServer,а первые три метода унаследованы им от IUnknown. Метод CreateInstance - "основной рабочий" метод этого интерфейса, именно он "создаёт экземпляры", метод LockServer - вспомогательный и о нём чуть ниже. Повторюсь - реализован интерфейс должен быть в "объекте типа", поэтому последовательность действий, которую выполняет клиент для того, чтобы получить ссылку на "объект экземпляра" выглядит так:

 IClassFactory * ptr1; ::CoGetClassObject(CLSID, ... ,IID_IClassFactory,ptr1);   IUnknown * ptr2; HRESULT hr = ptr1->CreateInstance(pUnkOuter,IID_IUnknown,ptr2);       ...   ptr1->Release();

При этом ptr1 - указатель на "объект типа", а ptr2 - указатель на "объект экземпляра", который как раз и создаётся методом CreateInstance. После создания экземпляра клиент может освободить "объект типа" (ptr1), а может, если ему требуются и ещё экземпляры, продолжать вызывать CreateInstance. Каждый вызов будет доставлять ему новую независимую ссылку на объект экземпляра.

Слово "независимая" специально подчёркнуто. Очень давно в нашей серии мы говорили о том, что в ответ на запрос клиента о предоставлении ссылки на объект сервер может всякий раз создавать новый объект, а может - выдавать всем один и тот же адрес. Что именно сервер выдаёт - дело сервера, т.е. как мы теперь видим - дело метода CreateInstance. И, хотя в спецификации метода IClassFactory::CreateInstance подчёркнуто, что "...создаёт неинициализированный объект указанного CLSID типа...", интерфейс не определяет семантики - CreateInstance может выдавать всем ссылку на один и тот же (статический, по-сути) объект. Сказанное - только возможность, которая может понадобиться в каком-то случае, т.к. в подавляющем большинстве "нормальных" реализаций CreateInstance действительно создаёт новый экземпляр пользуясь операцией new. Но и в том и в другом случае клиент должен считать, что он получил совершенно новую ссылку и что созданный объект - не проинициализирован.

Для вызова CreateInstance клиент должен обеспечить три параметра - указатель на IUnknown агрегата (pUnkOuter), IID интерфейса, который реализует "объект экземпляра" и указатель, куда будет помещена ссылка. Напомню, что во всех случаях (и при вызове CoGetClassObject и CreateInstance и в любом другом случае, когда "возвращается ссылка") счётчик ссылок продвигается "внутри", т.е. клиент не должен для этого указателя вызывать AddRef - кто ссылку создаёт, тот её и первый раз "защёлкивает".

Смысл параметров должен быть понятен из всех предыдущих статей, а IUnknown агрегата мы рассмотрим, когда будем изучать агрегирование. Если создаётся не агрегированный, а независимый объект, этот параметр должен иметь значение NULL.

CreateInstance - метод COM, и в этом качестве возвращает значение типа HRESULT, индицирующее успешность создания экземпляра. Как обычно, значение S_OK сообщает о полной успешности, но метод также может возвращать и состояния ошибки. Вот некоторые из них:

E_UNEXPECTED- неожиданная ошибка
E_OUTOFMEMORY- нехватка памяти
E_NOINTERFACE- запрошенный интерфейс не поддерживается объектом
E_INVALIDARG- неверный аргумент
CLASS_E_NOAGGREGATION- объект не поддерживает агрегацию

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

Как должен быть реализован CreateInstance - понятно. В примерах в нашем изложении мы создали практически все программные конструкции, необходимые для этого. Так что их теперь осталось только объединить "под одним методом". Но я думаю, что не стоит торопиться с кодом. Давайте выясним, а что делает метод LockServer и как он попал именно в этот интерфейс?

Метод LockServer выполняет простую и вспомогательную задачу - он блокирует сервер в загруженном состоянии. Этим я хотел сказать, что LockServer есть аналог AddRef, только - на уровне всего сервера. Вызов его LockServer(TRUE) приводит к тому, что "счётчик блокировок сервера продвигается вперёд на единицу", а LockServer(FALSE) - производит обратное действие. Когда "счётчик блокировок сервера" достигает нуля, то... сервер может быть выгружен.

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

Понимая это можно понять и то, что в DLL-сервере функциональность этого метода несколько избыточна - DLL-сервер сам не выгружается. И система его по своей инициативе тоже не выгружает, она дожидается, когда клиент из себя вызовет CoFreeUnusedLibraries. А клиент может её и не вызывать. Тем не менее, единообразия ради, и inproc-серверы обязаны реализовывать этот метод. Так же, как и EXE. Наверное, понятно, что в этом случае метод LockServer просто влияет на значение, которое возвращает системе функция DllCanUnloadNow.

Поэтому реализации всех IClassFactory, в одном сервере по сути, будут разделять один и тот же счётчик блокировок сервера, а вот методы CreateInstance у каждой из них будут свои. В примере я также покажу одно очень интересное решение по реализации AddRef/LockServer - словами его излагать долго и топорно, но код - очень изящен.

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

Почему CreateInstance - конструктор именно по умолчанию? Разве нельзя было бы предусмотреть ещё один параметр? Предусмотреть-то можно, да... зачем? Ведь явное понятие "конструктор" есть в C++, а вот в VB это реализуется совсем уже по-другому, а где-нибудь ещё и вовсе понятия конструктора нет (VBA). А COM - двоичная технология, COM-сервер должен работать везде, с любым клиентом. Именно поэтому минимум и ограничен только таким конструктором. Ибо в том языке (из того клиента) где этого понятия нет его всё равно можно вызвать неявно, без указания каких-либо параметров. Следовательно, правильнее было бы считать, что понятие "конструктор объекта" относится не к методу, но к целому интерфейсу. И интерфейс IClassFactory как раз и есть интерфейс "конструктора по умолчанию". Если ваш клиент способен понимать другие конструкторы вы можете реализовать и другие интерфейсы фабрики класса - именно те, которые вам нужны.

На этом на сегодня всё. В следующей статье - об очень интересном интерфейсе IClassFactory2

предыдущий выпускархив и оглавлениеследующий выпуск

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

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

форум

технология COM

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


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