## IID_PPV_ARGS Marco 我们已经知道 CoCreateInstance 和 QueryInterface 函数的最后一个参数都是 void** 类型,这就会带来潜在的类型不匹配的安全问题,考虑下面的代码段: ```cpp // Wrong! IFileOpenDialog *pFileOpen; hr = CoCreateInstance( __uuidof(FileOpenDialog), NULL, CLSCTX_ALL, __uuidof(IFileDialogCustomize), // The IID does not match the pointer type! reinterpret_cast(&pFileOpen) // Coerce to void**. ); ``` 在这段代码中,想要获取的接口类型是 IFileDialogCustomize,但是传入的却是一个 IFileOpenDialog 类型的指针变量。reinterpret_cast 表达式会绕过 C++ 类型系统的检测,所以编译器并不会主动抛出错误。 如果这段代码被运行,好的结果是函数返回失败,无法找到该接口;而坏的结果是,函数会返回成功,而你将获得一个类型结果不匹配的指针。换句话说,这个指针类型没有匹配到内存中真实的虚函数表,如果你所想不会有啥好事发生。 > 一个虚函数表(virtual method table)是一个函数指针表,它被用来实现 COM 组件运行期间的动态绑定。这种方式也是大多数 C++ 编译器用来实现动态绑定(多态)的方法。 IID_PPV_ARGS 宏是一个帮助你避免类型错误的方法,使用方法如下: ```cpp __uuidof(IFileDialogCustomize), reinterpret_cast(&pFileOpen) ``` 替换为 ```cpp IID_PPV_ARGS(&pFileOpen) ``` 这个宏会自动将 `__uuidof(IFileOpenDialog)` 参数插入。它可以保证你返回的接口指针类型的正确性。这是修改后的代码: ```cpp // Right. IFileOpenDialog *pFileOpen; hr = CoCreateInstance(__uuidof(FileOpenDialog), NULL, CLSCTX_ALL, IID_PPV_ARGS(&pFileOpen)); ``` 你可以在 QueryInterface 函数中使用一样的宏: ```cpp IFileDialogCustomize *pCustom; hr = pFileOpen->QueryInterface(IID_PPV_ARGS(&pCustom)); ``` ### 參考資料 - [COM 编码实践 - 没事造轮子](https://wangzhechao.com/com-bian-ma-shi-jian/)