传递“IUnknown *&”时,COM对象释放功能会出错作为参数

时间:2014-06-14 10:18:14

标签: c++ pointers com pass-by-reference directx-9

在标题中,包含以下代码。

inline void SafeRelease( IUnknown * & in_COM_Pointer )
    {
        if ( NULL != in_COM_Pointer )
        {
            in_COM_Pointer->Release();
            in_COM_Pointer = NULL;
        }
    }

当按以下方式使用时,

SafeRelease(_D3DDevice); // _D3DDevice is a "IDirect3DDevice9 *". According to documentation it is inherited from "IUnknown".

它给出了编译错误:

  

错误C2664:'SafeRelease':无法转换参数1   'IDirect3DDevice9 *'到'IUnknown *&'

我知道如何使用模板或宏编写此函数。但我想知道为什么会这样。

  1. 为什么会出错?
  2. 如何正确编写?
  3. 如果继承不能用于编写此函数,我应该使用模板吗?
  4. 使用建议的方法实现它时我需要注意什么?

4 个答案:

答案 0 :(得分:2)

  

注意:要完全掌握本文中的信息,需要了解lvalues and rvalues的一些知识。


简介

SafeRelease(_D3DDevice); // ill-formed

此处我们尝试将_D3DDevice的地址传递给 SetRelease ,但它不可调用,因为它需要 lvalue (引用)类型指针到的IUnknown

仅仅因为 Derived 继承自 Base ,并不意味着可以转换 Derived 左值指针指向 Base 左值类型指针。

Derived*Base*的隐式转换将产生右值

struct A     { };
struct B : A { };

void func (A*&);

B* p = ...;
func (ptr);      // ill-formed, the implicitly yield `A*` is not an lvalue,
                 //             and rvalues cannot bind to lvalues refernces

什么是“解决方案”

inline void SafeRelease( IUnknown * in_COM_Pointer );
SafeRelease (&_D3DDevice); // (A)

(A)将产生一个临时类型指向IDirect3DDevice9 的指针,该指针可以隐含地变成指向IUnknown 的指针,因为 IDirect3DDevice9 < / em>继承自 IUnknown

我们不再尝试将左值引用形成隐式yield指针,代码编译..


启示

...但是,这也意味着我们将无法更新传入的任何指针参数的值,因此如果这是一个要求,您应该/应该使用模板,以便您可以获得对实际值作为参数传递。

template<typename T>
inline void SafeRelease(T * & in_COM_Pointer);

答案 1 :(得分:2)

COM不允许转换接口指针,必须使用QueryInterface()。这是由C ++编译器强制执行的。像这样:

class Base {};
class Derived : /*public*/ Base {};

inline void SafeRelease(Base* & ptr) {}

void test() {
    auto p = new Derived;
    SafeRelease(p);      // C2664
}

您可以使用模板功能:

template<typename T>
inline void SafeRelease(T * & in_COM_Pointer) {
    if (NULL != in_COM_Pointer) {
        in_COM_Pointer->Release();
        in_COM_Pointer = NULL;
    }
}

答案 2 :(得分:1)

IDirect3DDevice9继承自IUnknown,但不完全是IUnknown。这使IDirect3DDevice9*变量与IUnknown*&参数不兼容。

因此,您需要在类型之间进行转换,或者使用更灵活的释放函数,基于模板,例如:

template <typename IFoo>
VOID SafeRelease(IFoo*& pFoo)
{
    if(!pFoo)
        return;
    pFoo->Release();
    pFoo = NULL;
}

IDirect3DDevice9* pDevice = ...
...
SafeRelease(pDevice);

或者更确切地说,它是开发准确性的重大改进,在原始接口指针上使用模板包装器,例如CComPtr

CComPtr<IDirect3DDevice9> pDevice = ...
...
pDevice.Release(); // or pDevice = NULL, 
           // or nothing - automatic release on going out of scope

答案 3 :(得分:1)

  

“1。为什么会出错?

考虑一下:

struct Animal {};
struct Dog: Animal { void bark() {} };
struct Dolphin: Animal { void dive() {} };

void foo( Animal*& p ) { p = new Dolphin(); }

auto main() -> int
{
    Dog* p = new Dog();
    foo( p );             //! C2664, Would have changed p to point to Dolphin.
    p->bark();            // Uh huh...
}

所以,这是不允许的。

还有更多相同的内容,例如关于深const - 实际与正式论证的关系,它通常被称为Liskov替代原则,LSP,在Barbara Liskov之后。


  

“2。如何正确编写?

一般问题的一个解决方案,如Hans Passant has already mentioned,就是使用模板来直接处理手头的类型或类型,而不是转换。

在这个具体案例中,只要您确定没有nullpointer,只需拨打p->Release()而不是SafeRelease( p )


  

“3。如果继承不能用于编写此函数,我应该使用模板吗?

你可以使用模板,但没有必要;见上文。


  

“4。使用建议的方法实施时我需要注意什么?

建议的方法涉及为COM接口指针设想的隐式转换Derived*Base*

请注意,虽然Derived*Base*转换通常也适用于COM接口,但IUnknown接口受特殊规则的约束。

即,COM对象内部可能有多个IUnknown子对象,对应于此对象的可能IUnknown*指针,但这些指针值只有一个标识对象

因此,当您需要一个标识对象的IUnknown指针时,可以与其他IUnknown指针进行比较的指针来检查它是否是同一个对象,您必须使用QueryInterface获取IUnknown指针。

很高兴您可以通过任何接口指针使用QueryInterface,并且由于此成员函数是通过所有其他接口继承的IUnknown接口提供的,因此它说明您可以使用非标识IUnknown指针用于除识别之外的其他目的。