首页 > 解决方案 > C++ 铸造 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节目中的定义IExecAction是一个派生类IAction

这很好用:

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

但是如果我想做一些类型检查呢?我怎样才能正确使用它?

标签: c++windowsvisual-studiocastingtaskscheduler

解决方案


要从同一对象上的另一个 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 : IAction继承IEmailAction : IActionIAction. 并且这个演员表(如果考虑到这 3 个接口的布局)为您提供了相等的二进制值pExecActionpEmailAction。但pExecAction 不能具有与 相同的二进制值pEmailAction。一定是

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

为什么 ?因为在vtablepEmailAction的同一位置具有和具有不同的虚函数。例如,表中的第 10 位必须是指向方法的指针。从表中第 10 位的另一侧必须是指向方法的指针。if - 它们将具有相同的指向 vtable 的指针。但是指向哪个函数的指针 -或者将在第 10 个位置?结果指向这两个接口的指针不能相同(指向相同的内存)。那么这里的最小值(可能是两者)如何给出错误的结果。了解如何工作以及为什么指向和pExecActionIExecActionget_PathIEmailActionget_Server(void*)pEmailAction == (void*)pExecActionget_Pathget_Serverstatic_castQueryInterfacepExecActionpEmailAction将有所不同 - 我们需要寻找实施。接口的实现 - 这是一些类,它(通常)从所有这些接口继承并像这样实现它:

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 - 一个 forIExecAction和一个 for IEmailAction。它们有共同的初始部分(9 个条目),但随后又有所不同。并返回 2static_cast<IExecAction*>(this);static_cast<IEmailAction*>(this);不同的(总是)指向这 2 个不同 vtable 的指针。当IAction*我们选择返回或第一个或第二个 vtable 指针时。两者都是正确的。什么指针返回实现 - 我们不知道(实现的实际类的布局对我们IExecAction来说IEmailAction是未知的)


推荐阅读