Учебное пособие 1407
.pdf}
STDMETHODIMP_(ULONG) CoCarClassFactory::Release () { if(--m_refCount == 0)
{
delete this; return 0;
}
return m_refCount;
}
//Фабрики класса не реализуют интерфейсы кокласса, который они создают!
STDMETHODIMP CoCarClassFactory::QueryInterface(REFIID riid, void** ppv)
{
//Какой интерфейс желаете?
if(riid == IID_IUnknown)
{
*ppv = (lUnknown*)this;
}
else if(riid == IID_IClassFactory)
{
*ppv = (IClassFactory*)this;
}
else
{
*ppv = NULL; return E_NOINTERFACE;
}
((IUnknown*)(*ppv))->AddRef(); return S_OK;
}
Теперь реализуем IClassFactory:: CreateInstance(). В этом методе создадим объект СоСаr и запросим нужный интерфейс. Вернем HRESULT, который клиент сможет проанализировать. Если в методе CreateInstance() что-то не сработает, выделенную под кокласс память надо освободить:
// Создаем СоСаr!
39
STDMETHODIMP CoCarClassFactory:: CreateInstance(LPUNKNOWN pUnkOuter, REFIID riid, void** ppv)
{
// В этом объекте класса агрегация не поддерживается if(pUnkOuter != NULL)
return CLASS_E_NOAGGREGATION; СоСаr* pCarObj = NULL;
HRESULT hr;
//Создаем автомобиль pCarObj = new CoCar;
//Запрашиваем интерфейс
hr = pCarObj -> QueryInterface(riid, ppv);
// Проблема? Освобождаем занятую память if (FAILED(hr))
delete pCarObj;
return hr;
}
Реализуем IClassFactory: :LockServer()
для работы со счетчиком блокировок. В конструкторе и деструкторе объекта класса добавьте соответствующий код для этого счетчика. Для обращения к глобальным переменным, определенным в CarInProcServer.cpp, используйте ключевое, слово extern в файле СРР вашего объекта класса:
//Завершаем построение фабрики класса extern ULONG g_lockCount;
extern ULONG g_objCount;
//Конструктор
CoCarClassFactory::CoCarClassFactory()
{
m_refCount = 0; g_objCount++;
}
//Деструктор
CoCarClassFactory::~CoCarClassFactory() { g_objCount--; }
40
// Блокировки сервера
STDMETHODIMP CoCarClassFactory::LockServer(BOOL fLock)
{
if (fLock) |
++g_lockCount; |
else |
--g_lockCount; |
return S_OK;
}
Кроме того, убедитесь, что вы вставили нужные файлы в исходный код фабрики класса (существует обращение к CоСаr в CreateInstance(), поэтому требуется заголовочный файл CоСаr). Далее разработаем минимальный полный набор экспортируемых функций сервера.
3. Реализация хранилища компонентов DLL
Реализуйте в файле CarlnProcServer.cpp метод
DllGetClassObject():
//Этот экспортируемый метод делает фабрики класса видимыми для
//менеджера управления сервисом (Service Control Manager)
STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, void** ppv) { /*
1.Проверьте, совпадают ли CLSID и СоСаr, если нет — верните код ошибки
2.Создайте объект CoCarClassFactory
3.Запросите параметр REFIID у нового объекта класса
4.Установите HRESULT для клиента
*/
}
Последняя экспортируемая функция DLL — это
DllCanUnloadNow():
// Метод определяет, можно ли выгрузить DLL из памяти
STDAPI DllCanUnloadNow()
{
//Если g_objCount и g_lockCount оба равны нулю, возвращаем S_OK,
//в противном случае — S_FALSE
}
41
Ниже приводится возможный код для каждой из экспортируемых функций:
// Экспортируемые функции DLL
STDAPI DllCanUnloadNow ();
{
if (g_lockCount == 0 && g_objCount == 0)
return S_OK; |
// Выгрузи меня |
else |
|
return S_FALSE; |
// Оставь в памяти |
}
STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, void** ppv)
{
HRESULT hr;
CoCarClassFactory *pCFact = NULL;
//Знаем, как в этом сервере создавать машины if(rclsid != CLSID_CoCar)
return CLASS_E_CLASSNOTAVAILABLE;
//Запрашивается CoCarClassFactory
pCFact = new CoCarClassFactory;
// Получаем интерфейс из CoCarClassFactory hr = pCFact -> QueryInterface(riid, ppv) ; if(FAILED(hr))
delete pCFact; return hr;
}
Создайте и вставьте в проект файл carinprocserver.def, чтобы сделать эти две функции видимыми окружающему миру. Имя в операторе library должно совпадать с именем проекта, который вы компонуете:
LIBRARY "CARINPROCSERVER" EXPORTS
DllGetClassObject @l PRIVATE DllCanUnloadNow @2 PRIVATE
42
4. Внесение информации в реестр
Чтобы завершить наш DLL-сервер, нужно создать еще REG-файл для внесения минимальной и полной информации о сервере в реестр системы. Создайте ProgID для вашего кокласса с подключом CLSID. Введите его прямо под HKCR. Затем введите CLSID для вашего кокласса с подключом InprocServer32, указывающим на физический путь к DLL.
Вот пример файла carinprocserver.reg:
REGEDIT
HKEY_CLASSES_ROOT\CarInProcServer.CoCar\CLSID = {7AD2D539-EE35-lld2-B8DE- 0020781238D4}
HKEY_CLASSES_ROOT\CLSID\{7AD2D539-EE35-lld2-B8DE-0020781238D4} = CarlnProcServer.CoCar
HKEY_CLASSES_ROOT\CLSID\{7AD2D539-EE35-lld2-B8DE-0020781238D4} \InprocServer32 = E:\ATL\Labs\Chapter03\CarInProcServer \Debug\CarInProcServer.dll
После того как сформируете REG-файл, просто щелкните по нему дважды в Проводнике Windows, чтобы записать информацию в реестр. Используйте regedit.exe для проверки правильности занесения информации.
Итак, создан СОМ-сервер на обычном C++ и без всяких библиотек. Позже построим клиент СОМ на C++, обращающийся к этому серверу.
Контрольные вопросы
1.Для чего служит фабрика класса?
2.Какая информация записывается в реестр системы о распределённых COM-программах?
43
Практическое занятие № 6 Разработка клиента распределённого приложения для
Windows на С++
Цель практического занятия Изучить и освоить разработку клиента распределённого
приложения для Windows, получить практические навыки по разработке сетевых программ на языках С, С++.
Теоретические сведения
В этой работе спроектируем СОМ-клиента, который будет использовать созданный на предыдущем практическом занятии СОМ-сервер СоCаr. Вы осуществите запрос SCM с помощью библиотеки СОМ, будете оперировать с указателями интерфейса, работать с bstr и создавать фабрики класса. Завершится процесс преобразования CoCаr. Вот его этапы:
структура с набором глобальных функций; простой объект C++;
объект C++ с множественными интерфейсами; исполняемая DLL на базе СОМ.
Готовый проект находится в каталоге … \Ch3\CoCarClient3-3.
1. Подготовка рабочего пространства проекта
Создадим новое консольное приложение Win32 с именем CoCarClient, на этот раз выберите simple project (простой проект). Прежде чем использовать CarInProcServer.dll, необходимо внести в рабочее пространство следующие файлы:
-iid.h и iid.cpp. Нужны константы GUID. Вставьте файл СРР в проект;
-interfaces.h. Нужны имена интерфейсов, чтобы определить переменные указателей к ним;
-включите также заголовочные файлы в начало файла
cocarclient.cpp.
44
После копирования этих файлов в подкаталог проекта, можно приступать к созданию кода клиента
#include "interfaces.h" #include "iid.h" #include <iostream.h> int main()
{
return 0;
}
2. Реализация функции main()
Реализуйте в головном блоке следующие функциональные возможности.
// Код клиента на C++ int main(void)
{
//Инициализация подсистемы СОМ
//Получение указателя IClassFactory из CoCarClassFactory
//Вызов Createlnstance() из pICF, запрос IID_ICreateCar из СоСаr
//Выполнение ICreateCar.
//Запрос IID_IStats из указателя ICreateCar или IUnknown
//Вызов DisplayStats() из pIStats QI для IEngine
//Разгоняем машину
// Используя pIStats->GetPetName(), выводим BSTR на консоль,
//информируя пользователя о "кончине" машины
//Освобождаем все полученные указатели интерфейсов
//Завершаем подсистему СОМ
return 0;
}
Большая часть кода клиента совпадает с приложением CоСаrАрр (см. домашнее задание №2). Вот полный листинг:
// Программа СОМ-клиента
//
45
int main()
{
CoInitialize(NULL); HRESULT hr; IClassFactory* pCF = NULL;
ICreateCar* pICreateCar = NULL; IStats* pStats = NULL;
IEngine* pEngine = NULL;
cout << "**********************" << endl; cout << "The Amazing CoCar Client" << endl; cout << "**********************" << endl;
// Получить указатель фабрики класса CoCar.
hr = CoGetClassObject(CLSID_CoCar, CLSCTX_INPROC_SERVER, NULL, IID_IClassFactory, (void**)&pCF);
// Создать CoCar & получить ICreateCar.
hr = pCF->CreateInstance(NULL, IID_ICreateCar,(void**)&pICreateCar); pCF ->Release();
if (SUCCEEDED (hr) )
{
pICreateCar->SetMaxSpeed(30) ;
BSTR petName = SysAllocString(L"Shazzam! "); pICreateCar->SetPetName (petName) ; SysFreeString (petName) ;
// Теперь получаем IStats
hr = pICreateCar->QueryInterface(IID_IStats, (void**) SpStats) ; pICreateCar->Release( ) ;
}
if (SUCCEEDED(hr) )
{
//Указываем данные машины и получаем IEngine pStats->DisplayStats( ) ;
hr = pStats->QueryInterface(IID_IEngine, (void**) &pEngine) ;
}
// Разгоняемся!
46
if (SUCCEEDED(hr) ) { int curSp = 0; int maxSp = 0;
pEngine->GetMaxSpeed(&maxSp) ; do{
pEngine->SpeedUp ( ) ; pEngine-X3etCurSpeed(&curSp) ; cout << "Speed is: " << curSp << endl;
}while(curSp <= maxSp) ;
//Преобразуем BSTR в массив char char buff [80];
BSTR bstr; pStats->GetPetName(Sbstr) ;
WideCharToMultiByte (CP_ACP, NULL, bstr, -1, buff, 80, NULL, NULL); cout << buff << " has blown up! Lead Foot!" << endl << endl;
//Убираем за собой
SysFreeString(bstr) ; if (pEngine) pEngine->Release( ) ; if (pStats) pStats->Release( ) ;
}
CoUninitialize(); return 0;
}
Контрольные вопросы
1.Каким образом клиентская программа отыскивает соответствующую серверную программу?
2.Как формируется значение CLSID?
3.Для чего служат переменные типа HRESULT?
47
Практическое занятие № 7 Особенности разработки распределённых приложений для
Windows с применением различных языков
Цель практического занятия Изучить и освоить разработку клиентов распределён-
ных приложений для Windows на различных языках, принципы обеспечения удалённого взаимодействия разноязыковых модулей, получить практические навыки по разработке сетевых программ на языках С, С++.
Теоретические сведения
Эта работа позволяет поэкспериментировать с языковой независимостью модели СОМ. В ней расширяется разработанный ранее сервер CarInProcServer.dll до поддержки информации о типе и применяются файлы, сгенерированные компилятором MIDL. После обновления сервера СоСаr появляется возможность построить клиентские приложения на Visual Basic и Java, а также клиента C++ с использованием директив компилятора СОМ.
Готовые решения находятся в каталогах:
… \Ch4\CarServerTypeInfo: СоСаг с библиотекой типов;
… \Ch4\CarServerTypeInfo\VB Client: клиент на Visual Basic;
… \Ch4\ CarServerTypeInfo\VJ Client: клиент на J++;
… \Ch4\ CarServerTypeInfo\CPP Import Client: клиент
C++ с библиотекой типов.
В этой работе расширяется сервер CarInProcServer.dll (см. практическое занятие №6) для поддержки информации о типе. Сначала откройте рабочее пространство CarInprocServer (или сделайте копию проекта) и вставьте в проект новый файл (через меню File | New...) с именем CarServerTypeInfo.idl. В этом файле будут находиться все определения интерфейсов, коклассов и библиотек сервера.
48