转换 COM 接口

8
今天在我的代码中遇到了一个问题,通过将我的COM对象转换为IUnknown**引起了访问冲突。当传入该函数时,它执行的很好,但当调用我对象的某些函数时,它会执行一些随机函数,破坏堆栈然后死机。
示例代码(忽略为什么要这样做 - 我知道这样做很糟糕,也知道如何解决,但这是一个关于为什么会出现这种问题的问题):
void MyClass2::func(IMyInterface* pMyObj)
{
    CComPtr<IMyInterface2> pMyObj2;
    HRESULT hRes = pMyObj->GetInternalObject((IUnknown**)&pMyObj2);

    if (SUCCEEDED(hRes))
        pMyObj2->Function(); // corrupt stack
}

void MyClass::GetInternalObject(IUnknown** lpUnknown)
{
    pInternalObject->QueryInterface(IID_IMyInterface2, (void**)lpUnknown);
}

我一直对在COM对象上使用C/C++强制转换有些怀疑,但直到现在我还没有遇到过任何问题(可能是由于未定义的行为)。

我简单地看了一下,从我的理解来看,将其强制转换为IUnknown在技术上是有效的,只要继承链中不存在多重继承,然而这并不被认为是最佳实践 - 我应该传递一个IUnknown到MyClass::GetInternalObject(IUnknown** lpUnknown),然后查询返回值以获取所需的接口。

我的问题是,是否有规则可以在COM对象上使用C/C++强制转换,除了多重继承和它们带来的调整器thunk之外,如何将COM对象强制转换导致访问冲突的惊喜?请详细说明。

编辑:它们都是如何正确执行的好例子,但我希望得到一个技术上的解释,说明为什么不应该强制转换COM对象(假设存在一个)。例如,在情况x中,强制转换将返回pMyObj2-4,但QueryInterface将返回pMyObj2-8,因为y的原因......或者说,强制转换COM对象只是一种不良实践/风格吗?

TIA


1
你正在追寻错误的问题。这种问题通常是版本问题的典型结果,某人在更改接口定义后没有更新接口标识符(IID)。因此,你要么调用了完全错误的方法,要么使用了错误的参数进行调用。与此组件的作者联系以解决此问题。 - Hans Passant
下面有几个答案展示了正确的方法。将IUnknown接口地址传递给GetInternalObject(),并查询所需的接口结果。破坏封送是不采用其他方式的许多原因之一。COM的规则很简单。您按IID查询接口,您会得到该IID的接口指针。如果它是从基础接口(如IUnknown)派生的,则可以使用这些方法。如果函数通过地址返回IUnknown,则不能安全地执行上述代码。 - WhozCraig
@Hans 我不认为这是版本问题 - 我遇到的问题非常具体,否则我可以毫无问题地使用相同的COM接口。 - Sparkles
你确定这两个对象在同一个进程和同一个公寓中吗? - acelent
3个回答

11

我会使用 CComPtrCComQIPtr 来管理COM接口,而不是使用带有C语言风格转换的代码,因为在COM上下文中,它们看起来不太合适:

void MyClass2::Func(IMyInterface* pMyObj)
{
    // Assuming:
    //   HRESULT IMyInterface::GetInternalObject( /* [out] */ IUnknown** )
    CComPtr<IUnknown> spUnk;       
    HRESULT hr = pMyObj->GetInternalObject(&spUnk);
    if (SUCCEEDED(hr))
    {
        // Get IMyInterface2 via proper QueryInterface() call.
        CComQIPtr<IMyInterface2> spMyObj2( spUnk );
        if ( spMyObj2 )
        {
            // QueryInterface() succeeded

            spMyObj2->Function();
        }
    }
}

此外,我并不是一个COM专家,但我对你的代码持怀疑态度:

void MyClass::GetInternalObject(IUnknown** lpUnknown)
{
    pInternalObject->QueryInterface(IID_IMyInterface2, (void**)lpUnknown);
}

如果您要 QueryInterface()IID_MyInterface2 上,应将其存储在 IMyInterface2* 而不是 IUnknown* 中。 如果您的方法返回一个 IUnknown*,那么我会 QueryInterface() 一个 IID_IUnknown

// NOTE on naming convention: your "lpUnknown" is confusing.
// Since it's a double indirection pointer, you may want to use "ppUnknown".
//
void MyClass::GetInternalObject(IUnknown** ppUnknown)
{
    pInternalObject->QueryInterface(IID_IUnknown, (void**)ppUnknown);
}

或者更好的方法是使用IID_PPV_ARGS宏:

void MyClass::GetInternalObject(IUnknown** ppUnknown)
{
    IUnknown* pUnk = NULL;
    HRESULT hr = pInternalObject->QueryInterface(IID_PPV_ARGS(&pUnk));
    // Check hr...

    // Write output parameter
    *ppUnknown = pUnk;
}

COM风格的类型转换有一个特定的名称:QueryInterface()


2
我认为问题在于将IMyInterface*强制转换为IUnknown*是可以的(因为在COM中所有东西都继承自IUnknown),所以您认为将IMyInterface**强制转换为IUnknown**也是可以的。但在C++中这是不正确的,我怀疑在COM中也是如此。
对我来说,以下看起来更合乎逻辑,如果这不严格正确,请谅解,我的COM已经很生疏了,但希望您能理解。
CComPtr<IUnknown> pMyObj2;
HRESULT hRes = pMyObj->GetInternalObject(&pMyObj2);

if (SUCCEEDED(hRes))
{
    CComPtr<IMyInterface> pMyObj3 = (IMyInterface*)pMyObj2;
    pMyObj3->Function();
}

即先获取一个IUnknown对象,然后将其向下转换为您实际需要的类型。


1
那么,是否有保证IMyInterface的内存布局是将其强制转换为IUnknown与调用IID_IUnknown的QueryInterface相同? - Sparkles
@Sparkles 对不起,我不知道。我只是回答因为你没有得到其他的答案。我的COM非常生疏,我是从C++的角度来回答的。关于C++中的真实情况是否也适用于COM,我不确定。如果你认为我在胡说八道,我会删除我的回答。 - john
CComPtr<IUnknown>强制转换为IMyInterface*的C风格代码会引发问题。在这种情况下,您应该始终使用QueryInterface() - 显式地(不要忘记CComPtr具有QueryInterface()成员函数)或以CComQIPtr的形式。 - sharptooth

0

我在你的代码片段中没有看到任何问题,堆栈损坏可能是由其他地方引起的。

我认为这不是你实际的代码,因为GetInternalObject应该是HRESULT类型,而你的不是,所以在复制/粘贴过程中丢失了一些内容。

为了更安全,最好避免直接调用QueryInterface,因为与转换一起使用时可能会误解接口。不过,将IUnknown*强制转换成其他类型可能是不可避免的。如果被调用方不能保证返回正确的接口并转换为IUnknown,则在调用方面,您可能更喜欢再次进行QI以确保持有您感兴趣的接口。

假设GetInternalObject本身是一个COM接口方法,您可以像这样使用它:

void MyClass2::func(IMyInterface* pMyObj)
{
    CComPtr<IUnknown> pMyObj2Unknown;
    pMyObj->GetInternalObject((IUnknown**)&pMyObj2Unknown);
    CComQIPtr<IMyInterface2> pMyObj2 = pMyObj2Unknown; // This is only needed if callee is not trusted to return you a correct pointer
    if (pMyObj2)
        pMyObj2->Function(); // corrupt stack
}

STDMETHODIMP MyClass::GetInternalObject(IUnknown** lpUnknown) // COM method is typically both HRESULT and __stdcall
{
    CComQIPtr<IMyInterface2> pMyInterface2 = pInternalObject;
    if(!pMyInterface2)
        return E_NOINTERFACE;
    *lpUnknown = pMyInterface2.Detach(); // *lpUnknown will have to me IMyInterface2 this way
    return S_OK;
}

PS:如果GetInternalObject是本地方法而不是COM,那么你完全可以避免强制转换为IUnknown*


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