8

我今天在我的代码中遇到了一个问题,通过将我的 COM 对象转换为 IUnknown** 导致了访问冲突,AFAICT。它传入的函数执行没有问题,但是当调用我的对象的一个​​函数时,它会执行一些随机函数并破坏堆栈然后死。

指示性代码(忽略为什么这样做 - 我知道它很糟糕,我知道如何修复它,但这是为什么会出现这样的问题的问题):

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

4

3 回答 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()'ing 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().

于 2012-11-15T13:31:07.477 回答
2

我认为问题在于,因为从IMyInterface*to的转换IUnknown*是可以的(在 COM 中,一切都继承自IUknown对吗?)你认为从IMyInterface**toIUnknown**的转换也可以。但这在 C++ 中并非如此,我也怀疑在 COM 中也是如此。

对我来说,以下看起来更合乎逻辑,如果这不是严格正确的,请道歉,我的 COM 非常生锈,但希望你明白。

CComPtr<IUnknown> pMyObj2;
HRESULT hRes = pMyObj->GetInternalObject(&pMyObj2);

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

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

于 2012-11-15T10:54:01.237 回答
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*

于 2012-11-15T13:52:00.430 回答