Архитектура Аудит Военная наука Иностранные языки Медицина Металлургия Метрология Образование Политология Производство Психология Стандартизация Технологии |
Методы IUnknown::AddRef() и IUnknown::Release()
За управление временем жизни компонентов отвечают два метода интерфейса IUnknown: AddRef() и Release(). Обычно СОМ-компонент имеет несколько интерфейсов, каждый из которых может быть связан со многими внешними клиентами. Обратите внимание на то, что в нашем примере компонент в действительности является классом C++, а в данный момент мы обсуждаем управление временем жизни определенного экземпляра класса. Пользователь будет создавать экземпляр с помощью некоторого механизма, который мы еще обсудим, и использовать возможности этого экземпляра посредством его СОМ-интерфейсов. Первоначально экземпляр будет создан с помощью оператора C++ new, а затем мы попытаемся определить, когда этот экземпляр может быть удален. Поскольку экземпляр СОМ-компонента может иметь несколько интерфейсов, связанных со многими клиентами, нашему объекту необходимо иметь некоторую возможность подсчета обращений к нему (счетчик). Всякий раз, когда клиент запрашивает интерфейс, значение счетчика будет увеличиваться, а когда клиент завершает работу с интерфейсом – уменьшаться. В конце концов, когда значение счетчика обращений станет равным нулю, СОМ-компонент будет уничтожен. Именно для этого и служат методы IUnknown:: AddRef() и IUnknown:: Release(). Таким образом, в рассматриваемом нами примере класса Math требуется отслеживать значение внутреннего счетчика обращений. Назовем этот счетчик m_lRef. Когда компонент по запросу клиента возвращает интерфейс, значение счетчика будет увеличиваться, а по окончании использования интерфейса клиент уменьшает это значение посредством вызова IUnknown:: Release(). Пользователь компонента не может непосредственно удалить экземпляр С++-объекта, поскольку в его распоряжении имеется только указатель на виртуальную таблицу функций C++. В действительности клиент не имеет права удалять объект в любом случае, поскольку могут существовать другие клиенты, использующие тот же компонентный объект. Только сам компонент, основываясь на значении своего внутреннего счетчика обращений, может определить момент своего удаления. Ниже приводится текст программы компонента Math с включением реализации интерфейса IUnknown. class Math: public IMath{public: HRESULT QueryInterface(REFIID riid, void** ppv); ULONG AddRef(); ULONG Release(); long Add(long Op1, long Op2); long Subtract(long Op1, long Op2); long Multiply(long Op1, long Op2); long Divide (long Op1, long Op2); // Реализация private: // Новая переменная-член класса добавлена для подсчета // обращений извне к интерфейсу объекта DWORD m_lRef; public: Math( ); }; Math:: Math( ){ m_lRef = 0; }HRESULT Math:: QueryInterface( REFIID riid, void** ppv ){ switch( riid ) { case IID_IUnknown: case IID_IMath; *ppv = this; // Поскольку мы возвращаем новый указатель на // интерфейс, необходимо вызвать метод AddRef AddRef(); return ( S_OK ); default: return ( E_NOINTERFACE ); }} // Реализация функии IUnknown: ReleaseULONG Math:: Release( ){ InterlockedDecrement( & m_lRef ); // когда значение счетчика обращений // становится равным нулю, объект удаляет сам себя if ( m_lRef == 0 ) { delete this; // нельзя вернуть m_lRef, поскольку его уже не существует return 0; } else return m_lRef; } // Реализация функции IUnknown:: AddRefULONG Math:: AddRef( ){ InterlockedIncrement( & m_lRef ); return m_lRef; }
Для реализации функций AddRef() и Release(), унаследованных от класса IUnknown, мы ввели переменную-член m_lRef, которая отвечает за подсчет текущих обращений к объекту или ожидающих обработки указателей интерфейса. Хотя функции AddRef () и Release () могут изменять значение счетчика обращений к СОМ-интерфейсу, сам интерфейс не является экземпляром объекта. Объект в каждый момент времени может иметь любое количество пользователей своих интерфейсов и должен отслеживать значение внутреннего счетчика активных интерфейсов. Если это значение достигает нуля, объект сам себя удаляет. Очень важно правильно и своевременно использовать методы AddRef () и Release(). Эта пара функций аналогична паре операторов new и delete, используемых в языке C++ для управления памятью. Как только пользователь получает новый указатель на интерфейс или присваивает его значение какой-либо переменной, необходимо вызывать AddRef(). В данном случае следует быть очень внимательным, поскольку некоторые функции СОМ-интерфейсов возвращают указатели на другие интерфейсы и в таких случаях сами вызывают метод AddRef() для возвращаемого указателя. Наиболее наглядным примером этого является метод QueryInterface(), в котором AddRef() вызывается при каждом запросе интерфейса и, таким образом, не возникает необходимость в новом вызове метода AddRef(). Высокоуровневые средства разработки (такие, как VB) полностью скрывают реализацию IUnknown, облегчая тем самым работу программиста. Низкоуровневые средства разработки (например, VC++) напротив, дают полный доступ к исходным текстам, реализующим IUnknown. На C++ или Delphi можно самостоятельно реализовать IUnknown. Но проще воспользоваться стандартными реализациями. Для C++ самый удобный, но в тоже время гибкий и очень компактный способ реализации IUnknown – воспользоваться библиотекой ATL (Active Template Library). ATL – это библиотека, главным образом состоящая из набора шаблонов C++. ATL упрощает работу с COM, предоставляя реализации для многих стандартных интерфейсов. Множественные интерфейсы Одним из наиболее мощных свойств СОМ является то, что каждый компонент может предоставлять и обычно предоставляет несколько интерфейсов для одного объекта. Подумайте над этим еще раз. При разработке С++-класса создается всего один интерфейс. А при создании на основе класса компонента создается как минимум еще один интерфейс. Обычно при построении СОМ-компонента вам необходимо предоставить несколько интерфейсов для одного экземпляра класса C++. Объявление нескольких интерфейсов для одного класса C++ на первый взгляд не является трудным, но на самом деле может быть очень непростым делом. Во-первых, поскольку СОМ-интерфейсы фактически являются указателями на виртуальную таблицу функций C++, класс с множеством интерфейсов требует создания множества виртуальных таблиц. Другой причиной взаимосвязи компонентных объектов и их классов реализации интерфейса является необходимость подсчета количества обращений. Все интерфейсы СОМ-объекта должны взаимодействовать между собой в обеспечение подсчета обращений. Для каждого объекта существует только один счетчик обращений, и интерфейсы должны использовать его совместно. Эта взаимосвязь осуществляется с помощью множества интерфейсов IUnknown. Запомните: каждый СОМ-компонент должен обладать собственной реализацией интерфейса IUnknown. В языке C++ существуют три основных способа обеспечения множества интерфейсов для СОМ-компонента: - множественное наследование (Multiple Inheritance), - реализации интерфейсов (Interface Implementations), - вложенность классов C++ (C++ class nesting). Мы сосредоточимся на одном из этих трех способов – множественном наследовании классов C++, так как именно с помощью него в активной библиотеке шаблонов (ATL) реализованы СОМ-интерфейсы. Вложенность классов C++ применяется и в библиотеках базовых классов Microsoft (MFC). Язык C++ и множественное наследование В примере компонента Math мы использовали один интерфейс IMath. Однако, большинство СОМ-компонентов предоставляют несколько интерфейсов. Например, предположим, что компонент Math предоставляет еще и интерфейс анализатора выражений, определенного следующим образом: class IExpression: public IUnknown { public: virtual void SetExpression(string strExpression) = 0; virtual BOOL Validate() = 0; virtual BOOL Evaluate() = 0; };
Используя множественное наследование, мы могли бы снабдить класс Math обоими интерфейсами: class Math: public IMath, public IExpression { // Реализация компонента включает // и реализацию каждого наследуемого интерфейса ... };
Единственная проблема этого подхода состоит в том, что в таком случае может возникнуть конфликт базовых интерфейсов IUnknown (так как оба класса, и IMath, и IExpression, наследуются от IUnknown), однако в данном случае ничего подобного не происходит, поскольку унаследованный класс должен " совместно использовать" реализацию интерфейса IUnknown. Важным моментом здесь является то, что СОМ-компоненты должны предоставлять несколько интерфейсов или, другими словами, несколько указателей на виртуальные таблицы. Если обеспечивать это с помощью множественного наследования, то необходимо, чтобы тип указателя данного экземпляра правильно приводился к типу указателя на виртуальную таблицу. Таким образом, метод QueryInterface для описанного выше класса будет иметь вид: HRESULT Math:: QueryInterface(REFIID riid, void** ppv){ switch(riid) { case IID_IUnknown: case IID_IMath: // Множественное наследование требует явного приведения типов *ppv = (IMath*) this; break; case IID_IExpression: // Множественное наследование требует явного приведения типов *ppv = (IExpression*) this; break; default: return(E_NOINTERFACE); } // Возвращаем указатель нового интерфейса // и таким образом вызываем AddRef для нового указателя AddRef(); return(S_OK); }
Указатель на интерфейс IUnknown может быть возвращен посредством обращения к IMath или IExpression, поскольку оба эти класса содержат такой метод. Мы выберем обращение к IMath. Поддержка нескольких интерфейсов объекта представляет собой одну из наиболее привлекательных особенностей СОМ. В COM присутствует понятие класса. Класс в COM носит название CoClass. CoClass – это класс, поддерживающий набор методов и свойств (один или более), с помощью которых можно взаимодействовать с объектами этого класса. Такой набор методов и свойств называется интерфейсом (Interface). Каждый CoClass имеет два глобальных уникальных идентификатора – один из них, текстовый, называется ProgID и предназначен для человека, а второй, бинарный, называется CLSID. Глобальные уникальные идентификаторы (GUID) В распределенных объектных или компонентных средах уникальная идентификация компонентов имеет первоочередное значение. В СОМ используются технологии, описанные в стандарте распределенных вычислительных сред (Distributed Computing Environment – DCE) и используемые для вызовов удаленных процедур (Remote Procedure Call – RPC). Стандарт описывает и такое понятие, как универсальный уникальный идентификатор (Universally Unique Identifier – UUID). UUID представляет собой 128-разрядное значение, которому гарантируется статистическая уникальность. Это достигается путем сочетания уникального сетевого адреса (48 битов) с множеством других значений. Используемый в СОМ UUID называется глобальным уникальным идентификатором (Globally Unique Identifier – GUID), но в основных чертах он полностью идентичен UUID. В СОМ идентификаторы GUID используются для идентификации классов компонента (CLSID), интерфейсов (IID), библиотек типов и категорий компонентов (CATID). Ниже приведены GUID, используемые в нашем примере компонента Math. // {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);
Макрос DEFINE_GUID создает глобальную константу, которая может быть использована в любой вашей программе, как на стороне клиента, так и на стороне сервера. Однако ее значение определяется один раз. Система программирования СОМ предоставляет множество макросов, предназначенных для облегчения работы с глобальными идентификаторами. В той точке вашей программы, в которой требуется определить GUID-структуру, необходимо перед файлом заголовков с объявлениями включить файл INITGUID.H. Вот как это выглядит на нашем примере компонента Math: /// 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: 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; }; #include " client.h" #include < initguid.h> #include " imath.h"
За счёт включения в программу файла INITGUID.H мы отменяем значение макроса DEFINE_GUID таким образом, что он не просто объявляет тип переменной GUID, а создает и инициализирует переменную этого типа. В примере Math нам потребовались два GUID. Идентификатор CLSID идентифицирует сам компонент, a IID — пользовательский СОМ-интерфейс. Существует множество способов генерации GUID для компонентов. Если вы пользуетесь утилитами среды Visual C++ AppWizard и ClassWizard или мастером объектов ATL, то GUID будут генерироваться автоматически. Программно создать их можно с помощью СОМ-функции CoCreateGuid или двух программ Visual C++. Одна из них, UUIDGEN, вызывается из командной строки, и ее можно применить при создании последовательности GUID для целого проекта. Приведенное ниже содержимое командной строки обеспечит создание списка из 50 GUID и запись их в заданный файл. c: \msdev\bin\uuidgen -n50 > Project_Guids.txtДля создания экземпляра объекта используется CLSID. Если имеется только ProgID CoClass’а или CLSID в строковом виде (" {XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX} ", где X – шестнадцатеричная цифра), то CLSID можно получить, вызвав функцию CLSIDFromString. Для случая с ProgID информация о CoClass’е должна содержаться в реестре машины, на которой производится вызов функции. В реестр информация заносится автоматически при регистрации объекта (во время процедуры инсталляции или при компиляции). Библиотека СОМ API предоставляет несколько функций, предназначенных для сравнения, создания и преобразования типов GUID. Наиболее употребительные из них приведены в табл. 2. Таблица 2. Вспомогательные функции GUID
Реестр Информация, необходимая сервисам СОМ и приложениям-клиентам для размещения и создания экземпляров компонентов, хранится в реестре Windows. Обратившись к реестру, приложения могут определить количество и тип установленных в системе компонентов и т.д. Информация в реестре упорядочена иерархически и имеет несколько предопределенных высокоуровневых разделов. В этой лекции наибольшее значение для нас будет иметь раздел HKEY_CLASSES_ROOT, в котором хранится информация о компонентах. Важным подразделом HKEY_CLASSES_ROOT является CLSID (идентификаторы классов), в котором описывается каждый компонент, установленный в системе. Например, компонент Math, который мы создали, нуждается для своей работы в нескольких элементах реестра. Перечислим их: HKEY_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 Component HKEY_CLASSES_ROOT\CLSID\{A888F560-58E4-11d0-A68A-0000837E3100}\ProgID = Math.Component.1 HKEY_CLASSES_ROOT\CLSID\{A888F560-58E4-11d0-A68A-0000837E3100}\VersionIndependentProgID = Math.Component HKEY_CLASSES_ROOT\CLSID\{A888F560-58E4-11d0-A68A-0000837E3100}\InprocServer32 = c: \book\chap6\server\debug\server.dll HKEY_CLASSES_ROOT\CLSID\{A888F560-58E4-11d0-A68A-0000837E3100}\NotInsertable
В первых трех строках создается программный идентификатор (ProgID) для компонента Math. Идентификатор класса CLSID компонента является его уникальным идентификатором, однако он очень неудобен для чтения и запоминания. Понятие ProgID включено в СОМ для облегчения взаимодействия с компонентами разработчиков. Более подробно этот аспект будет рассмотрен в следующем разделе данной лекции. В третьей строке обеспечивается непосредственная связь между ProgID нашего элемента управления и соответствующим CLSID. В последних строках рассматриваемого фрагмента кода содержится вся информация, необходимая СОМ для помещения компонентов на хранение. Между ProgID и информацией о версии компонента существует взаимосвязь. Однако наиболее важным элементом является раздел InProcServer32, который описывает точное положение хранилища компонентов. В таблице 3 основные элементы реестра описаны более подробно. Таблица 3. Основные элементы реестра
Хорошим средством просмотра реестра с точки зрения СОМ является утилита OLEVIEW, предоставляемая Visual C++ и средой SDK. Она обеспечивает несколько различных подходов к использованию компонентов в вашей системе, а также обладает множеством других полезных свойств. Перевести CLSID, IID или любой другой GUID в строку можно с помощью функции StringFromGUID2. Как уже говорилось выше, практически все необходимые GUID генерируются автоматически, но при необходимости можно сгенерировать GUID вручную, с помощью утилиты guidgen. Компонент однозначно идентифицируется своим CLSID. Однако запоминание CLSID многочисленных компонентов может представлять затруднения. Поэтому предлагается другой механизм именования компонентов, а именно программный идентификатор, или ProgID, который представляет собой простую символьную строку, связываемую с определенным компонентом через реестр. Например, пусть для компонента Math мы выбрали ProgID равный Math.Component. С помощью ProgID определение удобного для понимания имени компонента намного упрощается, как, например, в этом фрагменте программы, написанной на Visual Basic: Dim objMath As ObjectSet objMath = CreateObject(" Math.Component" )objMath.Add(100, 100)Set objMath = Nothing
Функция Visual Basic CreateObject в качестве параметра принимает ProgID компонента. На самом деле для преобразования ProgID в действительный CLSID компонента оператор использует СОМ-функцию CLSIDFromProgID. А затем уже CreateObject создает экземпляр компонента с помощью функции CoCreateInstance.
Экземпляр СОМ-объекта Чтобы воспользоваться функциональностью COM-объекта, нужно создать его экземпляр (instance). Экземпляр COM-объекта аналогичен объекту в C++ или VB, за тем исключением, что он может быть создан в другом процессе или на другом компьютере. Для создания экземпляра COM-объекта на C++ применяются функции CoCreateInstance, CoCreateInstanceEx, CoGetObject, CoGetClassObject, CoGetInstanceFromFile, CoGetInstanceFromIStorage, OleLoadFromStream и некоторые другие. На VB все немного проще. Там есть две универсальные функции, CreateObject и GetObject, и оператор new. Все объекты VB являются COM-объектами. Чаще всего применяются функции CoCreateInstance, CoCreateInstanceEx, CreateObject и оператор new. Чтобы создать экземпляр объекта с помощью CoCreateInstance, этой функции необходимо передать: CLSID требуемого объекта, контекст создаваемого объекта, IID требующегося интерфейса, и указатель, в который и будет возвращен указатель на интерфейс созданного объекта. Контекст создаваемого объекта – это информация о том, где должен создаваться объект: в том же процессе (адресном пространстве EXE-модуля), в другом процессе того же компьютера, на удаленном сервере, или как локальная заглушка для удаленного объекта. Функция CoCreateInstance была создана на заре существования COM-модели и предназначена для локального COM. Она не позволяет создавать объекты на конкретном удаленном сервере, вместо этого она берет информацию о сервере из реестра. Информацию о местонахождении сервера и атрибуты защиты можно настроить с помощью утилиты DCOMCNFG или средств администрирования MTS или COM+. К недостаткам CoCreateInstance можно отнести также то, что при ее использовании нет возможности задать учетную запись (и пароль), от имени которой будет создаваться объект, и то, что она позволяет получить указатель только на один интерфейс создаваемого объекта. Для локально создаваемого объекта эти ограничения не существенны – атрибуты защиты при этом не работают (имеется прямой доступ к объекту), а указатель на другой интерфейс можно молниеносно получить с помощью вызова метода IUnknown:: QueryInterface (ведь по принципам COM любой COM-интерфейс должен быть унаследован от IUnknown). Вызов же QueryInterface у удаленного объекта приводит к передаче данных по сети, что значительно медленней локального вызова. Всех этих недостатков лишена новая версия этой функции – CoCreateInstanceEx. Остальные функции позволяют создать объект, загрузив его состояние из разных источников (OleLoadFromStream, CoGetInstanceFromFile, CoGetInstanceFromIStorage), или создать объект с помощью моникера и строки параметра (CoGetObject). Моникер – это COM-объект, умеющий создавать другой объект. ОС сама (по префиксу в строковом параметре) находит необходимый моникер, тот в свою очередь создает необходимый объект там, где ему необходимо, и возвращает указатель на интерфейс созданного объекта. Создание моникера – задача сложная, и по плечу она не каждому. Спасает то, что имеется много стандартных реализаций моникеров. Так, через них делается связывание документов в OLE, создание Queued-объектов (асинхронно работающих объектов повышенной надежности, о них речь пойдет дальше), и получение указателей на WMI-объекты, интерфейс новой технологии Windows 2000 – Active Directory. Существует интересная разновидность моникеров – URL-моникеры, которые позволяют получить указатель на объект, основываясь на URL.
Функции СОМ API Фирма Microsoft поставляет множество функций Win32 API, специально предназначенных для СОМ, ActiveX и OLE. Существует более сотни функций, предназначенных для работы с СОМ, поэтому мы не в состоянии здесь рассмотреть их все. API-функции СОМ обеспечивают основу высокоуровневых сервисов, таких как OLE и ActiveX. Нужно также не забывать, что СОМ – это только группа определений интерфейсов, которые должны быть реализованы пользователем, а вызовы API всего лишь дают возможность сделать это. В табл. 4 приведены API-функции, которые будут упомянуты в этой лекции. Таблица 4. Основные функции СОМ
Популярное:
|
Последнее изменение этой страницы: 2016-04-11; Просмотров: 1293; Нарушение авторского права страницы