Интерфейсы — основа СОМ-технологии
lUnknown
{
public: virtual HRESULT _stdcall Querylnterface(REFIID riid,
void **ppvObject) = 0;
virtual ULONG _stdcall AddRef(void) = 0;
virtual ULONG _stdcall Release(void) = 0;
};
Как видите, «неизвестный» содержит три чисто виртуальные функции и ни одного элемента данных. Каждый новый интерфейс, который создает разработчик, должен иметь среди своих предков I Unknown, а следовательно, он наследует все три указанных метода. Первый метод Querylnterface представляет собой фундаментальный механизм, используемый для получения доступа к желаемой функциональности СОМ-объекта. Он позволяет получить указатель на существующий интерфейс или получить отказ, если интерфейс отсутствует. Первый — входной параметр riid — содержит уникальную ссылку на зарегистрированный идентификатор желаемого интерфейса. Это та уникальная, вечная бирка (клеймо), которую конкретный интерфейс должен носить вечно. Второй — выходной параметр — используется для записи по адресу ppvOb j ect адреса запрошенного интерфейса или нуля в случае отказа. Дважды использованное слово адрес оправдывает количество звездочек в типе void**. Тип возвращаемого значения HRESULT, обманчиво относимый к семейству handle (дескриптор), представляет собой 32-битное иоле данных, в котором кодируются признаки, рассмотренные нами в четвергом уроке.
Предположим, вы хотите получить указатель на какой-либо произвольный интерфейс 1Му, уже зарегистрированный системой и получивший уникальный идентификатор IID_IMY, с тем чтобы пользоваться предоставляемыми им методами. Тогда следует действовать по одной из общепринятых схем1:
//====== Указатель на незнакомый объект
lUnknown *pUnk;
// Иногда приходит как параметр IМу *рМу;
// Указатель на желаемый интерфейс
//====== Запрашиваем его у объекта
HRESULT hr=pUnk->Query!nterfасе(IID_IMY,(void **)&pMy);
if (FAILED(hr)) // Макрос, расшифровывающий HRESULT
{
//В случае неудачи
delete pMy; // Освобождаем память
//====== Возвращаем результат с причиной отказа
return hr;
else //В случае успеха
//====== Используем указатель для вызова методов:
pMy->SomeMethod();
pMy->Release(); // Освобождаем интерфейс
Возможна и другая тактика:
//====== В случае успеха (определяется макросом)
if (SUCCEEDED(hr))
{
//====== Используем указатель
}
else
{
//====== Сообщаем о неудаче
}
Второй параметр функции Queryinterf асе (указатель на указатель) позволяет возвратить в вызывающую функцию адрес запрашиваемого интерфейса. Примерная схема реализации метода Queryinterf асе (в классе СОМ-объекта, производном от IМу) может иметь такой вид:
HRESULT _stdcall СМу::Queryinterfасе(REFIID id, void **ppv)
{
//=== В *ppv надо записать адрес искомого интерфейса
//=== Пессимистический прогноз (интерфейс не найден)
*ppv = 0;
// Допрашиваем REFIID искомого интерфейса. Если он
// нам не знаком, то вернем отказ E_NOINTERFACE
// Если нас не знают, но хотят познакомиться,
// то возвращаем свой адрес, однако приведенный
// к типу "неизвестного" родителя
if (id == IID_IUnknown)
*ppv = static_cast<IUnknown*>(this);
// Если знают, то возвращаем свой адрес приведенный
// к типу "известного" родителя IМу
else if (id == IID_IMy)
*ppv = static_cast<IMy*>(this);
//===== Иначе возвращаем отказ else
return E_NOINTERFACE;
//=== Если вопрос был корректным, то добавляем единицу
//=== к счетчику наших пользователей
AddRef();
return S_OK;
}
Методы AddRef и Release управляют временем жизни объектов посредством подсчета ссылок (references) на пользователей интерфейса. В соответствии с общей концепцией объект (или его интерфейс) не может быть выгружен системой из памяти, пока не равен нулю счетчик ссылок на его пользователей. При создании интерфейса в счетчик автоматически заносится единица. Каждое обращение к AddRef увеличивает счетчик на единицу, а каждое обращение к Release — уменьшает. При обнулении счетчика объект уничтожает себя сам. Например, так:
ULONG СМу::Release()
{
//====== Если есть пользователи интерфейса
if (—m_Ref != 0)
return m_Ref; // Возвращаем их число
delete this;
// Если нет — уходим из жизни,
// освобождая память
return 0;
}
Вы, наверное, заметили, что появилась переменная m_Ref. Ранее было сказано об отсутствии переменных у интерфейсов. Интерфейсы — это голая функциональность. Но обратите внимание на тот факт, что метод Release принадлежит не интерфейсу 1Му, а классу ему, в котором переменные естественны. Обычно в классе СОМ-объекта и реализуются чисто виртуальные методы всех интерфейсов, в том числе и главного интерфейса zunknown. Класс ему обычно создает разработчик СОМ-объекта и производит его от желаемого интерфейса, например, так:
class СМу : public IMy
{
// Данные и методы класса,
// в том числе и методы lUnknown
};
В свою очередь, интерфейс IMy должен иметь какого-то родителя, может быть, только iUnknown, а может быть одного из его потомков, например:
interface IMy : IClassFactory
{
// Методы интерфейса
};
СОМ-объектом считается любой объект, поддерживающий хотя бы lUnknown. Историческое развитие С ОМ-технологий определило многообразие терминов типа: OLE 94, OLE-2, OCX-96, OLE Automation и т. д. Элементы ActiveX принадлежат к той же группе СОМ-объектов. Каждый новый термин из этой серии подразумевает все более высокий уровень предоставляемых интерфейсов. Элементы ActiveX должны как минимум обладать способностью к активизации на месте, поддерживать OLE Automation, допуская чтение и запись своих свойств, а также вызов своих методов.