无类型指针指针(void **)

4

我正在阅读一个关于COM示例的文章,链接地址为:http://msdn.microsoft.com/en-us/library/windows/desktop/dd389098(v=vs.85).aspx

但是我无法理解中的(void **)。

hr = pGraph->QueryInterface(IID_IMediaControl, (void **)&pControl);

我尝试了类中不同类型指针返回的一些值

class Point{
private:
    int x, y;
public:
    Point(int inputX, int inputY){x = inputX, y = inputY;}
    int getX(){return x;}
    int getY(){return y;}
    friend ostream& operator << (ostream &out, Point &cPoint);
    Point operator-(){
        return Point(-x, -y);
    }
};

ostream& operator << (ostream &out, Point &cPoint){
    return out<< "(" << cPoint.x << ", " << cPoint.y << ")";
}

并打印出来

Point *p = new Point(1,2);
cout << p << endl << &p << endl << endl
<< *&p << endl<< **&p << endl<<endl 
<< (void *) &p << endl << (void **) &p ;

(void *)与(void **)没有区别。而(void **)&pControl想要返回什么?

1
void* 是指向某些未知数据类型的指针。 void** 有一个更具体的类型,它是指向 void* 的指针,它们不同。除非 pControl 是一个 void*,否则这种行为是不安全的。如果它是一个 void*,那么转换就不是必要的。 - Ryan Haining
1
指针是特定于它们的类型的,唯一的例外是void*,它只是"未类型化"。但是指向(任何)指针的指针总是具有特定的类型。在这种情况下,底层声明void**是指向指向void的指针。需要进行类型转换,因为&pControl计算为指向特定类型的指针的地址(在您的情况下,指向pControl的指针的地址)。而void*可以保存来自任何数据类型的地址;而指向代码的指针(函数指针、成员指针等)则另当别论。 - WhozCraig
@HoyCheung 不好意思,我刚才弄错了URL语法,已经修改了。 - Ryan Haining
那么在我的Point类的情况下,&p已经是一个(Point **)。为什么强制转换(void *)和(void **)返回相同的数字?如果强制转换失败会发生什么? - Hoy Cheung
如果你真的想在COM方面做些事情,最好获取尽可能多的Don Box书籍副本。 - Mare Infinitus
显示剩余4条评论
1个回答

6
hr = pGraph->QueryInterface(IID_IMediaControl, (void **)&pControl);

What does (void **)&pControl want to return?

QueryInterface() 是COM接口的基本根接口 IUnknown 的三种方法之一。

MSDN文档中关于IUnknown::QueryInterface()的说明明确指出:

HRESULT QueryInterface(
  [in]   REFIID riid,
  [out]  void **ppvObject
);

ppvObject [out] The address of a pointer variable that receives the interface pointer requested in the riid parameter. Upon successful return, *ppvObject contains the requested interface pointer to the object. If the object does not support the interface, *ppvObject is set to NULL.

因此,在您的特定情况下,成功返回后,pControl将包含请求的指向IMediaControl接口的指针,如通过第一个参数IID_IMediaControl在函数调用中指定的那样。
现在,让我们尝试更好地理解为什么要双重指针间接引用:void **void *意味着“指向任何东西的指针”。
因此,人们可能会想:“为什么QueryInterface()的第二个参数不只是一个void*呢?”
问题在于,此参数是一个输出参数。这意味着QueryInterface()将向该参数中写入一些内容,以供调用者使用。
在C语言(COM具有几个C-ism)中,当您拥有一个输出参数时,必须使用一个指针*)。(注意在C++中,您也可以使用引用&。)
因此,在这种情况下,我们有了第一级void *间接引用,表示“指向任何东西的指针”。和第二级间接引用(另一个*),表示:“这是一个输出参数”。
您还可以这样考虑:
typedef void* PointerToAnything;

HRESULT QueryInterface(..., /* [out] */ PointerToAnything* pSomeInterface);

// pSomeInterface is an output parameter.
//
// [out] --> use * (pointer), 
// so it's 'PointerToAnything*' (not just 'PointerToAnything'),
// so, with proper substitution, it's 'void**' (not just 'void*').

那么为什么你想要使用 &pControl 呢?我认为我们使用对象的方式是 pControl->function()。使用 &pControl,我们需要使用 (*pControl)->function(),这会增加更多的麻烦。 - Hoy Cheung
1
在您的代码中,pControl 应该是指向某个 COM 接口(例如 IMediaControl*)的指针。但是当您将其传递给 QueryInterface() 时,必须指定它的地址,即 pControl 的地址,因此您使用 &pControl。这是因为 QueryInterface() 的第二个参数是一个输出参数。 - Mr.C64
不,QueryInterface()将某个COM接口的地址分配给指针。但是为了做到这一点,QueryInterface()需要指针的地址(即pControl的地址,即&pControl的地址)。 - Mr.C64
我现在明白你的意思了。谢谢。 - Hoy Cheung

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接