Использует неявное преобразование для восходящего вместо QueryInterface () законный со множественным наследованием?

Предположите, что у меня есть класс, реализовывая два или больше COM-интерфейса (точно как здесь):

class CMyClass : public IInterface1, public IInterface2 { 
};

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

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

теперь существуют два IUnknowns в объекте - каждый - основа IInterface1 и другой основа IInterface2. И они находятся в различных подобъектах.

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

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

5
задан Community 23 May 2017 в 10:30
поделиться

4 ответа

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

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

3
ответ дан 14 December 2019 в 08:40
поделиться

Как указывает Ханс , ваша реализация QueryInterface верна.

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

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

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

MSDN: Правила модели компонентных объектов

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

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

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

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

2
ответ дан 14 December 2019 в 08:40
поделиться

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

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

UPDATED: Привет! Мне пришлось уехать сразу после написания ответа. Только сейчас я смог прочитать все остальные ответы и могу что-то добавить к своему ответу.

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

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

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

Для уменьшения количества vtables Microsoft ввела __declspec(novtable) или ATL_NO_VTABLE, но это не относится к вашему вопросу.

2
ответ дан 14 December 2019 в 08:40
поделиться

Если у вас есть интерфейс IInterface1 : IDispatch и интерфейс IInterface2 : IDispatch, то QI для IUnknown на IInterface1 и IInterface2 должны возвращать один и тот же указатель в соответствии с правилом идентификации объекта

но. ..

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

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

0
ответ дан 14 December 2019 в 08:40
поделиться
Другие вопросы по тегам:

Похожие вопросы: