Когда я разрабатываю в COM, я всегда вижу (void **) преобразование типов, как показано ниже.
QueryInterface(/* [in] */ REFIID riid,/* [out] */ void** ppInterface)
Что именно это означает?
ИМХО, он говорит компилятору не применять проверку типа, поскольку тип, на который указывает ppInterface, не известен клиентскому коду во время компиляции.
Спасибо ~~ ~
Я так понимаю:
void * p подразумевает AnyType * p
void ** pp подразумевает указатель на AnyType *
Если void ** pp означает «указатель на void *», тогда какие проверки выполняет компилятор, когда он его видит?
Передача с помощью void * также гарантирует, что указанный объект не может быть удален или подделан (случайно).
«Это означает, что объект не может быть удален с помощью указателя типа void *, потому что нет объектов типа void».
Это позволяет API чтобы указать, что указатель может использоваться как параметр [in-out] в будущем, но на данный момент указатель не используется. (Обычно обязательным значением является NULL.)
При возврате одного из многих возможных типов без общего супертипа (например, с QueryInterface), возвращение void * действительно является единственным вариантом, и, поскольку это необходимо передать как Параметр [out] необходим указатель на этот тип (void **).
не применять проверку типа
Действительно, void *
или void **
существуют, чтобы разрешить использование разных типов указателей, которые могут быть понижены до ] void *
, чтобы соответствовать типу параметров функции.
Это указатель на указатель интерфейса, который вы запрашиваете с помощью этого вызова. Очевидно, вы можете запрашивать все виды интерфейсов, поэтому это должен быть указатель void. Если интерфейс не существует, указатель устанавливается в NULL.
редактировать: Подробную информацию можно найти здесь: http://msdn.microsoft.com/en-us/library/ms682521 (VS.85) .aspx
Это просто указатель на void *
.
Например:
Something* foo;
Bar((void**)&foo);
// now foo points to something meaningful
Изменить: Возможная реализация на C #.
struct Foo { }
static Foo foo = new Foo();
unsafe static void Main(string[] args)
{
Foo* foo;
Bar((void**)&foo);
}
static unsafe void Bar(void** v)
{
fixed (Foo* f = &foo)
{
*v = f;
}
}
A void **
- указатель на void *
. Это можно использовать для передачи адреса переменной void *
, которая будет использоваться в качестве выходного параметра - например:
void alloc_two(int n, void **a, void **b)
{
*a = malloc(n * 100);
*b = malloc(n * 200);
}
/* ... */
void *x;
void *y;
alloc_two(10, &x, &y);
Указатель на указатель неизвестного интерфейса, который может быть предоставлен.
Вместо использования указателей на указатели попробуйте использовать ссылку на указатель. Это немного больше C ++, чем использование **.
например.
void Initialise(MyType &*pType)
{
pType = new MyType();
}
Причина, по которой COM использует void**
с QueryInterface
, несколько особенная. (См. ниже.)
Вообще, void**
означает просто указатель на void*
, и он может использоваться для out-параметров, т.е. параметров, указывающих место, куда функция может вернуть значение. Ваш комментарий /* [out] */
указывает на то, что место, на которое указывает ppvInterface
, будет записано.
"Почему параметры с типом указателя могут использоваться как параметры out?", спросите вы? Помните, что с помощью переменной-указателя можно изменить две вещи:
ptr = ...
)*ptr = ...
)Указатели передаются в функцию по значению, т.е. функция получает свою локальную копию исходного указателя, который был ей передан. Это означает, что вы можете изменить параметр указателя внутри функции (1), не затрагивая исходный указатель, поскольку изменяется только его локальная копия. Однако вы можете изменить указатель на объект (2), и это будет видно вне функции, поскольку копия имеет то же значение, что и исходный указатель, и, следовательно, ссылается на тот же объект.
Теперь конкретно о COM:
Указатель на интерфейс (заданный riid
) будет возвращен в переменную, на которую ссылается ppvInterface
. QueryInterface
достигает этого с помощью механизма (2), упомянутого выше.
При использовании void**
, один *
необходим для обеспечения механизма (2); другой *
отражает тот факт, что QueryInterface
возвращает не вновь созданный объект (IUnknown
), а уже существующий: Чтобы избежать дублирования этого объекта, возвращается указатель на этот объект (IUnknown*
).
Если вы спрашиваете, почему ppvInterface
имеет тип void**
, а не IUnknown**
, что казалось бы более разумным с точки зрения безопасности типов (поскольку все интерфейсы должны происходить от IUnknown
), то прочитайте следующий аргумент, взятый из книги Essential COM Дона Бокса, стр. 60 (глава Type Coercion and IUnknown):
Еще одна тонкость, связанная с
QueryInterface
, касается его второго параметра, который имеет типvoid **
. Очень иронично, чтоQueryInterface
, лежащий в основе системы типов COM, имеет довольно небезопасный с точки зрения типов прототип в C++ [...]IPug *pPug = 0; hr = punk->QueryInterface(IID_IPug, (void**)&pPug);
К сожалению, для компилятора C++ одинаково корректно выглядит следующее:
IPug *pPug = 0; hr = punk->QueryInterface(IID_ICat, (void**)&pPug);
Эта более тонкая вариация также компилируется правильно:
IPug *pPug = 0; hr = punk->QueryInterface(IID_ICat, (void**)pPug);
Учитывая, что правила наследования не применяются к указателям, это альтернативное определение
QueryInterface
не снимает проблему:HRESULT QueryInterface(REFIID riid, IUnknown** ppv);
К ссылкам применимо то же ограничение, что и к указателям. Следующее альтернативное определение, возможно, более удобно для использования клиентами:
HRESULT QueryInterface(const IID& riid, void* ppv);
[...] К сожалению, это решение не уменьшает количество ошибок [...] и, устраняя необходимость приведения, устраняет визуальный индикатор того, что безопасность типов C++ может быть под угрозой. Учитывая желаемую семантику
QueryInterface
, типы аргументов, выбранные Microsoft, являются разумными, если не безопасными для типов или элегантными. [...]