Уже появились машинки, которые называют трехмерными принтерами (3D-printers), — и это название очень им подходит. Они похожи на струйные принтеры, но выбрасывают тонкую струю пластика под давлением, а не чернила. Такой принтер наносит пластмассу слоями тоньше миллиметра. Повторная «печать» по одному и тому же месту позволяет создать сложные трехмерные объекты. Их можно использовать как прототипы или формы для изготовления деталей, а иногда и как готовые детали. С такой машинкой можно было бы организовать Домашнюю Фабрику Пластиковых Деталей. При помощи пакета САПР можно было бы в мгновение ока проектировать и производить новые детали. На такой домашней фабрике Вы могли бы сделать ту хитрую детальку с переходом 1х3, без которой никак не собиралась вся модель. Вообще у Вас больше не было бы недостатка в деталях - хотя компания Lego, вероятно, предпочла бы все же продавать Вам свои.
В этой главе я собираюсь рассмотреть своего рода фабрику, на которой производятся не детали Lego, а компоненты. Эта фабрика класса — просто компонент с интерфейсом для создания других компонентов, так что обойдется она нам дешевле, чем трехмерный принтер за 50000 долларов.
Но прежде чем заняться фабрикой класса, мы познакомимся с самым простым способом создания компонентов — при помощи функции CoCreateInstance. He удивительно, что этим способом пользуются чаще всего. К сожалению, он недостаточно гибок и годится не для всех компонентов. Как раз здесь, с точки зрения клиента, и появляется фабрика класса. Все компоненты создаются на этой фабрике - CoCreateInstance при создании компонента тоже пользуется ее услугами, но неявно и незаметно для вызывающей программы. Клиент получает большую свободу в создании компонентов, если он прямо использует фабрику. Точно так же, как у Вас было бы больше возможностей, если бы вы не покупали детали Lego у фирмы, а делали их сами, на Домашней Фабрике Пластиковых Деталей.
Для создания компонентов в библиотеке СОМ служит функция CoCreateInstance, которая, получив CLSID, создает экземпляр соответствующего компонента и возвращает интерфейс этого экземпляра. В этом разделе мы рассмотрим использование CoCreateInstance и увидим, с какими ограничениями оно связано. Но сначала давайте посмотрим на саму функцию.
HRESULT __stdcall CoCreateInstance( const CLSID& cisid, IDnknown* pIUnknownOuter, // Внешний компонент DWORD dwClsContext, // Контекст сервера const IID& iid, void** ppv );У функции четыре входных параметра (in) и единственный выходной (out). Первый параметр — CLSID создаваемого компонента. Второй параметр используется для агрегирования компонентов и будет обсуждаться в следующей главе. Третий параметр — dwClsContext— ограничивает контекст исполнения компонента, с которым данный клиент может работать. Этот параметр мы рассмотрим позже.
Четвертый параметр, iid— это IID интерфейса, который мы хотим использовать для работы с компонентом. Указатель на этот интерфейс возвращается через последний параметр — ppv. Поскольку в CoCreateInstance передается IID, клиент может не вызывать QueryInterface для созданного компонента.
// Создать компонент IX* рIХ = NULL; HRESULT hr = ::CoCreateInstance(CLSID_Component1, NULL, CLSCTX_INPROC_SERVER, IID_IX, (void**)&pIX); if (SUCCEEDED(hr)), { pIX->Fx() ; pIX->Release() ; }В данном примере мы создаем компонент, задаваемый CLSID_Componentl. Мы не агрегируем его, поэтому значением второго параметра является NULL. В следующей главе мы будем передавать функции значение, отличное от NULL. Параметр CLSCTX_INPROC_SERVER заставляет CoCreateInstance загружать только те компоненты, которые содержатся в серверах в процессе или в DLL.
Значения, передаваемые CoCreateInstance ткачестве двух последних параметров, — те же самые, которые мы передавали бы Qwrylnterface. В данном примере мы передаем IID_IX, чтобы запросить интерфейс IX, который возвращается указателем р1Х. Если вызов CoCreateInstance был успешным, то интерфейс IX готов к работе. Освобождение же интерфейса IX указывает, что клиент завершил использование и этого интерфейса, и самого компонента.
CLSCTX_INPROC_SERVER | Клиент принимает только компоненты, которые исполняются в одном с ним процессе. Подобные компоненты должны быть реализованы в DLL. |
CLSCTX_INPROC_HANDLER | Клиент будет работать с обработчиками в процессе. Обработчик в процессе - это компонент внутри процесса, который реализует только часть компонента. Другие части реализуются компонентом вне процесса - локальным или удаленным сервером. |
CLSCTX_LOCAL_SERVER | Клиент будет работать с компонентами, которые выполняются в другом процессе, но на той же самой машине. Локальные серверы реализуются в EXE, как мы увидим в гл. 10. |
CLSCTX_REMOTE_SERVER | Клиент допускает компоненты, выполняющиеся на другой машине. Использование этого флага требует задействования DCOM. Мы рассмотрим его в гл. 10. |
Константы | Значения |
CLSCTX_INPROC | CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER |
CLSCTX_ALL | CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER | CLSCTX_LOCAL_SERVER | CLSCTX_REMOTE_SERVER |
CLSCTX_SERVER | CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER | CLSCTX_REMOTE_SERVER |
// // Client.cpp - реализация клиента // #include#include #include "Iface.h" void trace(const char* msg) { cout << "Client: \t\t" << msg << endl ;} // // main function // int main() { // Инициализация библиотеки COM CoInitialize(NULL) ; trace("Call CoCreateInstance to create") ; trace(" component and get interface IX.") ; IX* pIX = NULL ; HRESULT hr = ::CoCreateInstance(CLSID_Component1, NULL, CLSCTX_INPROC_SERVER, IID_IX, (void**)&pIX) ; if (SUCCEEDED(hr)) { trace("Succeeded getting IX.") ; pIX->Fx() ; // Use interface IX. trace("Ask for interface IY.") ; IY* pIY = NULL ; hr = pIX->QueryInterface(IID_IY, (void**)&pIY) ; if (SUCCEEDED(hr)) { trace("Succeeded getting IY.") ; pIY->Fy() ; // Use interface IY. pIY->Release() ; trace("Release IY interface.") ; } else { trace("Could not get interface IY.") ; } trace("Ask for interface IZ.") ; IZ* pIZ = NULL ; hr = pIX->QueryInterface(IID_IZ, (void**)&pIZ) ; if (SUCCEEDED(hr)) { trace("Succeeded in getting interface IZ.") ; pIZ->Fz() ; pIZ->Release() ; trace("Release IZ interface.") ; } else { trace("Could not get interface IZ.") ; } trace("Release IX interface.") ; pIX->Release() ; } else { cout << "Client: \t\tCould not create component. hr = " << hex << hr << endl ; } // Закрыть библиотеку COM CoUninitialize() ; return 0 ; }
Выше мы видели, что CoCreateInstance получает CLSID, создает соответствующий компонент и возвращает требуемый указатель на интерфейс. В большинстве случаев CoCreateInstance вполне достаточно. Однако CoCreateInstance недостаточно гибка, чтобы предоставить клиенту способ управления процессом создания компонента. Когда CoCreateInstance возвращает управление, компонент уже создан. Уже поздно управлять тем, где он загружается в память, или проверять, есть ли вообще у клиента право создавать компонент.
Проблема состоит в том, как управлять созданием компонента. Нам не нужно беспокоиться об управлении инициализацией компонента. Компонент нетрудно инициализировать через интерфейс, который можно запросить после создания. Но Вы не можете получить интерфейс компонента до тех пор, пока тот не создан, — а тогда уже поздно задавать условия создания.
Решение состоит в том, чтобы явно использовать другой компонент, единственным назначением которого будет создание нужного нам компонента.
На самом деле CoCreateInstance не создает компоненты непосредственно. Вместо этого она создает компонент, называемый фабрикой класса (class factory) который затем и порождает нужный компонент. Фабрика класса — компонент, единственной задачей которого является создание других компонентов. Точнее, конкретная фабрика класса создает компоненты, сооответствующие только одному конкретному CLSID. Клиент использует поддерживаемые фабрикой класса интерфейсы для управления тем, как фабрика создает каждый компонент. Стандартный интерфейс создания компонентов — IClassfactory. Компоненты, создаваемые CoCreateInstance, порождаются именно при помощи этого интерфейса.
Теперь давайте посмотрим, как клиент может создать компонент, напрямую используя фабрику класса. Первый шаг — создание самой фабрики. Когда фабрика класса создана, мы используем некоторый интерфейс, подобный IClassFactory, для окончательного создания своего компонента.
Объявление CoGetClassObject показано ниже:
HRESULT __stdcall CoGetClassObject( const CLSID& clsid, DWORD dwClsContext, COSERVERINFO* pServerInfo, // Зарезервировано для DOOM const IID& iid, void** ppv );Как видите, CoGetClassObject очень похожа на CoCreateInstance. Первым параметром обеих функций является CLSID нужного компонента. Обеим также передается контекст выполнения — dwClsContext. Два последних параметра тоже совпадают. Но CoGetClassObject возвращает запрашиваемый указатель для фабрики класса, тогда как CoCreateInstance — для самого компонента. Отличаются эти функции только одним параметром. CoCreateInstance принимает указатель IUnknown, тогда как CoGetClassObject - указатель на COSERVERINFO. COSERVERINFO используется DCOM для управления доступом к удаленным компонентам. Мы рассмотрим эту структуру в гл. 10.
Повторю, существенное различие между CoGetClassObject и CoCreateInstance состоит в том, что первая возвращает указатель, относящийся к фабрике класса нужного нам компонента, а не к самому компоненту. Требуемый компонент создается при помощи интерфейса, указатель на который возвращает CoGetClassObject. Обычно это указатель IClassFactory.
interface IClassFactory : IUnknown { HRESULT __stdcall CreateInstance(IUnknown* pUnknownOuter, const IID& iid, void** ppv); HRESULT __stdcall LockServer(BOOL bLock); } ;У IClassFactory есть две функции-члена, CreateInstance и LockServer. Обсуждение LockServer мы отложим до конца главы.
Два оставшихся параметра — те же, что у QueryInterface. С их помощью вызывающая функция может запросить интерфейс компонента одновременно с созданием последнего. Это экономит клиенту один вызов функции. Если компонент к тому же выполняется на удаленной машине, то так экономится и один цикл запрос — ответ по сети.
Самое интересное в CreateInstance — не те параметры, которые у нее есть, а параметр, которого у нее нет. IClassFactory::CreateInstance не получает в качестве параметра CLSID. Это означает, что данная функция может создавать компоненты, соответствующие только одному CLSID — тому, который был передан CoGetClassObject:
IClassFactory2 |
Microsoft уже объявила еще один интерфейс создания компонентов, дополняющий IClassFactory. IClassFactory2 добавляет к IClassFactory поддержку лицензирования или разрешения на создание. Клиент обязан передать фабрике класса при помощи IClassFactory2 корректный ключ или лицензию, прежде чем та будет создавать компоненты. С помощью IClassFactory2 фабрика класса может гарантировать, что клиент получил компонент легально и имеет право пользования. Я уверен, что это не последний интерфейс для создания компонентов. |
HRESULT CoCreateInstance(const CLSID& cisid, IDnknown* pUnknownOuter, DWORD dwClsContext, const IID& iid, void* ppv) { // Установить в NULL выходной параметр *ppv = NULL ; // Создать фабрику класса и получить // указатель на интерфейс IClassFactory IClassFactory* pIFactory = NULL ; HRESULT hr = CoGetClassObject(clsid, dwClsContext, NULL, IID_IClassFactory, (void**)&pIFactory) ; if (SUCCEEOED(hr)) { // Создать компонент hr = pIFactory->CreateInstance(pUnknownOuter, iid, ppv); // Освободить фабрику класса pIFactory->Release() ; } return hr; }CoCreateInstance вызывает CoGetClassObject и получает указатель на интерфейс IClassFactory фабрики класса. Затем с помощью полученного указателя СоCreateInstance вызывает IClassFactory::CreateInstance, результатом чего и является создание нового компонента.
Создавать компоненты вручную с помощью фабрики класса гораздо хлопотнее, чем позволить CoCreateInstance сделать все за Вас. Но если Вы запомните, что фабрика класса — это просто компонент, который создает другие компоненты, будет гораздо легче понять, как это делается.
В этом разделе мы рассмотрим реализацию компонента, обращая особое внимание на реализацию фабрики класса. Но сначала посмотрим, как создаются сами фабрики класса.
STDAPI DllGetClassObject( const CLSID& cisid, const IID& lid, void** ppv );Три параметра этой функции Вам уже знакомы: это те же параметры, что передаются CoGetClassObject. Первый — идентификатор класса компонентов, которые будет создавать фабрика класса. Второй — идентификатор интерфейса фабрики, который желает использовать клиент. Указатель на этот интерфейс возвращается через третий параметр.
Весьма существенно, что DllGetClassObject передается CLSID. Этот параметр, позволяет одной DLL поддерживать несколько компонентов, так как по значению CLSID можно выбрать подходящую фабрику класса.
После того как фабрика класса создана, клиент использует интерфейс IClassFactory для создания компонента. Как именно IClassFactory::CreateInstance создает компонент — дело разработчика. Как уже отмечалось, IClassFactory инкапсулирует этот процесс, поэтому при создании компонента фабрика класса может использовать специфические знания.
Просматривая код, особое внимание уделите CFactory::CreateInstance и DllGetClassObject.
// // Cmpnt.cpp // #includeТолько что представленная реализация DllGetClassObject делает три вещи. Во-первых, она проверяет, соответствует ли запрос именно той фабрике, которую она умеет создавать. Затем при помощи операции new создается фабрика класса. Наконец, DllGetClassObject запрашивает у фабрики класса интерфейс, требуемый клиенту. Реализация IClassFactory::CreateInstance похожа на реализацию DllGetClassObject. Обе функции создают компонент и запрашивают у него интерфейс. IClassFactory::CreateInstance создает СА, тогда как DllGetClassObject — CFactory.#include #include "Iface.h" // Объявления интерфейсов #include "Registry.h" // Функции для работы с Реестром // Функция трассировки void trace(const char* msg) { cout << msg << endl ;} /////////////////////////////////////////////////////////// // // Глобальные переменные // static HMODULE g_hModule = NULL ; // Описатель модуля DLL static long g_cComponents = 0 ; // Счетчик активных компонентов static long g_cServerLocks = 0 ; // Счетчик блокировок // Дружественное имя компонента const char g_szFriendlyName[] = "Inside COM, Chapter 7 Example" ; // He зависящий от версии ProgID const char g_szVerIndProgID[] = "InsideCOM.Chap07" ; // ProgID const char g_szProgID[] = "InsideCOM.Chap07.1" ; /////////////////////////////////////////////////////////// // // Компонент // class CA : public IX, public IY { public: // IUnknown virtual HRESULT __stdcall QueryInterface(const IID& iid, void** ppv) ; virtual ULONG __stdcall AddRef() ; virtual ULONG __stdcall Release() ; // Интерфейс IX virtual void __stdcall Fx() { cout << "Fx" << endl ;} // Интерфейс IY virtual void __stdcall Fy() { cout << "Fy" << endl ;} // Конструктор CA() ; // Деструктор ~CA() ; private: // Счетчик ссылок long m_cRef ; } ; // // Конструктор // CA::CA() : m_cRef(1) { InterlockedIncrement(&g_cComponents) ; } // // Деструктор // CA::~CA() { InterlockedDecrement(&g_cComponents) ; trace("Component:\t\tDestroy self.") ; } // // Реализация IUnknown // HRESULT __stdcall CA::QueryInterface(const IID& iid, void** ppv) { if (iid == IID_IUnknown) { *ppv = static_cast (this) ; } else if (iid == IID_IX) { *ppv = static_cast (this) ; trace("Component:\t\tReturn pointer to IX.") ; } else if (iid == IID_IY) { *ppv = static_cast (this) ; trace("Component:\t\tReturn pointer to IY.") ; } else { *ppv = NULL ; return E_NOINTERFACE ; } reinterpret_cast (*ppv)->AddRef() ; return S_OK ; } ULONG __stdcall CA::AddRef() { return InterlockedIncrement(&m_cRef) ; } ULONG __stdcall CA::Release() { if (InterlockedDecrement(&m_cRef) == 0) { delete this ; return 0 ; } return m_cRef ; } /////////////////////////////////////////////////////////// // // Фабрика класса // class CFactory : public IClassFactory { public: // IUnknown virtual HRESULT __stdcall QueryInterface(const IID& iid, void** ppv) ; virtual ULONG __stdcall AddRef() ; virtual ULONG __stdcall Release() ; // Интерфейс IClassFactory virtual HRESULT __stdcall CreateInstance(IUnknown* pUnknownOuter, const IID& iid, void** ppv) ; virtual HRESULT __stdcall LockServer(BOOL bLock) ; // Конструктор CFactory() : m_cRef(1) {} // Деструктор ~CFactory() { trace("Class factory:\t\tDestroy self.") ;} private: long m_cRef ; } ; // // Реализация IUnknown для фабрики класса // HRESULT __stdcall CFactory::QueryInterface(const IID& iid, void** ppv) { if ((iid == IID_IUnknown) || (iid == IID_IClassFactory)) { *ppv = static_cast (this) ; } else { *ppv = NULL ; return E_NOINTERFACE ; } reinterpret_cast (*ppv)->AddRef() ; return S_OK ; } ULONG __stdcall CFactory::AddRef() { return InterlockedIncrement(&m_cRef) ; } ULONG __stdcall CFactory::Release() { if (InterlockedDecrement(&m_cRef) == 0) { delete this ; return 0 ; } return m_cRef ; } // // Реализация IClassFactory // HRESULT __stdcall CFactory::CreateInstance(IUnknown* pUnknownOuter, const IID& iid, void** ppv) { trace("Class factory:\t\tCreate component.") ; // Агрегирование не поддерживается. if (pUnknownOuter != NULL) { return CLASS_E_NOAGGREGATION ; } // Создать компонент. CA* pA = new CA ; if (pA == NULL) { return E_OUTOFMEMORY ; } // Получить запрошенный интерфейс. HRESULT hr = pA->QueryInterface(iid, ppv) ; // Освободить указатель на IUnknown. // (If QueryInterface failed, component will delete itself.) pA->Release() ; return hr ; } // LockServer HRESULT __stdcall CFactory::LockServer(BOOL bLock) { if (bLock) { InterlockedIncrement(&g_cServerLocks) ; } else { InterlockedDecrement(&g_cServerLocks) ; } return S_OK ; } /////////////////////////////////////////////////////////// // // Экспортируемые функции // // // Можно ли выгружать DLL? // STDAPI DllCanUnloadNow() { if ((g_cComponents == 0) && (g_cServerLocks == 0)) { return S_OK ; } else { return S_FALSE ; } } // // Получить фабрику класса // STDAPI DllGetClassObject(const CLSID& clsid, const IID& iid, void** ppv) { trace("DllGetClassObject:\tCreate class factory.") ; // Можно ли создать такой компонент? if (clsid != CLSID_Component1) { return CLASS_E_CLASSNOTAVAILABLE ; } // Создать фабрику класса. CFactory* pFactory = new CFactory ; // Счетчик ссылок устанавливается // в конструкторе в 1 if (pFactory == NULL) { return E_OUTOFMEMORY ; } // Получить требуемый интерфейс. HRESULT hr = pFactory->QueryInterface(iid, ppv) ; pFactory->Release() ; return hr ; } // // Регистрация сервера // STDAPI DllRegisterServer() { return RegisterServer(g_hModule, CLSID_Component1, g_szFriendlyName, g_szVerIndProgID, g_szProgID) ; } // // Удаление сервера из Реестра // STDAPI DllUnregisterServer() { return UnregisterServer(CLSID_Component1, g_szVerIndProgID, g_szProgID) ; } /////////////////////////////////////////////////////////// // // Реализация модуля DLL // BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, void* lpReserved) { if (dwReason == DLL_PROCESS_ATTACH) { g_hModule = hModule ; } return TRUE ; } Клиент: Вызвать CoCreateInstance для Клиент: создания компонента и получения интерфейса IX DllGetClassObject: Создать фабрику класса. Фабрика класса: Создать компонент. Компонент: Вернуть указатель на IX. Фабрика класса: Саморазрушение. Клиент: IX получен успешно. Fx Клиент: Запросить интерфейс IY. Компонент: Вернуть указатель на IY. Клиент: IY получен успешно. Fy Клиент: Освободить интерфейс IY. Клиент: Запросить интерфейс IZ. Клиент: Не могу получить интерфейс IZ. Клиент: Освободить интерфейс IX. Компонент: Саморазрушение.
Обратите внимание, что DllGetClassObject обладает подробными знаниями о создаваемой ею фабрике класса, а IClassFactory::CreateInstance - о создаваемом компоненте. Эти функции изолируют клиент от деталей реализация компонента. Можно сделать эти функции пригодными для повторного использования, однако в том или ином месте им обязательно потребуются специфические знания о том, как создавать конкретную фабрику класса или конкретный компонент. Способы, используемые DllGetClassObject и IClassFactory::CreateInstance для создания компонентов, полностью оставлены на усмотрение разработчика. Повторно применимая реализация DllGetClassObject и IClassFactory::CreateInstance будет представлена в гл. 9.
Функции DllRegisterServer и DllUnregisterServer регистрируют и удаляют из Реестра Windows информацию о компоненте. Мы кратко рассматривали их в гл. 6. Реализованы эти функции в файле REGISTRY.CPP. Я не буду объяснять их код — он достаточно прост, и при желании Вы сможете разобраться сами. Мы будем использовать тот же файл REGISTRY.H для регистрации компонентов и в последующих главах книги.
Make-файл примера этой главы содержит строку
regsvr32 -s Cmpnt.dllпосле компиляции и компоновки файла CMPNT.DLL. Как пояснялось в предыдущей главе, REGSVR32.ЕХЕ вызывает функцию DllRegisterServer, т. е. фактически выполняет регистрацию компонента. Если Вы не запускали make-файл, этот шаг Вам придется сделать самостоятельно. Для удобства я привожу командный файл REGISTER.BAT, состоящий из одной этой команды. Аналогичные файлы для выполнения регистрации прилагаются и к примерам следующих глав.
BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, void* IpReserved) { if(dwReason == DLL_PROCESS_ATTACH) { g_hModule = hModule; } return TRUE; }Эта функция заносит описатель в глобальную переменную q_hModule, откуда его могут считать функции DllRegisterServer и DllUnregisterServer.
Я уже говорил выше, что DllGetClassObject позволяет нам поддерживать несколько компонентов в одной DLL. Ключевой момент здесь — передача DllGetClassObject CLSID создаваемого компонента. Для каждого CLSID DllGetClassObject легко может создать особую фабрику класса (см. рис. 7-3).
СА* рА = new СА ; pA->QueryInterface(...) ;Если Вы правильно спроектировали свою фабрику класса и компоненты, то Вам понадобится только одна реализация фабрики для всех компонентов. Я предпочитаю для каждого компонента создавать по Одной простой функции. Эта функция создает компонент при помощи операции new и возвращает указатель на IUnknown. Затем я строю из указателей на эти функции таблицу, индексируемую CLSID каждого компонента. DllGetClassObject просто отыскивает в таблице указатель нужной функции, создает фабрику класса и передает ей этот указатель. Затем, вместо того, чтобы непосредственно использовать операцию new, фабрика класса при помощи указателя вызывает соответствующую функцию создания компонента (см. рис. 7-4).
Прежде чем идти дальше, я хотел бы подчеркнуть один важный момент. Даже если один и тот же код фабрики класса используется несколькими компонентами, данный экземпляр фабрики класса может создавать только компоненты, соответствующие одному CLSID. Соотношение между экземпляром фабрики класса и CLSID всегда будет один к одному. Хотя CFactory могла бы реализовывать все наши фабрики класса, любой конкретный экземпляр CFactory может создавать только компоненты одного CLSID. Это следствие того, что IClassFactory::CreateInstance не передается в качестве параметра CLSID.
Я достаточно долго избегал разговора о LockServer и DIICanUnloadNow. Теперь пора заняться и ими.
Как мы видели в гл. 5, когда клиент динамически связывается с компонентом, он должен загрузить DLL в память. В Win32 для этой цели используется, функция LoadLibrary(На самом деле библиотека СОМ использует CoLoadLibrary, которая обращается к LoadLibrary). Когда мы закончили работу с DLL, хотелось бы выгрузить из памяти. Не стоит засорять память неиспользуемыми компонентами. Библиотека СОМ предоставляет функцию CoFreeUnusedLibraries, которая, как следует из названия, освобождает, неиспользуемые библиотеки. Клиент должен периодически вызывать эту функцию в периоды бездействия.
static long g_cComponents = 0 ;Затем IClassFactory::CreateInstance или конструктор компонента увеличивают значение g_cComponents, а деструктор компонента уменьшает его. DIICanUnloadNow дает положительный ответ тогда, когда g_cComponents равна 0.
Это создает трудную ситуацию. Выгрузка DLL, у которой имеются исполняющиеся фабрики класса, может вызвать проблемы у клиентов. Предположим, что у клиента имеется указатель на фабрику класса, а соответствующая DLL выгружена. Если клиент попытается использовать указатель на IClassFactory, произойдет сбой. Клиенту необходим некоторый способ удержания DLL в памяти, если он собирается использовать указатель IClassFactory за пределами одной функции. IClassFactory::LockServer позволяет клиенту удерживать сервер в памяти до завершения работы. Клиент просто вызывает LockServer(TRUE) для блокирования сервера и LockServer(FALSE) для деблокирования.
Реализовать LockServer теперь можно простым увеличением и уменьшением счетчика g_cComponents. Многие, и я в их числе, предпочитают использовать отдельные счетчики для компонентов и блокировок. В этом случае DllCanUnloadNow должна проверять на равенство нулю оба счетчика.
1 В большинстве случаев для создания компонентов пользуются CoCreateInstance. Однако иногда эта функция не дает достаточной гибкости. Тогда можно воспользоваться CoGetClassObject, которая дает возможность прямо управлять фабрикой класса и интерфейсом, используемым для создания компонентов. Стандартный для создания компонентов интерфейс -IClassFactory, который используется и CoCreateInstance.
Независимо от того, использует ли клиент CoCreateInstance или CoGetClassObject, у компонента всегда имеется отдельная фабрика класса. Фабрика класса - это компонент, создающий компонент. Фабрика класса компонента обычно реализует IClassFactory. Однако некоторые компоненты предъявляют особые требования к процессу создания. Такие компоненты вместо (или в дополнение) к IClassFactory будут реализовывать и другие интерфейсы. Если бы мы не могли собирать,из деталей Lego конструкции, играть с ними было бы не слишком весело, даже при наличии Домашней Фабрики Пластиковых Деталей. Изготовление деталей - только часть процесса, который описывает эта книга. По-настоящему интересные вещи начинаются тогда, когда мы собираем детали вместе. Так и компоненты становятся понастоящему интересными, только когда Вы соединяете их вместе, в новую цельную структуру В следующей главе мы увидим, как построить новые компоненты из уже имеющихся.