Почему аргументы указателя COM приводятся как void вместо IUnknown? - PullRequest
2 голосов
/ 22 декабря 2019

Извиняюсь, если это глупый вопрос, но мне неясно, почему аргументы указателя COM обычно приводятся как (void**) вместо (IUnknown**). И иногда иногда используются IUnknown указатели, например IObjectWithSite::SetSite. Кто-нибудь может объяснить это?

Ответы [ 3 ]

2 голосов
/ 22 декабря 2019

В методах интерфейса "get-type" (например, IObjectWithSite::QueryInterface, IObjectWithSite::GetSite, IMoniker::BindToObject и т. Д.), Потому что это ничего не изменит, вам придется все равно разыграть, кроме случаев, когдатребуется ссылка IUnknown*, но она у вас уже есть, потому что ... вы используете ее (ссылка IUknown* всегда совпадает с указателем на правила COM ).

IObjectWithSite::SetSite является метод «набор типа», так что это имеет смысл, чтобы дать вам IUnknown* ссылки.

1013 * это, вероятно, более спорно в некоторых статические методы, такие как CoCreateInstance или CoGetObject Я думаю, что они могли бы поставить IUnknown** вместо void**, но тогда у них было бы два разных стиля. И вы не сможете использовать старый макрос IID_PPV_ARGS , который настолько практичен в использовании и рекомендован в качестве практики кодирования, чтобы избежать ошибок приведения типов.

Я предлагаю вам получить копиюавторитетный "Essential COM" от Don Box, и прочитайте до 60 страницы (по крайней мере: -).

0 голосов
/ 22 декабря 2019

Важный вынос

Это не имеет ничего общего с наследством или удобством. Сигнатуры функций являются результатом основ COM, позволяющих ему работать. Они требуются , чтобы быть набранными, как они есть. Если вам не хочется читать этот ответ, вот важные выводы: моральный эквивалент приведения в C ++ - это вызов QueryInterface в COM. Единственный раз, когда вам разрешено использовать приведение C ++ в COM, - это реализация QueryInterface.

деталей вашего COM-объекта * Подробности

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

Давайте выполним 2 мысленных эксперимента, начиная с того, который сделает COM непрактичным дляиспользуйте:

Предположим, что CoCreateInstance должен был вернуть IUnknown* вместо реального интерфейса, запрошенного через void*. В этом случае клиент должен был бы немедленно вызвать QueryInterface на возвращенном IUnknown*, чтобы получить указатель интерфейса, который он запросил 1 . Это не практично 2 .

Это сразу приводит к невозможности решения эксперимента. Давайте предположим, что QueryInterface вернул IUnknown*. Чтобы добраться до реального указателя интерфейса, клиенту нужно будет вызвать QueryInterface. Но это только возвращает IUnknown*! В этот момент поверхность интерфейса расходуемого COM сворачивается в единый интерфейс IUnknown.

Всякий раз, когда COM возвращает указатель на интерфейс, он должен возвращать указатель на конечный тип. Единственный тип языка программирования, который соответствует всем указателям на интерфейс, - это, действительно, void*. 3 . Это должно объяснить, почему выходные параметры нужно вводить void**, а не IUnknown**.

. Для IObjectWithSite :: SetSite , с другой стороны, IUnknown* является входом . Интерфейс по-прежнему принимает любой COM-интерфейс, но его необходимо передавать в качестве указателя на (сопоставимый по идентичности) интерфейс IUnknown.


1 COM не требует конкретной реализации объекта для реализаций. Вместо этого он делегирует запросы указателей на интерфейсы соответствующим реализациям QueryInterface.

2 Даже при игнорировании немедленной необходимости вызывать Release() на IUnknown для учета увеличенного количества ссылок как части вызова QI.

3 С Модель объектов компонентов : «Единственное требование языка для COM - это то, что код генерируется на языке, который может создавать структуры указателей и, явно или неявно, вызывать функции через указатели». Умышленно не требуется, чтобы наследование интерфейса осуществлялось с использованием конструкций языкового уровня. Даже когда для реализации IUnknown требуется IFoo, нет никакой связи между IFoo* и IUnknown* в языке программирования, таком как C.

0 голосов
/ 22 декабря 2019

Я предполагаю, что вы говорите о out-параметрах, которые "возвращают" значение через указатель, например:

IUnknown *u;
QueryInterface(IID_IUnknown, (void **)&u);

IDispatch *d;
QueryInterface(IID_IDispatch, (void **)&d);

Примечание: Смотрите здесь , если вы не знакомы с концепциейиспользования передачи по указателю для «возвращаемых» значений.


Прототип QueryInterface:

HRESULT QueryInterface(REFIID iid, void **ptr);

Аргумент &u будет иметь тип IUnknown **уже (поэтому нет причины приводить его к тому типу, который уже есть, как вы, похоже, предлагаете). Приведение заключается в изменении типа на ожидаемый тип для параметра функции.

В стандарте C ++ приведенный выше код фактически является неопределенным поведением (строгое нарушение псевдонимов - вы не можете притворяться, что указатель указывает на объект другого типачем на самом деле). Правильное использование этой функции:

void *temp;
QueryInterface(IID_IFoo, &temp);
IFoo *foo = static_cast<IFoo *>(temp);

Однако компилятор Microsoft поддерживает версию с приведением в стиле C. Он вносит те же изменения в область памяти, как если бы указатель интерфейса был объявлен как void * вместо его реального типа.


Почему QueryInterface принимает void **, а не IUnknown **? Хорошо, было бы неплохо избегать приведения, если вы специально запрашиваете IID_IUnknown, однако любой другой интерфейс в любом случае потребует приведения в стиле C. И это может вызвать путаницу, поскольку (для других интерфейсов) возвращаемый указатель не обязательно является допустимым значением IUnknown *.

В программировании на C ++ вы можете (и, возможно, должны) использовать классы-оболочки шаблонов, которые выполняют все нужные типыманипуляции. Вызовы API Windows совместимы с C, поэтому они не могут содержать обобщенные типы со строгим контролем типов.

NB. Все вызовы QueryInterface должны проверять возвращаемое значение, я здесь опущен для краткости.

...