Оптимизация QueryInterface
Фактически реализация QueryInterface, показанная ранее в этой
главе, очень проста и легко может поддерживаться любым программистом,
имеющим хоть некоторое представление о СОМ и C++. Тем не менее, многие
среды и каркасы приложений поддерживают реализацию, управляемую данными.
Это помогает достичь большей расширяемости и эффективности благодаря уменьшению
размера кода. Такие реализации предполагают, что каждый совместимый с
СОМ класс предусматривает таблицу, которая отображает каждый поддерживаемый
IID на какой-нибудь аспект объекта, используя фиксированные смещения
или какие-то другие способы. В сущности, реализация QueryInterface,
приведенная ранее в этой главе, строит таблицу, основанную на скомпилированном
машинном коде для каждого из последовательных операторов if,
а фиксированные смещения вычисляются с использованием оператора static_cast
(static_cast просто добавляет смещение базового класса, чтобы
найти совместимый с типом указатель vptr).
Чтобы реализовать управляемый таблицей QueryInterface, необходимо
сначала определить, что эта таблица будет содержать. Как минимум, каждый
элемент таблицы должен содержать указатель на IID и некое дополнительное
состояние, которое позволит реализации найти указатель vptr объекта
для запрошенного интерфейса. Хранение указателя функции в каждом элементе
таблицы придаст этому способу максимальную гибкость, так как это даст
возможность добавлять новые методики поиска интерфейсов к обычному вычислению
смещения, которое используется для приведения к базовому классу. Исходный
код в приложении к данной книге содержит заголовочный файл inttable.h,
который определяет элементы таблицы интерфейсов следующим образом:
// inttable.h (book-specific header file)
// inttable.h (заголовочный файл, специфический для этой книги)
// typedef for extensibility function
// typedef для функции расширяемости
typedef HRESULT (*INTERFACE_FINDER)
(void *pThis, DWORD dwData, REFIID riid, void **ppv);
// pseudo-function to indicate entry is just offset
// псевдофункция для индикации того, что запись просто
// является смещением
#define ENTRY_IS_OFFSET INTERFACE_FINDER(-1)
// basic table layout
// представление базовой таблицы
typedef struct INTERFACE_ENTRY {
const IID * pIID;
// the IID to match
// соответствующий IID
INTERFACE_FINDER pfnFinder;
// функция finder
DWORD dwData;
// offset/aux data
// данные по offset/aux
} INTERFACE_ENTRY;
Заголовочный файл также содержит следующие макросы для создания интерфейсных
таблиц внутри определения класса:
// Inttable.h (book-specific header file)
// Inttable.h (заголовочный файл, специфический для данной книги)
#define BASE_OFFSET(ClassName, BaseName) \
(DWORD(static_cast<BaseName*>(reinterpret_cast\
<ClassName*>(0x10000000))) - 0х10000000)
#define BEGIN_INTERFACE_TABLE(ClassName) \
typedef ClassName _ITCls;\
const INTERFACE_ENTRY *GetInterfaceTable(void) {\
static const INTERFACE_ENTRY table [] = {\
#define IMPLEMENTS_INTERFACE(Itf) \
{&IID_##Itf,ENTRY_IS_OFFSET,BASE_OFFSET(_ITCls,Itf)},
#define IMPLEMENTS_INTERFACE_AS(req, Itf) \
{&IID_##req,ENTRY_IS_OFFSET, BASE_OFFSET(_ITCls, Itf)},
#define END_INTERFACE_TABLE() \
{ 0, 0, 0 } }; return table; }
Все, что требуется, - это стандартная функция, которая может анализировать
интерфейсную таблицу в ответ на запрос QueryInterface. Такая
функция содержится в файле Inttable.h:
// inttable.cpp (book-specific source file)
// inttable.h (заголовочный файл, специфический для данной книги)
HRESULT InterfaceTableQueryInterface(void *pThis,
const INTERFACE_ENTRY *pTable,
REFIID riid, void **ppv)
{
if (InlineIsEqualGUID(riid, IID_IUnknown)) {
// first entry must be an offset
// первый элемент должен быть смещением
*ppv = (char*)pThis + pTable->dwData;
((Unknown*) (*ppv))->AddRef () ; // A2
return S_OK;
}
else {
HRESULT hr = E_NOINTERFACE;
while (pTable->pfnFinder) { // null fn ptr == EOT
if (!pTable->pIID || InlineIsEqualGUID(riid,*pTable->pIID)) {
if (pTable->pfnFinder == ENTRY_IS_OFFSET) {
*ppv = (char*)pThis + pTable->dwData;
((IUnknown*)(*ppv))->AddRef(); // A2
hr = S_OK;
break;
}
else {
hr = pTable->pfnFinder(pThis, pTable->dwData, riid, ppv);
if (hr == S_OK) break;
}
}
pTable++;
}
if (hr != S_OK) *ppv = 0;
return hr;
}
}
Получив указатель на запрошенный объект, InterfaceTableQueryInterface
сканирует таблицу в поисках элемента, соответствующего запрошенному IID,
и либо добавляет соответствующее смещение, либо вызывает соответствующую
функцию. Приведенный выше код использует усовершенствованную версию IsEqualGUID,
которая генерирует несколько больший код, но результаты по скорости примерно
на 20-30 процентов превышают данные по существующей реализации, которая
не управляется таблицей. Поскольку код для InterfaceTableQueryInterface
появится в исполняемой программе только один раз, это весьма неплохой
компромисс.
Очень легко автоматизировать поддержку СОМ для любого класса C++, основанную
на таком табличном управлении, простым использованием С-препроцессора.
Следующий фрагмент из заголовочного файла impunk.h определяет
QueryInterface, AddRef и Release для объекта,
использующего интерфейсные таблицы и расположенного в динамически распределяемой
области памяти:
// impunk.h (book-specific header file)
// impunk.h (заголовочный файл, специфический для данной книги)
// AUTO_LONG is just a long that constructs to zero
// AUTO_LONG - это просто long, с конструктором,
// устанавливающим значение в О
struct AUTO_LONG {
LONG value;
AUTO_LONG (void) : value (0) {}
};
#define IMPLEMENT_UNKNOWN(ClassName) \
AUTO_LONG m_cRef;\
STDMETHODIMP QueryInterface(REFIID riid, void **ppv){\
return InterfaceTableQueryInterface(this,\
GetInterfaceTable(), riid, ppv);\
}\
STDMETHODIMP_(ULONG) AddRef(void) { \
return InterlockedIncrement(&m_cRef.value); \
}\
STDMETHODIMP_(ULONG) Release(void) {\
ULONG res = InterlockedDecrement(&m_cRef.value) ;\
if (res == 0) \
delete this;\
return res;\
}\
Настоящий заголовочный файл содержит дополнительные макросы для поддержки
объектов, не находящихся в динамически распределяемой области памяти.
Для реализации примера PugCat, уже встречавшегося в этой главе,
необходимо всего лишь удалить текущие реализации QueryInterface,
AddRef и Release и добавить соответствующие макросы:
class PugCat : public IPug, public ICat {
protected:
virtual ~PugCat(void);
public:
PugCat(void);
// IUnknown methods
// методы IUnknown
IMPLEMENT_UNKNOWN (PugCat)
BEGIN_INTERFACE_TABLE(PugCat)
IMPLEMENTS_INTERFACE(IPug)
IMPLEMENTS_INTERFACE(IDog)
IMPLEMENTS_INTERFACE_AS(IAnimal,IDog)
IMPLEMENTS_INTERFACE(ICat)
END_INTERFACE_TABLE()
// IAnimal methods
// методы IAnimal
STDMETHODIMP Eat(void);
// IDog methods
// методы IDog
STDMETHODIMP Bark(void);
// IPug methods
// методы IPug
STDMETHODIMP Snore(void);
// ICat methods
// методы ICat
STDMETHODIMP IgnoreMaster(void);
};
Когда используются эти макросы препроцессора, для поддержки IUnknown
не требуется никакого дополнительного кода. Все, что осталось сделать,
это реализовать текущие методы интерфейса, которые делают этот класс уникальным.
Типы данных
Атрибуты и свойства
Исключения
Где мы находимся?
|