C++ 转换 Windows IAction

C++ casting Windows IAction

我正在努力从 windows 任务计划程序中检索一些信息。 MSDN表示有几种类型的动作。我想分别处理它们。我试过了:

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...*/ }

但是这个程序在第一个 dynamic_cast 处抛出异常。

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

taskschd.h 中的定义表明 IExecActionIAction 的派生 class。

效果很好:

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

但是如果我想做一些类型检查怎么办? 我该如何正确使用它?

为了从同一对象上的另一个 com 接口获取 com 接口的指针,我们只需要使用 QueryInterface 方法,该方法始终由任何接口实现。所以你的代码需要在下一个:

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

即使一个接口继承自另一个接口,使用 c/c++ 转换总是错误的。例如

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

此代码从 c++ 语法来看是正确的,因为 IExecAction : IActionIEmailAction : IAction 都继承自 IAction。并且此转换(如果考虑到这 3 个接口的布局)为 pExecActionpEmailAction 提供相等的二进制值。但是 pExecAction 不能有与 pEmailAction 相同的二进制值。必须

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

为什么?因为 have pEmailActionpExecActionvtable 的同一位置有不同的虚函数。例如在 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 的 table 中的第 10 个位置将在第 10 个位置?结果指向这 2 个接口的指针不能相同(指向同一内存)。那么这里的最小值 static_cast(可能是,两者都是)如何给出错误的结果。为了理解 QueryInterface 是如何工作的以及为什么指向 pExecActionpEmailAction 的指针会不同 - 我们需要寻找实现。接口的实现 - 这是一些 class,它(通常)继承所有这些接口并像这样实现它:

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

看起来 static_cast<IExecAction*>(this); 总是会给出另一个二进制值比较 static_cast<IEmailAction*>(this); - CAction 将包含 2 个不同的 vtables - 一个用于 IExecAction 和一个 IEmailAction。他们有共同的初始部分(9 个条目)但随后不同。 static_cast<IExecAction*>(this);static_cast<IEmailAction*>(this); return 2 个不同的(总是)指向这 2 个不同的 vtable 的指针。当 IAction* 我们 select return 或第一个或第二个 vtable 指针。两者都是正确的。什么指针 return 实现 - 我们不知道(实现 IExecActionIEmailAction 的实际 class 的布局对我们来说是未知的)