Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Casting COM Interfaces [duplicate]

Tags:

c++

casting

com

I came across a problem in my code today where an access violation was being caused, AFAICT, by casting a COM object of mine to an IUnknown**. The function it was passed into executed without a problem but when calling one of my object's functions it would execute some random function and corrupt the stack then die.

Indicative code (just ignore why it's done this way - I know it's bad and I know how to fix it but this is a question of why problems like this may occur):

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);
}

I have always been a bit suspicious of using C/C++ casts on COM objects but I've never encountered (possibly through undefined behaviour) any problems until now.

I had a quick look and from what I can tell casting to IUnknown is technically valid so long as there is no multiple interitance in the inheritance chain, however it is not considered best practice - I should really pass an IUnknown to MyClass::GetInternalObject(IUnknown** lpUnknown) and then query the return value for the interface I want.

My question is, are there rules as to when C/C++ casts can be used on COM objects, and aside from multiple inheritance and the adjustor thunks they bring, how can casting COM objects result in surprises like access violations? Please be detailed.

Edit: They're all good examples of how it should be done properly but what I was hoping for was a technical explanation of why you shouldn't cast COM objects (assuming one exists) e.g. casting will return pMyObj2-4 in situation x but QueryInterface will return pMyObj2-8 because of y...or is casting COM objects simply a matter of bad practice/style?

TIA

like image 332
Sparkles Avatar asked Nov 15 '12 10:11

Sparkles


2 Answers

I'd just use CComPtr and CComQIPtr to manage COM interfaces, instead of writing code with C-style casts that to me seem inappropriate in the context of 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();
        }
    }
}

Moreover, I'm not a COM expert, but I see with suspicion your code:

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

If you are QueryInterface()'ing IID_MyInterface2, you should store that in an IMyInterface2*, not in an IUnknown*. If your method returns an IUnknown*, then I'd QueryInterface() an 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);
}

or better use IID_PPV_ARGS macro:

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

    // Write output parameter
    *ppUnknown = pUnk;
}

COM style casts have a specific name: QueryInterface().

like image 56
Mr.C64 Avatar answered Sep 30 '22 05:09

Mr.C64


I think the issue is that because a cast from IMyInterface* to IUnknown* is OK (in COM everything inherits from IUknown right?) you think that a cast from IMyInterface** to IUnknown** is also OK. But that's not true in C++, and I doubt it's true in COM either.

To me the following looks more logical, apologies if this isn't strictly correct, my COM is very rusty, but hopefully you get the idea.

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

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

I.e. get an IUnknown object first, and then down cast that to your actual type.

like image 33
john Avatar answered Sep 30 '22 07:09

john