【问题标题】:Why does ITypeInfo::Invoke return 0x80020003 (Member not found.)?为什么 ITypeInfo::Invoke 返回 0x80020003(未找到成员。)?
【发布时间】:2014-08-02 01:05:26
【问题描述】:

我在 Microsoft Visual C++ 中为 DShellWindowsEvents from SHDocVw.dll 实现事件接收器时遇到问题。

问题出现在我的 IDispatch::Invoke 实现中。我通过将其调用委托给 ITypeInfo::Invoke(作为 suggested by Microsoft on remarks section)来实现它,但它总是失败,错误代码为 0x80020003(未找到成员)。

有趣的是,DShellWindowsEvents 只有两个方法(WindowRegistered 和 WindowRevoked),而 ITypeInfo::GetIDsOfNames 对这两个方法都有效,返回预期的 DISPID。但是它如何为 Invoke 提供“未找到成员”错误?

类型库的相关部分:

[
  uuid(FE4106E0-399A-11D0-A48C-00A0C90A8F39),
  helpstring("Event interface for IShellWindows")
]
dispinterface DShellWindowsEvents {
    properties:
    methods:
        [id(0x000000c8), helpstring("A new window was registered.")]
        void WindowRegistered([in] long lCookie);
        [id(0x000000c9), helpstring("A new window was revoked.")]
        void WindowRevoked([in] long lCookie);
};

[
  uuid(9BA05972-F6A8-11CF-A442-00A0C90A8F39),
  helpstring("ShellDispatch Load in Shell Context")
]
coclass ShellWindows {
    [default] interface IShellWindows;
    [default, source] dispinterface DShellWindowsEvents;
};

以及发生这一切的实际代码:

class CDShellWindowsEvents : public DShellWindowsEvents {

public:
    // IUnknown implementation
    virtual HRESULT __stdcall QueryInterface( REFIID riid, LPVOID *ppvObj )
    {
        if( ppvObj == NULL )  return E_INVALIDARG;
        *ppvObj = NULL;
        if(  riid == IID_IUnknown  ||  riid == IID_IDispatch  ||  riid == DIID_DShellWindowsEvents  )
        {
            *ppvObj = this;
            AddRef( );
            return NOERROR;
        }
        return E_NOINTERFACE;
    }

    virtual ULONG __stdcall AddRef( )
    {
        InterlockedIncrement( &m_cRef );
        return m_cRef;
    }

    virtual ULONG __stdcall Release( )
    {
        ULONG ulRefCount = InterlockedDecrement( &m_cRef );
        if( 0 == m_cRef )
            delete this;
        return ulRefCount;
    }


    // IDispatch implementation
    virtual HRESULT __stdcall GetTypeInfoCount( unsigned int * pctinfo )
    {
        if( pctinfo == NULL )  return E_INVALIDARG;
        *pctinfo = 1;
        return NOERROR;
    }

    virtual HRESULT __stdcall GetTypeInfo( unsigned int iTInfo, LCID lcid, ITypeInfo ** ppTInfo )
    {
        if( ppTInfo == NULL )  return E_INVALIDARG;
        *ppTInfo = NULL;

        if( iTInfo != 0 )  return DISP_E_BADINDEX;

        *ppTInfo = m_pTypeInfo;
        m_pTypeInfo->AddRef();
        return NOERROR;
    }

    virtual HRESULT __stdcall GetIDsOfNames(REFIID riid, OLECHAR ** rgszNames, unsigned int cNames, LCID lcid, DISPID * rgDispId )
    {
        return  m_pTypeInfo->GetIDsOfNames( rgszNames, cNames, rgDispId );
    }

    virtual HRESULT __stdcall Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS * pDispParams, VARIANT * pVarResult, EXCEPINFO * pExcepInfo, unsigned int * puArgErr )
    {
        // We could switch on dispIdMember here but we want to do this the flexible way, so we can easily implement it later for dispinterfaces with dozens of methods.

        return  m_pTypeInfo->Invoke( this, dispIdMember, wFlags, pDispParams, pVarResult, pExcepInfo, puArgErr );

        /*HRESULT ret;
        switch( dispIdMember )
        {
        case 0x000000c8:    ret = WindowRegistered( pDispParams->rgvarg[0].intVal );  break;
        case 0x000000c9:    ret = WindowRevoked( pDispParams->rgvarg[0].intVal );  break;
        default:            ret = DISP_E_MEMBERNOTFOUND;
        }
        if( pVarResult )  pVarResult->lVal = ret;
        return ret;*/
    }


    // DShellWindowsEvents implementation
    virtual HRESULT __stdcall WindowRegistered( int nCookie )
    {
        LOG( "CDShellWindowsEvents::WindowRegistered( nCookie=0x%X ) ." , nCookie );
        return NOERROR;
    }

    virtual HRESULT __stdcall WindowRevoked( int nCookie )
    {
        LOG( "CDShellWindowsEvents::WindowRevoked( nCookie=0x%X ) ." , nCookie );
        return NOERROR;
    }


    // Constructor
    CDShellWindowsEvents( )
    {
        m_cRef = 1;

        LoadRegTypeLib( LIBID_SHDocVw, 1, 0, LANG_NEUTRAL, &m_pTypeLib );
        m_pTypeLib->GetTypeInfoOfGuid( DIID_DShellWindowsEvents, &m_pTypeInfo );
    }


    // Destructor
    ~CDShellWindowsEvents( )
    {
        m_pTypeInfo->Release( );
        m_pTypeLib->Release( );
    }


private:
    ULONG m_cRef;
    ITypeLib *m_pTypeLib;
    ITypeInfo *m_pTypeInfo;
};

这是类型库没有为 dispinterface 指定 dual 的问题吗?如果是,有没有办法强制 ITypeInfo::Invoke 以双重身份威胁它,因为我知道它的 vtable 是有序的。

提前致谢。

【问题讨论】:

    标签: c++ events com activex ole


    【解决方案1】:

    A dispinterface is an automation-only interface, not a dual interface. 所以,DShellWindowsEvents 本身只有IDispatch 的7 个方法,而不是它声明的额外的2 个。这就像用合约声明一个 IDispatch-only 接口,例如预定义的 DISPID。

    要以这种方式使用ITypeLib::Invoke,您需要使用额外的方法将自己的接口声明为[dual]

    [ object, dual, oleautomation, uuid(...) ]
    interface IMyDualShellWindowsEvents : IDispatch
    {
        [id(0x000000c8), helpstring("A new window was registered.")]
        HRESULT WindowRegistered([in] long lCookie);
        [id(0x000000c9), helpstring("A new window was revoked.")]
        HRESULT WindowRevoked([in] long lCookie);
    }
    

    您需要提供或嵌入您自己的类型库并加载它。

    DShellWindowsEvents 是一个调度接口,所以你的事件对象不会是你的内部接口的QueryInterfaced,尽管你可以处理这种情况。因此,您不需要注册您的类型库,只需使用 LoadLibraryEx 加载它即可。

    我认为您可以将hidden, restricted 添加到界面属性中,因此它不会显示并且不能用于例如VBA,甚至通过手动添加对此类 private 类型库的引用。

    【讨论】:

    • 谢谢保罗,我现在明白了。你拯救了我的一天。
    猜你喜欢
    • 2021-02-25
    • 2021-03-14
    • 1970-01-01
    • 1970-01-01
    • 2015-01-07
    • 1970-01-01
    • 1970-01-01
    • 2016-05-06
    • 2012-01-06
    相关资源
    最近更新 更多