C++ casting Windows IAction

189 Views Asked by At

I'm working on retrieving some info from windows task scheduler. MSDN indicates there are several types of action. I want to deal with them separately. I tried:

IAction* pAction = NULL;
pActionCollection->get_Item(_variant_t(i), &pAction);
if (IExecAction* pExecAction = dynamic_cast<IExecAction*>(pAction)) { /*my work...*/ }
if (IComHandlerAction* pComHandlerAction = dynamic_cast<IComHandlerAction*>(pAction)) { /*my work...*/ }
if (IEmailAction* pEmailAction = dynamic_cast<IEmailAction*>(pAction)) { /*my work...*/ }
if (IShowMessageAction* pShowMessageAction = dynamic_cast<IShowMessageAction*>(pAction)) { /*my work...*/ }

But this program throws exception at the first dynamic_cast.

Exception thrown at 0x00007FFB516365A5 (vcruntime140d.dll) in myProgram.exe: 0xC0000005: Access violation reading location 0x00000130BAFEDB04.

The definition in taskschd.h shows IExecAction is a derived class from IAction.

This works well:

if (IExecAction* pExecAction = ((IExecAction*)pAction)) { /*my work...*/ }

But what if I want to do some type checking? How could I use it properly?

1

There are 1 best solutions below

0
On BEST ANSWER

for get pointer of com interface from another com interface on the same object we need use only QueryInterface method, which is always implemented by any interface. so code in your case need to be next:

    IAction* pAction;
    IExecAction* pExecAction;
    IEmailAction* pEmailAction;

    HRESULT hr;

    if (SUCCEEDED(hr = pAction->QueryInterface(IID_PPV_ARGS(&pExecAction))))
    {
        // use pExecAction
        pExecAction->Release();
    }

    if (SUCCEEDED(hr = pAction->QueryInterface(IID_PPV_ARGS(&pEmailAction))))
    {
        // use pExecAction
        pEmailAction->Release();
    }

even if one interface inherit from another use c/c++ cast is always wrong. for example

    pExecAction = static_cast<IExecAction*>(pAction);
    pEmailAction = static_cast<IEmailAction*>(pAction);

this code is correct from c++ syntax, because both IExecAction : IAction and IEmailAction : IAction inherit from IAction. and this cast (if take to account layout of this 3 interfaces) give you equal binary values for pExecAction and pEmailAction. but pExecAction can not have the same binary value as pEmailAction. must be

assert((void*)pEmailAction != (void*)pExecAction);

why ? because have pEmailAction and pExecAction have different virtual functions at the same position in vtable. for example on the 10-th position in the table of IExecAction must be pointer to get_Path method. from another side on the 10-th position in the table of IEmailAction must be pointer to get_Server method. if (void*)pEmailAction == (void*)pExecAction - they will be have the same pointers to vtable. but pointer to which function - get_Path or get_Server will be in the 10-th position ? as result pointer to this 2 interfaces can not be the same (point to the same memory). so how minimum one static_cast here (may be and both) give wrong result. for understand how QueryInterface work and why pointers to pExecAction and pEmailAction wiil be different - we need look for implementation. implementation of interfaces - this is some class, which inherit (usually) from all this interfaces and implement it like this:

class CAction : IExecAction, IEmailAction
{
    virtual ULONG STDMETHODCALLTYPE AddRef( );

    virtual HRESULT STDMETHODCALLTYPE QueryInterface( REFIID riid, void **ppvObject)
    {
        PVOID pvObject;

        if (riid == __uuidof(IAction))
        {
            pvObject = static_cast<IExecAction*>(this);
            // or can be
            pvObject = static_cast<IEmailAction*>(this);
        }
        else if (riid == __uuidof(IExecAction))
        {
            pvObject = static_cast<IExecAction*>(this);
        }
        else if (riid == __uuidof(IEmailAction))
        {
            pvObject = static_cast<IExecAction*>(this);
        }
        else
        {
            *ppvObject = 0;
            return E_NOINTERFACE;
        }

        *ppvObject = pvObject;
        AddRef();
        return S_OK;
    }
};

look that static_cast<IExecAction*>(this); will always give another binary value compare static_cast<IEmailAction*>(this); - the CAction will be containing 2 different vtables - one for IExecAction and one for IEmailAction. they have common initial part (9 entries) but then different. and static_cast<IExecAction*>(this); and static_cast<IEmailAction*>(this); return 2 different (always) pointers to this 2 different vtables. when for IAction* we select return or first or second vtable pointer. both will be correct. what pointer return implementation - we can not know (the layout of actual class which implement IExecAction, IEmailAction is unknown for us)