Допустимо ли использование неявного преобразования для выгрузки вместо QueryInterface () с множественным наследованием? - PullRequest
5 голосов
/ 06 июля 2010

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

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 s - один является основой IInterface1, а другой - IInterface2. И они находятся в разных подобъектах .

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

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

Ответы [ 4 ]

3 голосов
/ 06 июля 2010

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

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

2 голосов
/ 06 июля 2010

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

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

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

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

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

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

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

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

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

2 голосов
/ 06 июля 2010

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

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

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

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

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

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

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

0 голосов
/ 07 июля 2010

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

но ...

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

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

...