2010-07-06 1 views
5

Предположим, у меня есть класс, реализующий два или более COM интерфейсов (точно так, как here):Использует неявное преобразование для upcast вместо QueryInterface() с множественным наследованием?

class CMyClass : public IInterface1, public IInterface2 { 
}; 

QueryInterface() должен возвращать тот же указатель для каждого запроса одного и того же интерфейса (требуется явное вентиляционный для правильной настройки указателя) :

if(iid == __uuidof(IUnknown)) { 
    *ppv = static_cast<IInterface1*>(this); 
    //call Addref(), return S_OK 
} else if(iid == __uuidof(IInterface1)) { 
    *ppv = static_cast<IInterface1*>(this); 
    //call Addref(), return S_OK 
} else if(iid == __uuidof(IInterface2)) { 
    *ppv = static_cast<IInterface2*>(this); 
    //call Addref(), return S_OK 
} else { 
    *ppv = 0; 
    return E_NOINTERFACE; 
} 

теперь вы два IUnknown с в объекте - один является базой IInterface1, а другой является основой IInterface2. И .

Давайте представим, что я назвал QueryInterface() для IInterface2 - указатель, возвращаемый будет отличаться от указателя возвращается, когда я называю QueryInterface() для IUnknown. Все идет нормально. Затем я могу передать полученный IInterface2* в любую функцию, принимающую IUnknown*, и благодаря неявному преобразованию C++ указатель будет принят, но это будет не тот указатель, который будет получать QueryInterface() для IUnknown*. Фактически, если эта функция вызывает QueryInterface() для IUnknown сразу после ее вызова, она будет извлекать другой указатель.

Является ли это законным с точки зрения COM? Как мне обрабатывать ситуации, когда у меня есть указатель на многократно унаследованный объект, и я допускаю неявное повышение?

+0

Практически говоря, я никогда не видел, чтобы какой-либо код использовал правила COM-идентификации. Тем не менее, другой IUnknown * НЕ является законным - вы ДОЛЖНЫ выбрать один для возврата из QueryInterface. С точки зрения вашего собственного внутреннего использования на C++ объекта, реализующего объект COM, - если вы производите произвольное выполнение COM, так что все, что вы делаете, является законным C++, но не легитимирует COM. –

+0

@ Крис Бекке: Я думаю, что для реализации некоторых функций, подобных кешу, требуется идентификация. – sharptooth

ответ

3

COM не имеет правил, касающихся идентичности интерфейса, только для идентификации объекта. Первое правило QI говорит, что QI на IID_Unknown на двух указателях интерфейса должен возвращать один и тот же указатель, если они реализованы одним и тем же объектом . Ваша реализация QI делает это правильно.

Без гарантии идентичности интерфейса, метод COM не может предположить, что он получает тот же самый указатель IUnknown, который он будет получать, когда он вызывает QI на этом указателе. Поэтому, если идентификация объекта должна быть доказана, требуется отдельный QI.

2

Кажется, что есть небольшое недоразумение. Интерфейсы IInterface1 и IInterface2 являются абстрактные. Отдельных QueryInterface() для IInterface1 и IInterface2. Есть только декларация, что класс CMyClass будет применять все методы от IInterface1 и IInterface2 (набор методов CMyClass представляет собой набор методов IInterface1 и IInterface1 вместе).

Так вы реализуете в классе CMyClassодинQueryInterface(), одинAddRef() и одинRelease() метод. В поле QueryInterface() вы наводите указатель на экземпляр класса CMyClass на номер static_cast<IUnknown*>.

ОБНОВЛЕНО: Привет! Мне пришлось уехать сразу после написания моего ответа. Только теперь я мог прочитать все другие ответы и добавить что-то к моему ответу.

OK. Вы говорите, что если вы наложили IInterface1 на IUnknown, и если вы произнесли IInterface2 на IUnknown, вы получите два разных указателя. Ты прав! Но, тем не менее, это не имеет значения. Если вы сравниваете , то оба указателя (адреса которых в обоих случаях имеют QueryInterface(), AddRef() и Release()), вы увидите, что оба указателя одинаковы. Поэтому я тоже прав!

Есть много хороших примеров, как реализовать COM в чистом C.В этом случае вам нужно определить статические структуры с указателями на виртуальные функции, такие как QueryInterface(), AddRef() и Release(), и дать указателю такой структуры обратно в результате QueryInterface(). Он показывает еще один раз, что только COM, который вы даете, важна для COM, а не для указателя (неважно, какой vtable вы вернете).

Еще один маленький примечание. В некоторых комментариях вы пишете о возможности иметь много реализаций методов QueryInterface(), AddRef() и Release(). Я не согласен с этим. Причина в том, что интерфейсы являются чистыми абстрактными классами, и если вы определяете класс, реализующий некоторые интерфейсы, у вас нет типичного наследования классов. У вас есть только один класс с одной реализацией всех функций со всех интерфейсов. Если вы сделаете это на C++, то компилятор создает и заполняет статические vtables соответствующими указателями только для реализации функций QueryInterface(), AddRef(), Release() и т. Д., Но все vtables указывают на те же функции.

Чтобы уменьшить количество vtables, Microsoft представила __declspec(novtable) или ATL_NO_VTABLE, но это не часть ваших вопросов.

+0

Да, я это знаю. Но есть формальная проблема. Когда я неявно повышаюсь от 'IInterface2' до' IUnknown', я получаю указатель, отличный от того, который я получаю от 'QI()'. Весь вопрос о том, что «IUnknown» является «законным» в терминах COM? – sharptooth

+0

Существует множество реализаций этих методов, даже если все, кроме одного, пересылают вызов к единственной версии, которая его реализует. –

2

Как указано Hans, ваша реализация QueryInterface верна.

Пользователь пользователя COM-объекта для использования QueryInterface в любое время. Причина заключается именно в том, чтобы предотвратить описанный вами сценарий: приведение указателя IInterface1 * или IInterface2 * к указателю IUnknown * даст разные физические значения.

В C++ невозможно реализовать исполнитель, чтобы предотвратить неправильное выполнение пользователем.

ли это вызовет сбой в приложении зависит от того, заботится ли приложение о сравнении COM-объектов для идентификации.

MSDN: The Rules of the Component Object Model

идентичность объекта. Требуется, чтобы любой вызов QueryInterface на любой интерфейс для данного экземпляра объекта для конкретного интерфейса IUnknown всегда должна возвращать один и тот же физический значение указателя. Это дает возможность вызова QueryInterface (IID_IUnknown, ...) на любые два интерфейса и сравнивая результаты, чтобы определить ли они указывают на одного экземпляра объекта (тот же COM идентификатор объекта).

В Oleg указует, провал идентичности объекта будет иметь весьма ограниченный эффект, поскольку вызывающие члены COM-интерфейса, по существу, не влияют - каждая запись виртуальной таблицы будет указывать на тот же адрес функции, если функция подпись соответствует ,

Все реализации интеллектуального указателя COM используют QueryInterface при отливке на другой интерфейс или когда текущий интерфейс сомнительный. Операторы сравнения автоматически используют QueryInterface(IID_IUnknown, ...) для каждого указателя ввода, чтобы получить физические указатели IUnknown *, которые можно напрямую сравнить. Сбой идентификации объекта начнет влиять на ваше приложение, если вы решите использовать необработанные указатели во всем приложении.

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

+0

Я читал Правила, но ... Говорят, что 'QueryInterface()', а не что-то еще, должно следовать требованиям идентификации. Как это на самом деле означает, что скрытый взлет является незаконным? 'QI()' is 'QI()', конверсии на C++ - это преобразования на C++. – sharptooth

+0

Я не согласен. В цитате рассказывается о * объектной идентичности *, а не о идентичности интерфейса. –

+1

@sharptooth: Когда вы думаете в COM, старайтесь держаться подальше от мышления, которое IInterface1 наследует от IUnknown. Более точный способ сказать: IInterface1 содержит элементы AddRef, Release и QueryInterface, а также его собственные функции. – rwong

0

Если у вас есть interface IInterface1 : IDispatch и interface IInterface2 : IDispatch затем QI для IUnknown на IInterface1 и IInterface2 должен возвращать тот же указатель для каждого правила идентичности объекта

но ...

QI для IDispatch на IInterface1 может вернуть другую реализацию по сравнению до QI за IDispatch по телефону IInterface2.

Так что ответ (опять) это зависит от. Отрицательное значение для повышения до IUnknown, может быть положительным для повышения на что-либо еще.