Сущность технологии COM

       

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


Содержание раздела