Архитектура Аудит Военная наука Иностранные языки Медицина Металлургия Метрология
Образование Политология Производство Психология Стандартизация Технологии


Простые СОМ-клиенты и СОМ-серверы



Для иллюстрации описанных приемов работы с СОМ приведем два примера на языке C++, в которых используется СОМ. Они достаточно просты и используют язык C++ и функции СОМ API. Всю работу будем проделывать самостоятельно, без использования таких структур, как MFC или ATL. Хотя примеры и тривиальны, на них вполне можно продемонстрировать основные приемы, используемые в СОМ. Ниже в этой лекции мы применим библиотеку активных шаблонов фирмы Microsoft, чтобы заново реализовать данный пример сервера.

Прежде чем мы начнем разработку, необходимо внести существенное изменение в интерфейс компонента. Первоначальное определение абстрактного класса выглядит следующим образом:

class IMath: public IUnknown{public: virtual long Add(long Op1, long Op2) = 0; virtual long Subtract(long Op1, long Op2) = 0; virtual long Multiply(long Op1, long Op2) = 0; virtual long Divide (long Op1, long Op2) = 0; };

 

Такой способ объявления порождает одну проблему. Каждый метод СОМ-интерфейса должен возвращать значение типа HRESULT. В нашем случае возвращается только результат операции. Поэтому нам требуется возвратить HRESULT, а результат операции передать через параметр. Это выглядит следующим образом:

class IMath: public IUnknown{public: virtual HRESULT Add(long Op1, long Op2, long *pResult) = 0; virtual HRESULT Subtract(long Op1, long Op2, long *pResult) = 0; virtual HRESULT Multiply(long Op1, long Op2, long *pResult) = 0; virtual HRESULT Divide (long Op1, long Op2, long *pResult) = 0; };

 

Такая запись несколько необычна, поскольку известно, что возврат методом значения вычислений уменьшает сложность программы. Теперь же приходится работать с указателем на возвращаемый результат. Возвращение значения типа HRESULT каждым методом является общим правилом в СОМ. Однако существуют технические приемы (например, ключевое слово языка определения интерфейсов retval), позволяющее приложению клиента трактовать каждый метод таким образом, как если бы он на самом деле возвращал результат, а не HRESULT. Ниже мы рассмотрим эту возможность на примере клиента.

 

Макросы STDMETHOD и STDMETHODIMP

Файлы заголовков СОМ предоставляют несколько макросов, используемых при объявлении и реализации СОМ-интерфейсов. Из соображений простоты вплоть до этого момента мы использовали в примерах обыкновенную С++-программу. Однако Microsoft рекомендует применять именно эти макросы, поскольку благодаря им исключается возможность несовместимости программных сред. В первую очередь разберемся с макросами STDMETHOD и STDMETHOD_, а также с STDMETHODIMP и STDMETHODIMP_. Вот новое определение интерфейса IMath с использованием макроса STDMETHOD.

class IMath: public IUnknown{public: STDMETHOD( Add(long, long, long *))PURE; STDMETHOD( Subtract(long, long, long *))PURE; STDMETHOD( Multiply(long, long, long *))PURE; STDMETHOD( Divide (long, long, long *))PURE; };

 

Результат развертывания макроса STDMETHOD_ зависит от целевой среды и языка программирования: С или C++. Для среды Win32 с использованием C++ определение этого макроса выглядит следующим образом:

// OBJBASE.H#define STDMETHODCALLTYPE __stdcall#define STDMETHOD(method) virtual HRESULT STDMETHODCALLTYPE method#define STDMETHOD_(type, method) virtual type STDMETHODCALLTYPE method#define PURE = 0#define STDMETHODIMP HRESULT STDMETHODCALLTYPE#define STDMETHODIMP_(type) type STDMETHODCALLTYPE

 

Если развернуть этот макрос, то наша программа станет очень похожа на предыдущие примеры. Единственное отличие состоит в добавлении модификатора __stdcall. Этот модификатор указывает на необходимость соблюдения определенного соглашения фирмы Microsoft относительно вызовов, используемого в API-функциях Win32. Это соглашение требует, чтобы вызываемая программа очищала стек после своего вызова. Модификатор PURE — всего лишь способ обозначить функцию как чисто виртуальную (pure virtual, т.е. = 0).

Большая часть методов СОМ-интерфейсов возвращает стандартный тип HRESULT. В этом состоит единственное различие между макросами STDMETHOD и STDMETHOD_, поскольку метод, определенный как STDMETHOD, всегда возвращает данные типа HRESULT, а определение макроса STDMETHOD_ позволяет пользователю задавать тип возвращаемого значения. Вот как в определении интерфейса IClassFactory используется макрос STDMETHOD:

STDMETHOD (LockServer(BOOL fLock)) PURE; //Разворачивается таким образом virtual HRESULT _stdcall LockServer(BOOL fLock)= 0;

 

Макрос STDMETHOD применен для объявления методов интерфейса, как в абстрактных определениях, так и в определениях класса. Единственным отличием является модификатор PURE. Приведем программу для производного класса:

 

class Math: public IMath{...public: //IUnknown STDMETHOD(QueryInterface( REFIID, void** )); STDMETHOD_(ULONG, AddRef()); STDMETHOD_(ULONG, Release()); //IMath STDMETHOD(Add( long, long, long*)); STDMETHOD(Substract( long, long, long*)); STDMETHOD(Multiply( long, long, long*)); STDMETHOD(Divide( long, long, long*)); };

 

И, наконец, при реализации класса используется макрос STDMETHODIMP. Приведем пример релизации класса Math:

STDMETHODIMP Math:: Add( long Op1, long Op2, long *pResult ) { *pResult = Op1 + Op2; return S_OK; } STDMETHODIMP Math:: Subtract( long Op1, long Op2, long *pResult ){ *pResult = Op1 - Op2; return S_OK; ) STDMETHODIMP Math:: Multiply( long Op1, long Op2, long *pResult ){ *pResult = Op1 * Op2; return S_OK; } STDMETHODIMP_ (long) Math:: Divide( long Op1, long Op2, long *pResult ){ *pResult = Op1 / Op2; return S_OK; }

 

Проект сервера

Приложение сервера обеспечивает реализацию интерфейса IMath и, таким образом, создает и помещает на хранение компонент Math. Для разработки сервера воспользуйтесь средой разработки Visual C++.

Теперь необходимо объявить интерфейс абстрактного компонента и его CLSID и IID. Ранее это уже делалось, но сейчас все фрагменты кода собраны вместе. Наберите следующий текст и сохраните его в файле с именем IMATH.H.

//// imath.h// // {A888F560-58E4-11d0-A68A-0000837E3100}DEFINE_GUID( CLSID_Math, 0xa888f560, 0x58e4, 0x11d0, 0xa6, 0x8a, 0x0, 0x0, 0x83, 0x7e, 0x31, 0x0); // {A888F561-58E4-11d0-A68A-0000837E3100}DEFINE_GUID( IID_IMath, 0xa888f561, 0x58e4, 0x11d0, 0xa6, 0x8a, 0x0, 0x0, 0x83, 0x7e, 0x31, 0x0); class IMath: public IUnknown{public: STDMETHOD( Add( long, long, long* )) PURE; STDMETHOD( Subtract( long, long, long* )) PURE; STDMETHOD( Multiply( long, long, long* )) PURE; STDMETHOD( Divide( long, long, long* )) PURE; };

 

Чтобы предоставить программе клиента информацию об определениях интерфейса и идентификаторов CLSID и IID, необходимо отделить их от собственно текста программы. Только благодаря этому программа клиента может получить доступ к функциям компонента. Фактически клиент не нуждается в CLSID (поскольку доступ к компоненту осуществляется с помощью его ProgID), поэтому ничто не помешает нам расположить его именно здесь.

Строки, начинающиеся с макроса DEFINE_GUID, можно ввести таким же образом, как это сделано здесь, либо создать ваши собственные с помощью утилиты GUIDGEN. Теперь необходимо объявить класс компонента и фабрику классов для него. Чтобы сделать это, создайте файл с именем МАТН.Н и введите следующие определения:

//// math.h// #include " imath.h" extern long g_lObjs; extern long g_lLocks; class Math: public IMath{protected: // Reference count long m_lRef; public: Math(); ~Math(); public: // IUnknown STDMETHOD(QueryInterface( REFIID, void** )); STDMETHOD_(ULONG, AddRef()); STDMETHOD_(ULONG, Release()); // IMath STDMETHOD(Add( long, long, long* )); STDMETHOD(Subtract( long, long, long* )); STDMETHOD(Multiply( long, long, long* )); STDMETHOD(Divide( long, long, long* )); }; class MathClassFactory: public IClassFactory{protected: long m_lRef; public: MathClassFactory(); ~MathClassFactory(); // IUnknown STDMETHOD( QueryInterface(REFIID, void** )); STDMETHOD_(ULONG, AddRef()); STDMETHOD_(ULONG, Release()); // IClassFactory STDMETHOD( CreateInstance(LPUNKNOWN, REFIID, void**)); STDMETHOD( LockServer(BOOL)); };

 

Большая часть этих строк встречалась вам и раньше. Класс Math является производным по отношению к классу интерфейсов IMath, который в свою очередь является производным для IUnknown. Объявим методы IUnknown и IMath. Отслеживание общего количества экземпляров компонента в DLL-файле и количества вызовов IClassFactory:: LockServer возлагается на две глобальные переменные. Затем объявляем класс для фабрики классов компонента Math. Теперь можно взглянуть, на текст программы. Создайте файл МАТН.СРР и введите в него следующее:

//// Math.cpp// #include < windows.h> #include " math.h" //// Math class implementation//// ConstructorsMath:: Math(){ m_lRef = 0; // Увеличить значение внешнего счетчика объектов InterlockedIncrement( & g_lObjs ); } // The destructorMath:: ~Math(){ // Уменьшить значение внешнего счетчика объектов InterlockedDecrement( & g_lObjs ); }

 

В конструкторе внутренний счетчик инициализируется значением нуль, а значение счетчика экземпляров для DLL-файла увеличивается. Затем деструктор его уменьшает. Теперь добавьте в программу следующее:

STDMETHODIMP Math:: QueryInterface( REFIID riid, void** ppv ){ *ppv = 0; if ( riid == IID_IUnknown || riid == IID_IMath ) *ppv = this; if ( *ppv ) { AddRef(); return( S_OK ); } return (E_NOINTERFACE); } STDMETHODIMP_(ULONG) Math:: AddRef(){ return InterlockedIncrement( & m_lRef ); } STDMETHODIMP_(ULONG) Math:: Release(){ if ( InterlockedDecrement( & m_lRef ) == 0 ) { delete this; return 0; } return m_lRef; }

 

Таким образом, будет обеспечена реализация трех методов интерфейса IUnknown. Наш компонент поддерживает только два интерфейса: обязательный IUnknown и пользовательский IMath. Функция QueryInterface проверяет возможность получения клиентом требуемого интерфейса, а также возвращает указатель на указатель виртуальной таблицы компонента. Перед возвратом указателя увеличивается значение внутреннего счетчика обращений. Это осуществляется с помощью вызова метода AddRef(). Реализации функций AddRef() и Release() ничем не отличаются от использованных ранее.

STDMETHODIMP Math:: Add( long lOp1, long lOp2, long* pResult ){ *pResult = lOp1 + lOp2; return S_OK; } STDMETHODIMP Math:: Subtract( long lOp1, long lOp2, long* pResult ){ *pResult = lOp1 - lOp2; return S_OK; } STDMETHODIMP Math:: Multiply( long lOp1, long lOp2, long* pResult ){ *pResult = lOp1 * lOp2; return S_OK; } STDMETHODIMP Math:: Divide( long lOp1, long lOp2, long* pResult ){ *pResult = lOp1 / lOp2; return S_OK; }

 

Мы получили текст относительно простой программы, но зато она поможет нам быстро прогрессировать в понимании СОМ. Следующий текст программы представляет собой реализацию класса C++ для фабрики классов.

MathClassFactory:: MathClassFactory(){ m_lRef = 0; } MathClassFactory:: ~MathClassFactory(){} STDMETHODIMP MathClassFactory:: QueryInterface( REFIID riid, void** ppv ){ *ppv = 0; if ( riid == IID_IUnknown || riid == IID_IClassFactory ) *ppv = this; if ( *ppv ) { AddRef(); return S_OK; } return(E_NOINTERFACE); } STDMETHODIMP_(ULONG) MathClassFactory:: AddRef(){ return InterlockedIncrement( & m_lRef ); } STDMETHODIMP_(ULONG) MathClassFactory:: Release(){ if ( InterlockedDecrement( & m_lRef ) == 0 ) { delete this; return 0; } return m_lRef; } STDMETHODIMP MathClassFactory:: CreateInstance ( LPUNKNOWN pUnkOuter, REFIID riid, void** ppvObj ){ Math* pMath; HRESULT hr; *ppvObj = 0; pMath = new Math; if ( pMath == 0 ) return( E_OUTOFMEMORY ); hr = pMath-> QueryInterface( riid, ppvObj ); if ( FAILED( hr ) ) delete pMath; return hr; } STDMETHODIMP MathClassFactory:: LockServer( BOOL fLock ){ if ( fLock ) InterlockedIncrement( & g_lLocks ); else InterlockedDecrement( & g_lLocks ); return S_OK; }

 

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

После сохранения предыдущего файла МАТН.СРР создайте новый и назовите его SERVER.CPP. Этот файл будет содержать главную программу реализации хранилища нашего компонента. Файлы IMATH.H, МАТН.Н и МАТН.СРР хранят тексты программ компонента. Теперь приведем код, с помощью которого наш компонент помещается в хранилище.

//// server.cpp: Defines the initialization routines for the DLL.// #include < windows.h> #include < initguid.h> #include " math.h" long g_lObjs = 0; long g_lLocks = 0; STDAPI DllGetClassObject( REFCLSID rclsid, REFIID riid, void** ppv ){ HRESULT hr; MathClassFactory *pCF; pCF = 0; // Make sure the CLSID is for our Expression component if ( rclsid! = CLSID_Math ) return( E_FAIL ); pCF = new MathClassFactory; if ( pCF == 0 ) return( E_OUTOFMEMORY ); hr = pCF-> QueryInterface( riid, ppv ); // Check for failure of QueryInterface if ( FAILED( hr ) ) { delete pCF; pCF = 0; } return hr; } STDAPI DllCanUnloadNow(void){ if ( g_lObjs || g_lLocks ) return( S_FALSE ); else return( S_OK ); }

 

Вначале мы включили в программу файл заголовков INITGUID.H, чтобы определить GUID, используемый в DLL-файле. Затем определили две глобальные переменные, отвечающие за подсчет обращений к хранилищу компонента. Имейте в виду: для того чтобы DLL-файл стал настоящим хранилищем компонентов, стандарт СОМ требует наличия В нем как минимум двух функций (на самом деле их четыре, но остальные две будут рассмотрены в следующих примерах). Сначала реализуем функцию DllGetClassObject. СОМ вызывает эту точку входа по требованию клиента компонента. Указанная функция проверяет, поддерживается ли затребованный клиентом компонент DLL-файлом. Удостоверившись в этом, мы создаем экземпляр фабрики классов для объекта Math и вызываем функцию QueryInterface из интерфейса, затребованного клиентом. Фабрика классов объекта Math поддерживает только интерфейсы IUnknown и IClassFactory. Если клиент или СОМ требует какой-либо другой интерфейс, то возвращается код ошибки. Благодаря двум глобальным переменным реализация функции DllCanUnloadNow намного упростилась. Проверяем, имеются ли ожидающие обработки экземпляры компонента Math, и подсчитываем количество вызовов функции LockServer. Если какая-либо из проверок дает положительный результат, DLL-файл не может быть выгружен.

Остался всего один шаг. Чтобы сделать обе функции, определенные в файле SERVER.CPP, доступными для общего пользования, требуется создать файл определений SERVER. DEF и ввести в него следующие строки:

;; Server.def: Declares the module parameters for the DLL.; LIBRARY " SERVER" DESCRIPTION 'SERVER Windows Dynamic Link Library' EXPORTS; Имена точек входа для внешнего пользования помещаются здесь DllGetClassObject PRIVATE DllCanUnloadNow PRIVATE

 

Прежде чем создавать проект, используйте элемент Files into project... меню Insert для включения в проект файлов МАТН.СРР, SERVER.CPP и SERVER. DEF, и лишь после этого приступайте к созданию проекта. Последним шагом будет регистрация компонента Math. К этой статье прилагается файл SERVER.REG, который выглядит следующим образом:

REGEDITHKEY_CLASSES_ROOT\Math.Component.1 = Chapter 6 Math ComponentHKEY_CLASSES_ROOT\Math.Component.1\CurVer = Math.Component.1HKEY_CLASSES_ROOT\Math.Component.1\CLSID = {A888F560-58E4-11d0-A68A-0000837E3100} HKEY_CLASSES_ROOT\CLSID\{A888F560-58E4-11d0-A68A-0000837E3100} = Chapter 6 Math ComponentHKEY_CLASSES_ROOT\CLSID\{A888F560-58E4-11d0-A68A-0000837E3100}\ProgID = Math.Component.1HKEY_CLASSES_ROOT\CLSID\{A888F560-58E4-11d0-A68A-0000837E3100}\VersionIndependentProgID = Math.ComponentHKEY_CLASSES_ROOT\CLSID\{A888F560-58E4-11d0-A68A-0000837E3100}\InprocServer32 = c: \book\chap6\server\debug\server.dllHKEY_CLASSES_ROOT\CLSID\{A888F560-58E4-11d0-A68A-0000837E3100}\NotInsertable

 

Если вы использовали в примере имеющиеся GUID, то вам потребуется изменить только информацию о расположении SERVER.DLL в файле SERVER.REG в разделе InProcServer32. Однако если были сгенерированы собственные GUID, необходимо во всех строках, содержащих CLSID, обновить значения GUID. После того как вы введете необходимую информацию или обновите файл SERVER.REG, включите, его в реестр с помощью утилиты REGEDIT или дважды щелкните на названии этого файла в окне Windows Explorer.

Теперь, после создания простого компонента Math, требуется организовать доступ к нему из приложения клиента, чтобы проверить, как он функционирует.

 

Приложение клиента

Клиентское приложение представляет собой простое консольное приложение (Console Application) Win32. С помощью утилиты AppWizard создайте консольное приложение с именем Client. Это опять-таки только основа проекта, и AppWizard предоставит только МАК-файл.

После этого мы создадим файл CLIENT.CPP и включим в него следующую программу:

//// Client.cpp// #include < windows.h> #include < tchar.h> #include < iostream.h> #include < initguid.h> #include "..\server\imath.h" int main( int argc, char *argv[] ){ cout < < " Initializing COM" < < endl; if ( FAILED( CoInitialize( NULL ))) { cout < < " Unable to initialize COM" < < endl; return -1; } char* szProgID = " Math.Component.1"; WCHAR szWideProgID[128]; CLSID clsid; long lLen = MultiByteToWideChar( CP_ACP, 0, szProgID, strlen( szProgID ), szWideProgID, sizeof( szWideProgID ) ); szWideProgID[ lLen ] = '\0'; HRESULT hr =:: CLSIDFromProgID( szWideProgID, & clsid ); if ( FAILED( hr )) { cout.setf( ios:: hex, ios:: basefield ); cout < < " Unable to get CLSID from ProgID. HR = " < < hr < < endl; return -1; } IClassFactory* pCF; // Получить фабрику классов для класса Math hr = CoGetClassObject( clsid, CLSCTX_INPROC, NULL, IID_IClassFactory, (void**) & pCF ); if ( FAILED( hr )) { cout.setf( ios:: hex, ios:: basefield ); cout < < " Failed to GetClassObject server instance. HR = " < < hr < < endl; return -1; } // с помощью фабрики классов создать экземпляр // компонента и получить интерфейс IUnknown. IUnknown* pUnk; hr = pCF-> CreateInstance( NULL, IID_IUnknown, (void**) & pUnk ); // Release the class factory pCF-> Release(); if ( FAILED( hr )) { cout.setf( ios:: hex, ios:: basefield ); cout < < " Failed to create server instance. HR = " < < hr < < endl; return -1; } cout < < " Instance created" < < endl; IMath* pMath = NULL; hr = pUnk-> QueryInterface( IID_IMath, (LPVOID*)& pMath ); pUnk-> Release(); if ( FAILED( hr )) { cout < < " QueryInterface() for IMath failed" < < endl; return -1; } long result; pMath-> Multiply( 100, 8, & result ); cout < < " 100 * 8 is " < < result < < endl; pMath-> Subtract( 1000, 333, & result ); cout < < " 1000 - 333 is " < < result < < endl; cout < < " Releasing instance" < < endl; pMath-> Release(); cout < < " Shuting down COM" < < endl; CoUninitialize(); return 0; }

 

В начале программы мы поместили файл заголовков IMATH.H из проекта сервера. Для определения GUID компонентов перед ним включен файл INITGUID.H. Функция main в первую очередь обеспечивает инициализацию библиотеки СОМ. В примере для определения CLSID используется ProgID компонента. Однако, прежде чем мы сможем вызвать CLSIDFromProgID, необходимо преобразовать ProgID (для которого используется кодировка ANSI) в строку с кодировкой Unicode. Все вызовы СОМ, OLE и ActiveX имеют встроенные реализации Unicode. Поэтому до передачи строк в любую API-функцию СОМ они должны быть преобразованы в вызовы с кодировкой Unicode.

После получения CLSID компонента вызываем функцию CoGetClassObject и запрашиваем указатель на интерфейс фабрики классов для компонента Math. После этого с помощью вызова CreateInstance создаем экземпляр компонента Math. Затем освобождаем интерфейс фабрики классов. Функция CreateInstance возвращает указатель на интерфейс IUnknown, с помощью которого мы в конце концов запрашиваем IMath. Получив указатель на него, используем сервисы компонента для выполнения некоторых простых операций.

По окончании всего этого мы освобождаем указатель на интерфейс IMath и вызываем CoUninitialize (это необходимо сделать до завершения работы приложения).

После ввода описанной выше программы включите в проект клиента файл CLIENT.CPP и постройте приложение. При пошаговой отладке клиента можно дойти даже до текста программы сервера. Не пожалейте времени и хорошо разберитесь в этих простых примерах клиента и сервера СОМ. Данные примеры помогут вам понять, что представляет собой СОМ-разработка. Теперь, после овладения основами СОМ, рассмотрим средство, несколько облегчающее СОМ-разработку, — библиотеку активных шаблонов.

 

 


Поделиться:



Популярное:

Последнее изменение этой страницы: 2016-04-11; Просмотров: 933; Нарушение авторского права страницы


lektsia.com 2007 - 2024 год. Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав! (0.021 с.)
Главная | Случайная страница | Обратная связь