Фабрика
классов
Логика функционирования
нашего проекта (типа клиент-сервер ) вырождена, то есть излишне упрощена, так
как мы хотели показать лишь основную нить алгоритма использования СОМ-объектов.
Обычно в рамках этого алгоритма присутствует так называемая фабрика классов
— специальный класс на стороне сервера, который реализует функциональность уже
существующего и зарегистрированного в библиотеке СОМ интерфейса iciassFactory.
Фабрики классов — это объекты СОМ создающие другие объекты сервера. Их цель
— создать объект определенного типа, который однозначно задан своим CLSID. Каждый
СОМ-объект должен в соответствии со стандартом иметь связанную с ним фабрику
классов, которая ответственна за его создание. Так, в нашем случае мы должны
иметь фабрику классов, способную воспроизводить любое требуемое клиентами количество
объектов класса CoSay.
Интерфейс iciassFactory
имеет всего два метода: Createlnstance и LockServer. Первый необходим для того,
чтобы динамически создавать произвольное количество объектов тех классов (CLSID),
которые живут в доме DLL СОМ-сервера, а второй — для того, чтобы запретить или
разрешить системе выгружать сервер из памяти. Это позволяет пользователю гибко
управлять необходимыми ресурсами. Если СОМ-объект пока не нужен клиентскому
приложению, но вскоре может понадобиться, то, вызвав метод LockServer с параметром
TRUE, клиент может запретить выгрузку из памяти DLL-сервера, несмотря на то
что счетчик числа пользователей ее объектами равен нулю. Если в течение какого-то
времени не предвидится использование СОМ-объектов, то клиент может вызвать метод
LockServer с параметром FALSE, разрешив тем самым выгрузку DLL-сервера из памяти.
Для реализации
этой функциональности вновь откройте проект СОМ-сервера My с от и в файл МуСоm.срр
добавьте две глобальные переменные:
//======
Счетчик числа блокировок DLL
ULONG
gLockCount;
//======
Счетчик числа пользователей СОМ-объектами
ULONG
gObjCount;
В
этот же файл введите новую функцию, которую будет экспортировать наша DLL:
STDAPI
DllCanUnloadNow()
{
//======
Если счетчики нулевые, то мы позволяем
//======
системе выгрузку DLL-сервера
return
!gLockCount && IgObjCount ? S_OK : S_FALSE;
}
В конструктор
класса coSay добавьте код, увеличивающий счетчик числа пользователей объектом
Со Say:
gObjCount++;
а
в деструктор — уменьшающий:
gObjCount--;
Важным шагом,
о котором, тем не менее, легко забыть, является своевременная коррекция файла
MyCom.def. Вставьте в конец этого файла строку
DllCanUnloadNow
PRIVATE
которая добавляет
в список экспортируемых функций еще один элемент. В файл MyCom. h добавьте декларацию
нового класса CoSayFactory, реализующего интерфейс iclassFactory. Отметьте,
что он произведен от интерфейса iClassFactory, который, как и положено, имеет
родителя I unknown. Вы помните, что на плечи класса ложится бремя реализации
всех методов своих предков. По той же причине мы вновь заводим счетчик числа
пользователей классом (m_ref):
//======
Фабрика классов СОМ DLL-сервера
class
CoSayFactory :
public IClassFactory
{
public:
CoSayFactory()
;
virtual
~CoSayFactory() ;
//
lUnknown
HRESULT
_
stdcall Querylnterface(REFIID riid,
void**
ppv);
UbONG
_
stdcall AddRefO; ULONG _
stdcall Release();
//
IClassFactory
HRESULT
_
stdcall Createlnstance(LPUNKNOWN pUnk,
REFIID
riid,
void** ppv);
HRESULT
_
stdcall LockServer(BOOL bLock);
private:
ULONG
m_ref; };
Реализацию
тел заявленных методов вставьте в файл МуСоm.срр. Здесь мы вынуждены повторяться,
вновь прокручивая логику управления временем жизни объектов СОМ:
//==========
Фабрика классов
CoSayFactory::CoSayFactory()
{
m_ref
= 0; gObjCount++;
}
CoSayFactory::-CoSayFactory()
{
gObjCount--;
}
//======
Методы lUnknown
HRESULT
_
stdcall CoSayFactory
::QueryInterface(REFIID
riid,
void** ppv)
{
*ppv
= 0;
//===
На сей раз обойдемся без шаблона static_cast<>
if
(riid == IID_IUnknown)
*ppv
= (lUnknown*)this;
else
if (riid == IID_IClassFactory)
*ppv
= (IClassFactory*)this;
else
return
E_NOINTERFACE;
AddRef();
return
S_OK;
}
ULONG
_stdcall CoSayFactory:rAddRef()
{
return
++m_ref;
}
ULONG
_stdcall CoSayFactory::Release()
{
if
(--m_ref==0)
delete
this;
return
m_ref;
//======
Методы интерфейса IClassFactory
HRESULT
_
stdcall CoSayFactory: :CreateInstance
(LPUNKNOWN
pUnk, REFIID riid,
void** ppv)
{
//
Этот параметр управляет аггрегированием
//
объектов СОМ, которое мы не поддерживаем
if
(pUnk)
return
CLASS_E_NOAGGREGATION;
//==
Создание нового объекта и запрос его интерфейса
CoSay
*pSay = new CoSay;
HRESULT
hr = pSay->Query!nterface (riid, ppv) ;
if
(FAILED (hr))
delete
pSay;
return hr;
//===
Управление счетчиком фиксаций сервера в памяти
HRESULT
_
stdcall CoSayFactory::LockServer(BOOL bLock)
{
if
(bLock) // Если TRUE, то увеличиваем счетчик
++gLockCount;
else
// Иначе — уменьшаем
--gLockCount;
return
S_OK;
}
Мы должны также
изменить алгоритм функции DllGetciassOb j ect, которая теперь создает объект
фабрики классов и запрашивает один из двух возможных интерфейсов (lUnknown,
IClassFactory):
STDAPI
DllGetClassObject (REFCLSID rclsid, REFIID riid, LPVOID* ppv)
{
if
(rclsid != CLSID_CoSay)
return
CLASS_E_CLASSNOTAVAILABLE;
CoSayFactory
*pCF = new CoSayFactory;
HRESULT
hr = pCF->Query!nterface(riid, ppv);
if
(FAILED(hr))
delete
pCF;
return
hr;
}
На этом модификация
сервера завершается. Дайте команду Build > Rebuild и устраните ошибки, если
они имеются. Затем вновь откройте проект клиентского приложения SayClient и
внесите изменения в функцию main, которая теперь должна работать с объектами
СОМ более изощренным способом. Она должна сначала загрузить СОМ-сервер и запросить
адрес его фабрики классов, затем создать с ее помощью объект CoSay, попросив
у него адрес интерфейса isay, и лишь после этого можно начать управление объектом.
Последовательность освобождения объектов тоже должна быть тщательно выверена.
Ниже приведена новая версия файла SayClient.cpp:
#include
"interfaces.h"
void
main()
{
(reinitialize
(0) ;
IClassFactory
*pCF;
//
Мы зарегистрировали только один класс CoSay,
//
поэтому ищем DLL с его помощью, но при этом
//
создается не объект CoSay, а объект CoSayFactory
//
(см. код функции DllGetClassObject).
//
Поэтому здесь мы просим дать адрес
//
интерфейса IClassFactory
HRESULT
hr = CoGetClassObject(CLSID_CoSay, CLSCTX_INPROC_SERVER,0, IID_IClassFactory,(void**)&pCF);
if
(FAILED(hr))
{
MessageBox(0,"Could
not Get Class Factory !
",
"CoGetClassObject", MB_OK);
CoUninitialize();
return;
}
//
Далее мы с помощью фабрики классов
//
создаем объект CoSay и просим его
//
дать нам адрес интерфеса ISay
ISay
*pSay;
hr
= pCF->Create!nstance(0,IID_ISay, (void**)&pSay) ;
if
(FAILED(hr))
{
MessageBox(0,"Could
not create CoSay and get ISay!
",
"Createlnstance", MB_OK);
CoUninitialize
();
return;
}
//
Уменьшаем счетчик числа пользователей
//
фабрикой классов pCF->Release();
//======
Управляем объектом
pSay->Say();
BSTR
word = SysAllocString(L"Yes, My Lord");
pSay->SetWord(word);
SysFreeString(word);
pSay->Say();
//======
Уменьшаем число его пользователей
pSay->Release();
SCoUninitialize
() ;
}
Запустите приложение
(Ctrl+F5) и проверьте его работу. Алгоритм проверки остается тем же, что и ранее,
но здесь мы должны по логике разработчиков СОМ, радоваться тому, что выполняем
большее число правил и стандартов, а также имеем возможность одновременно создавать
несколько СОМ-объектов.
Примечание
На мой взгляд, не может
быть ничего лучшего, чем получить код хорошо продуманного класса C++, который
дает вам новую, хорошо документированную функциональность. При этом вы получаете
полную свободу в том, как ее использовать, и имеете возможность развивать
ее по вашему усмотрению. Использование методов класса предполагает выполнение
оговоренных заранее правил игры, так же как и при использовании методов интерфейсов.
Но эти правила значительно более естественные, чем правила СОМ. Вы, возможно,
возразите, что для внедрения в проект нового класса, сам проект надо строить
заново. Двоичный объект СОМ в этом смысле внедрить проще. Но здесь надо учитывать
тот факт, что для реализации всех выгод СОМ вам придется разработать универсальный
контейнер объектов, который будет способен внедрять СОМ-объекты будущих поколений
и управлять ими. Это невозможно сделать, не трогая кода вашего приложения.
Разработчик более или менее серьезного проекта постоянно корректирует его,
изменяя код того или иного модуля. Он просто обречен на это. На мой взгляд,
при реализации новых идей проще использовать исходные коды классов, чем двоичные
объекты. Без сомнения, за хорошие коды надо платить, также как и за хорошие
СОМ-объекты.