Оптимизация QueryInterface

Оптимизация QueryInterface

Фактически реализация QueryInterface, показанная ранее в этой главе, очень проста и легко может поддерживаться любым программистом, имеющим хоть некоторое представление о СОМ и C++. Тем не менее, многие среды и каркасы приложений поддерживают реализацию, управляемую данными. Это помогает достичь большей расширяемости и эффективности благодаря уменьшению размера кода. Такие реализации предполагают, что каждый совместимый с СОМ класс предусматривает таблицу, которая отображает каждый поддерживаемый IID на какой-нибудь аспект объекта, используя фиксированные смещения или какие-то другие способы. В сущности, реализация QueryInterface , приведенная ранее в этой главе, строит таблицу, основанную на скомпилированном машинном коде для каждого из последовательных операторов if, а фиксированные смещения вычисляются с использованием оператора staticcast (staticcast просто добавляет смещение базового класса, чтобы найти совместимый с типом указатель vptr).

Чтобы реализовать управляемый таблицей QueryInterface, необходимо сначала определить, что эта таблица будет содержать. Как минимум, каждый элемент таблицы должен содержать указатель на IID и некое дополнительное состояние, которое позволит реализации найти указатель vptr объекта для запрошенного интерфейса. Хранение указателя функции в каждом элементе таблицы придаст этому способу максимальную гибкость, так как это даст возможность добавлять новые методики поиска интерфейсов к обычному вычислению смещения, которое используется для приведения к базовому классу. Исходный код в приложении к данной книге содержит заголовочный файл inttable.h , который определяет элементы таблицы интерфейсов следующим образом:

// inttable.h (book-specific header file)

// inttable.h (заголовочный файл, специфический для этой книги)

// typedef for extensibility function

// typedef для функции расширяемости

typedef HRESULT (*INTERFACEFINDER) (void *pThis, DWORD dwData, REFIID riid, void **ppv);

// pseudo-function to indicate entry is just offset

// псевдофункция для индикации того, что запись просто

// является смещением

#define ENTRYISOFFSET INTERFACEFINDER(-1)

// basic table layout // представление базовой таблицы

typedef struct INTERFACEENTRY

{

const IID * pIID;

// the IID to match

// соответствующий IID

INTERFACEFINDER pfnFinder;

// функция finder DWORD dwData;

// offset/aux data

// данные по offset/aux

} INTERFACEENTRY;

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

// Inttable.h (book-specific header file)

// Inttable.h (заголовочный файл, специфический для данной книги)

#define BASEOFFSET(ClassName, BaseName) (DWORD(staticcast<BaseName*>(reinterpretcast <ClassName*>(0x10000000))) – 0х10000000)

#define BEGININTERFACETABLE(ClassName) typedef ClassName ITCls; const INTERFACEENTRY *GetInterfaceTable(void) { static const INTERFACEENTRY table [] = {

#define IMPLEMENTSINTERFACE(Itf) {&IID##Itf,ENTRYISOFFSET,BASEOFFSET(ITCls,Itf)},

#define IMPLEMENTSINTERFACEAS(req, Itf) {&IID##req,ENTRYISOFFSET, BASEOFFSET(ITCls, Itf)},

#define ENDINTERFACETABLE() { 0, 0, 0 } }; return table; }

Все, что требуется, – это стандартная функция, которая может анализировать интерфейсную таблицу в ответ на запрос QueryInterface. Такая функция содержится в файле Inttable.h:

// inttable.cpp (book-specific source file)

// inttable.h (заголовочный файл, специфический для данной книги)

HRESULT InterfaceTableQueryInterface(void *pThis, const INTERFACEENTRY *pTable, REFIID riid, void **ppv)

{

if (InlineIsEqualGUID(riid, IIDIUnknown))

{

// first entry must be an offset

// первый элемент должен быть смещением

*ppv = (char*)pThis + pTable->dwData;

((Unknown*) (*ppv))->AddRef () ;

// A2

return SOK;

} else

{

HRESULT hr = ENOINTERFACE;

while (pTable->pfnFinder)

{

// null fn ptr == EOT

if (!pTable->pIID || InlineIsEqualGUID(riid,*pTable->pIID))

{

if (pTable->pfnFinder == ENTRYISOFFSET)

{

*ppv = (char*)pThis + pTable->dwData;

((IUnknown*)(*ppv))->AddRef();

// A2

hr = SOK;

break;

} else

{

hr = pTable->pfnFinder(pThis, pTable->dwData, riid, ppv);

if (hr == SOK) break;

}

}

pTable++;

}

if (hr!= SOK)

*ppv = 0;

return hr;

}

}

Получив указатель на запрошенный объект, InterfaceTableQueryInterface сканирует таблицу в поисках элемента, соответствующего запрошенному IID, и либо добавляет соответствующее смещение, либо вызывает соответствующую функцию. Приведенный выше код использует усовершенствованную версию IsEqualGUID, которая генерирует несколько больший код, но результаты по скорости примерно на 20-30 процентов превышают данные по существующей реализации, которая не управляется таблицей. Поскольку код для InterfaceTableQueryInterface появится в исполняемой программе только один раз, это весьма неплохой компромисс.

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

// impunk.h (book-specific header file)

// impunk.h (заголовочный файл, специфический для данной книги)

// AUTOLONG is just a long that constructs to zero

// AUTOLONG – это просто long, с конструктором,

// устанавливающим значение в О

struct AUTOLONG

{

LONG value;

AUTOLONG (void) : value (0) {}

};

#define IMPLEMENTUNKNOWN(ClassName)

AUTOLONG mcRef;

STDMETHODIMP QueryInterface(REFIID riid, void **ppv){

return InterfaceTableQueryInterface(this,

GetInterfaceTable(), riid, ppv);

}

STDMETHODIMP(ULONG) AddRef(void) {

return InterlockedIncrement(&mcRef.value);

}

STDMETHODIMP(ULONG) Release(void) {

ULONG res = InterlockedDecrement(&mcRef.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

IMPLEMENTUNKNOWN (PugCat)

BEGININTERFACETABLE(PugCat)

IMPLEMENTSINTERFACE(IPug)

IMPLEMENTSINTERFACE(IDog)

IMPLEMENTSINTERFACEAS(IAnimal,IDog)

IMPLEMENTSINTERFACE(ICat)

ENDINTERFACETABLE()

// 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 не требуется никакого дополнительного кода. Все, что осталось сделать, это реализовать текущие методы интерфейса, которые делают этот класс уникальным.

Более 800 000 книг и аудиокниг! 📚

Получи 2 месяца Литрес Подписки в подарок и наслаждайся неограниченным чтением

ПОЛУЧИТЬ ПОДАРОК