Интерфейсы

Определение интерфейса

Интерфейс DLL – это набор функций, экспортируемых ею.

Инерфейс класса С++ – это набор членов данного класса.

Интерфейс COM  –  это не только набор функций, но и определенная структура в памяти, содержащая массив указателей на функции, где каждый элемент массива содержит адрес функции, реализуемой компонентом.

В языке прогаммирования С++ интерфейс реализуется с помощью абстрактных базовых классов.

Каждый компонент может поддерживать множество интерфейсов. И, следовательно, для реализации компонента с несколькими интерфейсами используется множественное наследование абстрактных базовых классов.

Реализация интерфейса

CОМ интерфейсы в С++ формируются на основе чисто абстрактныхе базовых классов (такие классы содержат только чисто виртуальные функции (virtual fx() = 0 ;)).Чисто виртуальные функции только объявляются, а реализуются в производных классах.

Будем называть наследование от чисто абстрактного базового класса  – наследованием интерфейса.

При использовании чисто абстрактных базовых классов в памяти создается определенная структура – таблица виртуальных функций (vtbl).

 

Microsoft Win32 SDK содержит заголовочный файл OBJBASE.H, в котором определен интерфейс:

            #define interface struct

В среде проектирования Borland Developer Studio в языке Object Pascal (Delphi for Windows32) interface является ключевым словом и сразу реализуется как COM-интерфейс.

Так как в разных языках программирования передача параметров выполняется по-разному, то необходимо соглашение о передаче параметров. Технология COM использует соглашение о вызове функций _stdcall : функция выбирает параметры из стека перед возвратом в вызывающую процедуру. В С++ – стек очищает вызывающая процедура (обязательно и для переменного числа параметров).

Интерфейс – это набор функций. Компонент – набор интерфейсов. Система – набор компонентов.

На следующей схеме приведены два компонента, каждый из которых реализует интерфейс IX1 и IX2.

 

Компонент 1

 

Компонент 2

 

Fx1

 

Fx1

 

Fx2

 

Fx2

 o–

IX1                            

  o–

IX1                            

 

Fxn

 

Fxn

 

 

 

 

 

Fx1

 

Fx1

 

Fx2

 

Fx2

 o–

IX2                            

  o–

IX2                            

 

Fxn

 

Fxn

 

 

 

 

 

 

 

 

 

После того, как интерфейс опубликован, его нельзя изменять.

Публикация – это описание интерфейса и кодов ответа успешного выполнения.

Таблица виртуальных функций

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

                                   Интерфейс IX

                                                                                  Табл. вирт. функций

pIX

®

Указатель vtbl

®

&Fx1

®

 

 

 

 

&Fx2

®

 

 

 

 

&Fx3

®

 

 

 

 

&Fx4

®

 

Таблица виртуальных функций – это массив указателей на реализации виртуальных функций для интерфейса.

Для приведенной выше схемы объявление интерфейса выглядит следующим образом:

                        interface IX

                                   { virtual void __stdcall Fx1() = 0;

                                     virtual void __stdcall Fx2() = 0;

                                     virtual void __stdcall Fx3() = 0;

                                     virtual void __stdcall Fx4() = 0;  };

Реализация этого интерфейса может быть записана как:

class CA : public IX

            { public:          // Реализация интерфейса IX

                                   virtual void __stdcall Fx1() {count << "CA::Fx1"<<endl ;}

                                   virtual void __stdcall Fx2() {count << "CA::Fx2"<<endl ;}

                                               …

            // Конструктор

                        CA (double d) : m_Fx2(d*d), m_Fx3(d*d*d)    {  }

            // Данные экземпляра

                        double m_Fx2;

                        double m_Fx3;

            };

Классы С++ позволяют работать с данными напрямую, а интерфейсы – нет. Доступ к данным COM-сервера должен осуществляться только через соответствующие методы интерфейса.

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

 

Интерфейс IX

Клиент                                                                          Табл. вирт. функций          CA

pA

®

Указатель vtbl

®

&Fx1

®

Fx1

 

 

&m_Fx2

®

&Fx2

®

Fx2

 

 

&m_Fx3

®

&Fx3

®

Fx3

 

 

 

 

&Fx4

®

Fx4

 

Объявление COM интерфейса

Для вызова метода COM-компонента  клиент запрашивает у компонента интерфейс. Требование обязательного запроса интерфейса делает систему прозрачной для изменения версий.

IUnknown – это специальный интерфейс (определен в заголовочном файле UNKNWN.H Win32 SDK), который наследуют все COM-интерфейсы.

Этот интерфейс расположен в верхней части таблицы виртуальных функций и имеет следующее формальное описание.

interface IUnknown

{

virtual HRESULT __stdcall QueryInterface ( const IID& iid, void** ppv)=0;

virtual ULONG __stdcall AddRef() =0;

virtual ULONG __stdcall Release() = 0;

}

 

pIX

®

Указатель vtbl

®

QueryInterface

®

QueryInterface

 

 

 

®

QueryInterface

®

QueryInterface

 

 

 

®

Release

®

Release

 

 

 

 

&Fx

®

Fx

 

Таким образом, чтобы интерфейс был COM интерфейсом, – он должен наследоваться от интерфейса IUnknown.

Для получение указателя на IUnknown можно использовать функцию CoCreateInstance из COM-библиотеки.

 

Спецификация СОМ определяет следующие соглашения по реализации метода QueryInterface:

§  клиент всегда получает один и тот же IUnknown;

§  клиент сможет получить интерфейс снова, если смог получить его раньше;

§  клиент всегда может снова получить интерфейс, который у него уже есть;

§  клиент всегда может вернуться туда, откуда начал (т.е., если IX получается через IY, то это возможно повторят многократно);

§  если клиент каким либо способом получил доступ к некоторому интерфейсу, то сможет получить к немуе доступ и другим способом (при наличии других интерфейсов).

, поддерживаемые компонентом – это те интерфейсы, указатели на которые возвращает метод QueryInterface.

В модели DCOM может использоваться QueryMultiInterface – для запроса нескольких интерфейсов за один вызов.

Контроль ссылок на компонент

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

§  в разных местах кода клиента могут быть вставлены вызовы этого компонента через различные интерфейсы и клиент может окончить использование одного интерфейса раньше другого;

§  сложно определить момент удаления компонента из памяти, т.к. не ясно указывают ли два указателя на интерфейсы одного и того же компонента (поэтому надо будет запрашивать IUnknown через оба интерфейса и сравнивать результаты);

§  возможность загрузить компонент и не выгружать до конца выполнения программы не обеспечивает эффективного выполнения;

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

Функции AddRef и Release описываются в компоненте и реализуют технику управления памятью, называемую подсчет ссылок (reference couting). Это позволяет компонентам самим себя удалять – когда значение счетчика доходит до нуля.

Существует три основных правила:

  1. Функции, вызывающие интерфейсы, должны вызывать и AddRef для соответствующего указателя. (Это также относится и к QueryInterface и функции CreateInstance. Поэтому в клиенте не следует вызывать AddRef после получения указателя на интерфейс.)
  2. При завершении работы с интерфейсом следует вызывать Release.
  3. При создании новой ссылки на интерфейс (присвоении значения одного указателя другому) всегда следует вызывать AddRef.

 

Компонент может поддерживать:

§  отдельные счетчики по каждому интерфейсу и, следовательно, клиент должен вызывать AddRef и Release именно для конкретного указателя на интерфейс (pIUnknown->Release() может не сработать);

§  общий счетчик на все интерфейсы.

Коды ответа

Все доступные коды ответа содержатся в файле Win32 WINERROR.H.

( #define E_NOINTERFACE 0x80004002L  )

Код возврата –  это32 битовое значение:

31р – Признак критичности  | 30-16рр – Средство                       | 15-0 Код возврата

 

В файле Win32 WINERROR.H предусмотрен набор кодов ответа, включая следующие значения кодов ответа:

S_OK – функция выполнена успешно

NOERROR –

S_FALSE

E_UNEXPECTED          неожиданная ошибка

E_NOTIMPL                   не реализовано

E_NOINTERFACE – интерфейс не найден

E_OUTOFMEMORY – нехватает памяти.

E_FAIL                ошибка по неуказанной причине

Все идентификаторы средств кроме FACILITY_ITF задют СОМ универсальные  коды ответа. Эти коды всегда и везде одни и те же.

Код ответа имеет тип HRESULT. Методы СОМ-компонентов должны возвращать код ответа типа HRESULT.

Макросы SUCCEEDED и FAILED позволяют обрабатывать несколько кодов ответа.

GUID

Любой компонент и интерфейс регистрируется в реестре Windows, по своему уникальному идентификатору – GUID.

GUID это 128 битовое уникальное значение (48 – уникально для компьютера и 60 – для времени) (16 байтов).

GUID может формироваться вызовом GuidGen.exe.

На следующем рисунке приведен диалог, используемый для формирования уникального идентификатора GUID.

Для сравнения GUID в файле OBJBASE.H определены методы IsEqullGUID, IsEqualIID и IsEqualCLSID.

Идентификатор класса (GUID) имеет тип CLSID.

Любой компонент и все его интерфейсы регистрируются в реестре Windows.

Реестр Windows состоит из разделов. Раздел содержит

§  подразделы

§  набор именованных параметров

§  один параметр по умолчанию.

Ветвь дерева HKEY_CLASSES_ROOT содержит разделы:

§  CLSID – 16 байтовые идентификаторы класса компонента и дружественное имя компонента (в параметре по умолчанию)

§  InProcServer32  – местоположение компонента

§  ProgID  – текстовый идентификатор компонента (программный идентификатор)

§  VersionIndependedProgID – текстовый идентификатор компонента без номера версии

§  Текстовый идентификатор компонента (с подразделами CLSID и CurVer);

§  CATID – идентификатор категории компонентов;

§  IID – информация об интерфейсах (используется для доступа к интерфейсу через границы процессов);

§  TypeLib – библиотека типа, содержащая информацию о параметрах функций-членов интерфейсов. (Связывание с именем файла, в котором хранится библиотека типа);

§  APPID – связывание идентификатора приложения APPID с именем удаленного сервера.

Реестр можно редактировать в редакторе реестра REGEDIT.EXE.

Для того чтобы зарегистрировать в реестре компонент, расположенный в DLL, используются функции   DllRegisterServer и  DllUnregisterServer.

Также зарегистрировать компонент можно вызовом REGSRV32.EXE.

Для того, чтобы выполнить регистрацию программно вызовом DllRegisterServer, следует загрузить DLL, вызвав метод LoadLibrary и GetProcAddress.

Реализация DllRegisterServer – это простой код обновления реестра. Для регистрации компонента или удаления его из реестра могут использоваться следующие функции: RegOpenKeyEx, RegCreateKeyEx, RegSetValueEx, RegEnumKeyEx, RegDeleteKey, RegCloseKey.

Категории компонентов

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

Категория компонентов – это набор интерфейсов, которым присвоен CLSID, называемый в данном случае CATID. Все входящие в категорию интерфейсы обязаны быть реализованы компонентом.

Компонент должен реализовать все интерфейсы категории

Компонент может входить в несколько категорий и поддерживать интерфейсы, не входящие в категорию.

Компонент регистрирует себя сам в некоторой категории. Этим компонент гарантирует, что он поддерживает все входящие в категорию интерфейсы.

Управление памятью

Для управления памятью используется специальный менеджер, который позволяет:

§  компоненту передать клиенту блок памяти;

§  клиенту освободить этот блок памяти.

Управление памятью можно осуществлять различными способами:

§  1.  

§   Менеджер используется через интерфейс, называемый IMalloc.

§   Этот интерфейс возвращается функцией CoGetMalloc.

§   Для выделения и освобождения блока памяти используются функции:       
         IMalloc::Alloc и IMalloc::Free.

§  2.

§   Использовать функции CoTaskMemAlloc и CoTaskMemFree.

Например:

void* CoTaskMemAlloc (ULONG cb); // размер выделяемого блока в байтах

void CoTaskMemFree (void* pv);     // указатель на освобождаемый блок памяти

В реестре содержатся строковые представления CLSID.

 

Библиотека COM содержит следующие функции для преобразования значений:

StringFromCLSID

Получает значение CLSID в виде строки

StringFromIID

Получает значение IID в виде строки

StringFromGUID2

Конвертирует GUID в строку символов Unicode (двухбайтовые символы)

wchar_t  szCLSID[58];

int r= StringFromGUID2(CLSID_Component1,szCLSID,58);

далее для преобразования в обычную строку можно использовать функцию wcstombs(CLSID_S,szCLSID,58),

где char CLSID_S [58];

CLSIDFromString

Получает значение CLSID

IIDFromString

Получает значение IID

 

Для получения и освобождения строки можно использовать следующий код:

            wchar_t* string;

            // Получить строку из CLSID

            :: StringFromCLSID(CLSID_Component1, &string);

            // Использовать строку, а затем освободить

            ::CoTaskMemFree(string);

Фабрики класса

Библиотека СОМ для создания других компонентов предоставляет функцию

CoCreateInstance (файл OLE32.DLL – для динамической компоновки или OLE32.LIB – для статической компоновки).

Процесс создания компонента имеет следующее формальное описание:

            CoInitialize(NULL);  //Инициализация библиотеки

                HRESULT __stdcall CoCreateInstance(

                                    const CLSID& clsid,    // CLSID компонента

                                    IUnkown* pIUnknownOuter,   // Внешний компонент

                                    DWORD dwCont, // Внешний контекст

                                    const IID& iid,  // Запрашиваемый интерфейс

                                    void** ppv);  // Указатель на запрашиваемый интерфейс

            CoUninitialize();    // Отключить библиотеку

 

Контекст класса определяется следующими константами (или их совокупностью):

§  CLSCTX_INPROC_SERVER            – Клиент использует только те компоненты, которые исполняются в одном с ним процессе.

§  CLSCTX_INPROC_HANDLER        – Используются обработчики в процессе – это компонент внутри процесса, который реализует часть компонента, а другая часть реализуется компонентом вне процесса: локальным или удаленным сервером.

§  CLSCTX_LOCAL_SERVER  Используемые компоненты выполняются в другом процессе (но на той же машине) – это EXE файлы.

§  CLSCTX_REMOTE_SERVER           – допускаются компоненты, выполняющиеся на другой машине (требуется DCOM).

Преимущества и недостатки различных контекстов: скорость и совместная память.

Файл OBJBASE.H содержит константы, позволяющие объединять контексты:

§  CLSCTX_INPROC

§  CLSCTX_ALL

§  CLSCTX_ SERVER

 

Фабрика классов (ФК)  – это компонент, предоставляющий сервисы для создания других компонентов. Но только соответствующие конкретному CLSID в созданной фабрике класса.

ФК инкапсулирует создание компонента. ФК создается тем же разработчиком, что и сам компонент. И, как правило, содержится в той же DLL, что и компонент.

На самом деле часто CoCreateInstance создает компонент – фабрику класса, а затем ФК порождает нужный компонент.

COM библиотека для работы с ФК предоставляет интерфейс  IClassFactory.

IClassFactory – это стандартный интерфейс создания компонентов.

Для его применения следует:

1.    Создать компонент – фабрику класса (CoGetClassObject) и получить указатель IClassFacory
  
HRESULT hr= CoGetClassObject(clsid,
                              dwClsContex,
                               NULL,
                               IID_IClassFactory,
                               (void)** &pIFactory);

2.    Используя IClassFacory создать сам компонент
    hr= pIFactory->CreateInctance(pIUnknownOuter, iid,ppv);

3.    Освободить фабрику класса
pIFactory->Release();

 

Метод CoCreateInstance по CLSID возвращает указатель на интерфейс компонента, а метод CoGetClassObject по CLSID возвращает указатель на интерфейс ФК для данного CLSID.

Метод CoGetClassObject имеет следующее формальное описание:

            HRESULT __stdcall CoGetClassObject(

                        const CLSID& clsid,

                        DWORD dwCont,                            // Внешний контекст

                        COSERVERINFO* pServerInfo    // Зарезервировано для DCOM

                        const IID& iid,

                        void** ppv);

Интерфейс IClassFacory имеет следующее формальное описание:

interfase IClassFactory: IUnknown

            {

                HRESULT __stdcall CreateInstance(

                                    IUnkown* pIUnknownOuter,   // Внешний компонент

                                    const IID& iid,

                                    void** ppv);

                HRESULT __stdcall LockServer(BOOL block);

            }

Microsoft также объявила интерфейс IClassFactory2 для создания компонентов с поддержкой лицензирования или разрешения на создание. Клиент обязан передать фабрике класса посредством IClassFactory2 конкретный ключ или лицензию, для того чтобы ФК смогла создавать компоненты.

Функция DllGetClassObject  определяет точку входа DLL и имеет следующее формальное описание:

            STDAPI DllGetClassObject (

                                               const CLSID& clsid,

                                               const IID& iid,

                                               void** ppv);

Эта функция вызывается из CoGetClassObject.

Рассмотрим более подробно процесс создания компонента посредством ФК:

1.    Клиент вызывает CoCreateInstanse, реализованную в СОМ-библиотеке;

2.    CoCreateInstanse реализована с помощью CoGetClassObject;

3.    CoGetClassObject отыскивает компонент в реестре;

4.    Если найден, то CoGetClassObject загружает DLL (являющуюся сервером компонента);

5.    CoGetClassObject вызывает DllGetClassObject (реализованную DLL-сервером);

6.    DllGetClassObject создает фабрику класса оператором new;

7.    DllGetClassObject дополнительно запрашивает у фабрики класса интерфейс IClassFactory;

8.    Этот интерфейс IClassFactory возвращается для CoCreateInstanse;

9.    CoCreateInstanse вызывает функцию CreateInstanse этого интерфейса IClassFactory;

10.CreateInstanse создает компонент (используя new) и запрашивает интерфейс IX.

 

На следующей схеме приведен способ применения одной фабрики класса , используемой для создания нескольких компонентов:

Клиент

 

DLL

Вызывает CoCreateInstance

a

DllGetClassObject

 

CLSID_1  |  &CreateFunction_1

 

 

a

 

CLSID_2  |  &CreateFunction_2

 

 

 

 

 

 

 

Фабрика класса

 

CLSID_n  |  &CreateFunction_n

 

 

 

 

 

 

 

Компонент 1

 

Компонент n

 

 

    

 |

 

 

 

 CreateFunction_1

 

 CreateFunction_1

 

 

 

 

 

 

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

1.    Для каждого компонента есть своя функции CreateFunction (создает компонент с помощью  new  и возвращает указатель IUnknown);

2.    Из указателей на эти функции строится таблица, индексируемая CLSID каждого компонента;

3.    Функция DllGetClassObject      
    – отыскивает в таблице указатель нужной функции        
    – создает фабрику класса и передает ей этот указатель;

4.    ФК вместо оператора new вызывает соответствующую функцию создания компонента.

Можно использовать один код ФК для создания нескольких компонентов, но один экземпляр ФК всегда создает только компоненты одного CLSID.

Вы можете следить за любыми ответами на эту запись через RSS 2.0 ленту. Вы можете оставить ответ, или trackback с вашего собственного сайта.

Оставьте отзыв

XHTML: Вы можете использовать следующие теги: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

 
Rambler's Top100