Почему точно делают мне нужно явное восходящее при реализации QueryInterface () в объекте с несколькими интерфейсами ()

У Jeff Atwood есть сообщение на этом здесь: http://www.codinghorror.com/blog/archives/000229.html

Он в конечном счете пошел с Клавиатурой Редактирования Pro, потому что "На основе моей предшествующей истории использования, я чувствовал, что EditPad Pro был лучшим соответствием: это довольно быстро на файлах крупного текста, имеет лучшую среди аналогов поддержку regex, и это не симулирует быть IDE".

14
задан sharptooth 16 November 2009 в 15:20
поделиться

2 ответа

Проблема в том, что * ppv обычно является void * - прямое присвоение этого ему просто возьмет существующий этот указатель и присвоить ему значение * ppv (поскольку все указатели могут быть преобразованы в void * ).

Это не проблема с одиночным наследование, потому что при одиночном наследовании базовый указатель всегда один и тот же для всех классов (потому что vtable просто расширяется для производных классов).

Однако - для множественного наследования вы фактически получаете несколько базовых указателей, в зависимости от того, какой вид класса, о котором вы говорите! Причина этого в том, что при множественном наследовании вы не можете просто расширить vtable - вам нужно несколько vtables в зависимости от того, о какой ветке вы говорите.

[0] @A vtable
[1] a0
[2] b0
[3] @C vtable
[4] c0
[5] d0

Обратите внимание, что если вы обрабатываете таблицу D vtable как A , она будет работать (это совпадение - на нее нельзя полагаться). Однако - если вы обрабатываете vtable D vtable как C , когда вы вызываете c0 (который компилятор ожидает в слоте 0 vtable), вы внезапно будете вызов a0 !

Когда вы вызываете c0 на D , компилятор фактически передает поддельный указатель this , который имеет vtable, которая выглядит так, как должна для C .

Поэтому, когда вы вызываете функцию C на D , ей необходимо настроить vtable на наведите курсор на середину объекта D (в таблице @C vtable) перед вызовом функции.

полагаюсь на это). Однако - если вы обрабатываете vtable D vtable как C , когда вы вызываете c0 (который компилятор ожидает в слоте 0 vtable), вы внезапно будете вызов a0 !

Когда вы вызываете c0 на D , компилятор фактически передает поддельный указатель this , который имеет vtable, которая выглядит так, как должна для C .

Поэтому, когда вы вызываете функцию C на D , ей необходимо настроить vtable на наведите курсор на середину объекта D (в таблице @C vtable) перед вызовом функции.

полагаюсь на это). Однако - если вы обрабатываете vtable D vtable как C , когда вы вызываете c0 (который компилятор ожидает в слоте 0 vtable), вы внезапно будете вызов a0 !

Когда вы вызываете c0 на D , компилятор фактически передает поддельный указатель this , который имеет vtable, которая выглядит так, как должна для C .

Поэтому, когда вы вызываете функцию C на D , ей необходимо настроить vtable на наведите курсор на середину объекта D (в таблице @C vtable) перед вызовом функции.

27
ответ дан 1 December 2019 в 07:39
поделиться

Вы занимаетесь программированием на COM, поэтому есть несколько вещей, которые нужно вспомнить о вашем коде, прежде чем смотреть, почему QueryInterface реализован именно так.

  1. Оба IInterface1 и IInterface2 происходят от IUnknown , и давайте предположим, что ни один из них не является потомком другого.
  2. Когда что-то вызывает QueryInterface (IID_IUnknown, (void **) & intf) для вашего объекта, intf будет объявлен как тип IUnknown * .
  3. Существует несколько «представлений» вашего объекта - указателей интерфейса - и QueryInterface может быть вызван через любой из них.

Поскольку точка № 3, значение this в вашем определении QueryInterface может варьироваться. Вызов функции через указатель IInterface1 , и этот будет иметь другое значение, чем если бы он был вызван через указатель IInterface2 . В любом случае this будет содержать действительный указатель типа IUnknown * из-за точки №1, поэтому, если вы просто назначите * ppv = this , вызывающий будут счастливы, с точки зрения C ++ . Вы сохраните значение типа IUnknown * в переменной того же типа (см. Пункт 2), так что все в порядке.

Однако COM имеет более строгие правила, чем обычный C ++ .В частности, он требует, чтобы любой запрос к интерфейсу IUnknown объекта должен возвращать тот же указатель, независимо от того, какое «представление» этого объекта использовалось для вызова запроса. Следовательно, вашему объекту недостаточно всегда присваивать просто this в * ppv . Иногда вызывающие абоненты получали версию IInterface1 , а иногда - версию IInterface2 . Правильная реализация COM должна гарантировать стабильные результаты. Обычно для всех поддерживаемых интерфейсов используется лестничная диаграмма if - else , но одно из условий проверяет наличие двух интерфейсов вместо одного, второе - IUnknown :

if (iid == IID_IUnknown || iid == IID_IInterface1) {
  *ppv = static_cast<IInterface1*>(this);
} else if (iid == IID_IInterface2) {
  *ppv = static_cast<IInterface2*>(this);
} else {
  *ppv = NULL;
  return E_NOINTERFACE;
}
AddRef();
return S_OK;

Неважно, с каким интерфейсом сгруппирована проверка IUnknown , если группировка не изменится, пока объект все еще существует, но вам действительно придется выйти из ваш способ сделать это возможным.

8
ответ дан 1 December 2019 в 07:39
поделиться
Другие вопросы по тегам:

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