【问题标题】:ATL COM events for javascriptjavascript 的 ATL COM 事件
【发布时间】:2012-02-28 12:21:23
【问题描述】:

我前段时间创建了一个 ATL COM 服务器组件 (exe)。它公开了一些普通的 COM API(从 IDispatch 派生)并触发了一些 COM 事件。事件机制是使用 ATL IConnectionPointContainer 实现的。这个 COM 服务器最初是由一个简单的 C# 应用程序使用的,它直接添加了对 COM 服务器的引用。一切,API 和事件,都可以在 C# 应用程序中正常工作。

然后是 COM 服务器能够在网页 (IE) 中与 javascript 一起使用的要求。因此,我在原始 COM 类中添加了 IProvideClassInfo2、IObjectSafety 实现。但是,COM 事件从未起作用。请参考下面的IDL、COM类头文件和事件触发代码。

IDL:

import "oaidl.idl";
import "ocidl.idl";

[
    object,
    // uuid replaced with dummy
    uuid(00000000-0000-0000-0000-000000000000), 
    dual,
    nonextensible,
    helpstring("ICtrl Interface"),
    pointer_default(unique)
]
interface ICtrl : IDispatch{
    [id(1), helpstring("method CtrlMethod1")] 
    HRESULT CtrlMethod1(void);
    [id(2), helpstring("method CtrlMethod2")] 
    HRESULT CtrlMethod2([in] ULONG Reason);
};


[
    // uuid replaced with dummy
    uuid(00000000-0000-0000-0000-000000000001), 
    version(1.0),
]
library MyControlLib
{
    importlib("stdole32.tlb");
    importlib("stdole2.tlb");
    [
        // uuid replaced with dummy 
        uuid(00000000-0000-0000-0000-000000000002)   
    ]
    dispinterface _ICtrlEvents
    {
        properties:
        methods:
            [id(1), helpstring("method OnCtrlEvent1")] 
            HRESULT OnCtrlEvent1([in] LONG ErrorCode);
            [id(2), helpstring("method OnCtrlEvent2")] 
            HRESULT OnCtrlEvent2([in] LONG ErrorCode);
    };


    [
        // uuid replaced with dummy
        uuid(00000000-0000-0000-0000-000000000003)       
    ]
    coclass Ctrl
    {
        [default] interface ICtrl;
        [default, source] dispinterface _ICtrlEvents;
    };
};

COM 类头:

// CCtrl

class ATL_NO_VTABLE CCtrl :
    public CComObjectRootEx<CComSingleThreadModel>,
    public CComCoClass<CCtrl, &CLSID_Ctrl>,
    public IConnectionPointContainerImpl<CCtrl>,
    public CProxy_ICtrlEvents<CCtrl>,
    public IDispatchImpl<ICtrl, &IID_ICtrl, &LIBID_MyControlLib, /*wMajor =*/ 1, /*wMinor =*/ 0>,
    public IDispatchImpl<_ICtrlEvents, &__uuidof(_ICtrlEvents), &LIBID_MyControlLib, /* wMajor = */ 1, /* wMinor = */ 0>,
    public IObjectSafetyImpl<CCtrl, INTERFACESAFE_FOR_UNTRUSTED_CALLER>,
    public IProvideClassInfo2Impl<&CLSID_Ctrl, NULL, &LIBID_MyControlLib>
{
public:
    DECLARE_CLASSFACTORY_SINGLETON(CCtrl)
    CCtrl();


    DECLARE_REGISTRY_RESOURCEID(IDR_CTRL)


    BEGIN_COM_MAP(CCtrl)
        COM_INTERFACE_ENTRY(ICtrl)
        COM_INTERFACE_ENTRY2(IDispatch, ICtrl)
        COM_INTERFACE_ENTRY2(IDispatch, _ICtrlEvents)
        COM_INTERFACE_ENTRY(IConnectionPointContainer)
        COM_INTERFACE_ENTRY(_ICtrlEvents)
        COM_INTERFACE_ENTRY(IObjectSafety)
        COM_INTERFACE_ENTRY(IProvideClassInfo)
        COM_INTERFACE_ENTRY(IProvideClassInfo2)
    END_COM_MAP()

    BEGIN_CONNECTION_POINT_MAP(CCtrl)
        CONNECTION_POINT_ENTRY(__uuidof(_ICtrlEvents))
    END_CONNECTION_POINT_MAP()


    DECLARE_PROTECT_FINAL_CONSTRUCT()

    HRESULT FinalConstruct();
    void FinalRelease();

public:

    STDMETHOD(CtrlMethod1)(void);
    STDMETHOD(CtrlMethod2)(ULONG Reason);

};

OBJECT_ENTRY_AUTO(__uuidof(Ctrl), CCtrl)

ATL 生成的事件触发代码:

#pragma once

template<class T>
class CProxy_ICtrlEvents :
    public ATL::IConnectionPointImpl<T, &__uuidof(_ICtrlEvents)>
{
public:

    HRESULT OnCtrlEvent1(LONG ErrorCode)
    {
        HRESULT hr = S_OK;
        T * pThis = static_cast<T *>(this);
        int cConnections = m_vec.GetSize();

        for (int iConnection = 0; iConnection < cConnections; iConnection++)
        {
            pThis->Lock();
            CComPtr<IUnknown> punkConnection = m_vec.GetAt(iConnection);
            pThis->Unlock();

            IDispatch * pConnection = static_cast<IDispatch *>(punkConnection.p);

            if (pConnection)
            {
                CComVariant avarParams[1];
                avarParams[0] = ErrorCode;
                avarParams[0].vt = VT_I4;
                CComVariant varResult;

                DISPPARAMS params = { avarParams, NULL, 1, 0 };
                hr = pConnection->Invoke(1, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &params, &varResult, NULL, NULL);
            }
        }
        return hr;
    }
    HRESULT Fire_OnCtrlEvent2(LONG ErrorCode)
    {
        HRESULT hr = S_OK;
        T * pThis = static_cast<T *>(this);
        int cConnections = m_vec.GetSize();

        for (int iConnection = 0; iConnection < cConnections; iConnection++)
        {
            pThis->Lock();
            CComPtr<IUnknown> punkConnection = m_vec.GetAt(iConnection);
            pThis->Unlock();

            IDispatch * pConnection = static_cast<IDispatch *>(punkConnection.p);

            if (pConnection)
            {
                CComVariant avarParams[1];
                avarParams[0] = ErrorCode;
                avarParams[0].vt = VT_I4;
                CComVariant varResult;

                DISPPARAMS params = { avarParams, NULL, 1, 0 };
                hr = pConnection->Invoke(2, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &params, &varResult, NULL, NULL);
            }
        }
        return hr;
    }
};

在 javascript 代码中,COM 对象是使用

创建的
var CtrlObj = new ActiveXObject('ProgID_of_Ctrl')

'ProgID_of_Ctrl' 映射到 __uuidof(Ctrl)。在 IE 调试器中,创建的对象是 ICtrl 类型的。 COM API 可见,但 COM 事件不可见。任何尝试使用 CtrlObj.attachEvent() 都会导致 javascript 错误。我希望 CtrlObj 应该是 coclass (Ctrl) 类型,就像 C# app 的情况一样。 COM_MAP 部分是否有错误?感谢任何 cmets 和帮助。

-码农

【问题讨论】:

    标签: javascript events com atl


    【解决方案1】:

    根据我的阅读,您应该使用OBJECT 标记和SCRIPT for 标记来连接HTML 中的ATL/COM 事件。像这样的:

    <object
      id="myCtrlObj"
      classid="CLSID:00000000-0000-0000-0000-000000000003"
      height="32"
      width="32"/>
    
    <script language="javascript"
       id="myCtrlHandler1"
       event="OnCtrlEvent1()"
       for="myCtrlObj">
       alert("OnCtrlEvent1 fired");
    </script>
    
    <script language="javascript"
       id="myCtrlHandler2"
       event="OnCtrlEvent2(reason)"
       for="myCtrlObj">
        alert("OnCtrlEvent2 fired with parameter: " + reason.toString());
    </script>
    

    因为您使用的是 JScript,所以我有时喜欢欺骗并让 IDispatch VARIANT 属性模拟事件的行为。在以下 JScript 代码中,请注意如何将 OnCtrlEvent1 和 OnCtrlEvent2 分配给函数:

    function tst()
    {
      var ctrl = new ActiveXObject("MyControl.Ctrl");
      ctrl.OnCtrlEvent1 = myevent1;
      ctrl.OnCtrlEvent2 = myevent2;
      ctrl.CtrlMethod1();
      ctrl.CtrlMethod2();
    }
    
    function myevent1()
    {
      alert("Event1");
    }
    
    function myevent2(reason)
    {
      alert("Event2 " + reason.toString());
    }
    

    这个诡计是通过将其作为 IDL 中的一个属性来处理的。这些 JScript 函数作为包含可调用 IDispatch 接口的 VARIANT 传递给我们。这是我的 MyControl.idl:

    import "oaidl.idl";
    import "ocidl.idl";
    
    [
        object,
        // uuid replaced with dummy 
        uuid(00000000-0000-0000-0000-000000000000)  
        dual,
        nonextensible,
        helpstring("ICtrl Interface"),
        pointer_default(unique)
    ]
    interface ICtrl : IDispatch{
        [id(1), helpstring("method CtrlMethod1")] HRESULT CtrlMethod1(void);
        [id(2), helpstring("method CtrlMethod2")] HRESULT CtrlMethod2(void);
        [propget, id(3), helpstring("property OnCtrlEvent1")] HRESULT OnCtrlEvent1([out, retval] VARIANT* pVal);
        [propput, id(3), helpstring("property OnCtrlEvent1")] HRESULT OnCtrlEvent1([in] VARIANT newVal);
        [propget, id(4), helpstring("property OnCtrlEvent2")] HRESULT OnCtrlEvent2([out, retval] VARIANT* pVal);
        [propput, id(4), helpstring("property OnCtrlEvent2")] HRESULT OnCtrlEvent2([in] VARIANT newVal);
    };
    [
        // uuid replaced with dummy 
        uuid(00000000-0000-0000-0000-000000000001),
        version(1.0),
        helpstring("MyControl 1.0 Type Library")
    ]
    library MyControlLib
    {
        importlib("stdole2.tlb");
        [
            // uuid replaced with dummy 
            uuid(00000000-0000-0000-0000-000000000003)
            helpstring("Ctrl Class")
        ]
        coclass Ctrl
        {
            [default] interface ICtrl;
        };
    };
    

    这是我的 Ctrl.h,您可以在其中看到 JScript 函数将保存在 VARIANT 成员中:

    class ATL_NO_VTABLE CCtrl :
        public CComObjectRootEx<CComSingleThreadModel>,
        public CComCoClass<CCtrl, &CLSID_Ctrl>,
        public IDispatchImpl<ICtrl, &IID_ICtrl, &LIBID_MyControlLib, /*wMajor =*/ 1, /*wMinor =*/ 0>
    {
    public:
    DECLARE_REGISTRY_RESOURCEID(IDR_CTRL)
    
    BEGIN_COM_MAP(CCtrl)
        COM_INTERFACE_ENTRY(ICtrl)
        COM_INTERFACE_ENTRY(IDispatch)
    END_COM_MAP()
    
    DECLARE_PROTECT_FINAL_CONSTRUCT()
    
        HRESULT FinalConstruct()
        {
            return S_OK;
        }
    
        void FinalRelease()
        {
        }
    
    public:
        STDMETHOD(CtrlMethod1)(void);
        STDMETHOD(CtrlMethod2)(void);
        STDMETHOD(get_OnCtrlEvent1)(VARIANT* pVal);
        STDMETHOD(put_OnCtrlEvent1)(VARIANT newVal);
        STDMETHOD(get_OnCtrlEvent2)(VARIANT* pVal);
        STDMETHOD(put_OnCtrlEvent2)(VARIANT newVal);
    
    private:
        CComVariant m_ctrlEvent1;
        CComVariant m_ctrlEvent2;
    
        STDMETHOD(Invoke_CtrlEvent1)();
        STDMETHOD(Invoke_CtrlEvent2)(LONG nReason);
    };
    
    OBJECT_ENTRY_AUTO(__uuidof(Ctrl), CCtrl)
    

    在 Ctrl.cpp 中,诡计是我们在这些 VARIANT 中查找 JScript 函数的 IDispatch 接口,并根据我们对参数的“知识”,使用正确的参数调用每个事件:

    #include "stdafx.h"
    #include "Ctrl.h"
    
    STDMETHODIMP CCtrl::CtrlMethod1(void)
    {
        Invoke_CtrlEvent1();
        return S_OK;
    }
    
    STDMETHODIMP CCtrl::CtrlMethod2(void)
    {
        Invoke_CtrlEvent2(12345);
        return S_OK;
    }
    
    STDMETHODIMP CCtrl::get_OnCtrlEvent1(VARIANT* pVal)
    {
        VariantInit(pVal);
        return VariantCopy(pVal, &m_ctrlEvent1);
        return S_OK;
    }
    
    STDMETHODIMP CCtrl::put_OnCtrlEvent1(VARIANT newVal)
    {
        m_ctrlEvent1 = newVal;
        return S_OK;
    }
    
    STDMETHODIMP CCtrl::get_OnCtrlEvent2(VARIANT* pVal)
    {
        VariantInit(pVal);
        return VariantCopy(pVal, &m_ctrlEvent2);
    }
    
    STDMETHODIMP CCtrl::put_OnCtrlEvent2(VARIANT newVal)
    {
        m_ctrlEvent2 = newVal;
        return S_OK;
    }
    
    STDMETHODIMP CCtrl::Invoke_CtrlEvent1()
    {
        if (m_ctrlEvent1.vt != VT_DISPATCH)
        {
            return S_OK;
        }
        DISPPARAMS DispParams = { 0, 0, 0, 0 };
        VARIANT Var = { 0 };
        return V_DISPATCH(&m_ctrlEvent1)->Invoke((DISPID) 0, IID_NULL, 0, DISPATCH_METHOD, &DispParams, &Var, NULL, NULL);
    }
    
    STDMETHODIMP CCtrl::Invoke_CtrlEvent2(LONG nReason)
    {
        if (m_ctrlEvent1.vt != VT_DISPATCH)
        {
            return S_OK;
        }
        VARIANTARG    Arg = {0};
        Arg.vt = VT_I4;
        Arg.lVal = nReason;
        DISPPARAMS DispParams = { &Arg, 0, 1, 0 };
        VARIANT Var = { 0 };
        return V_DISPATCH(&m_ctrlEvent2)->Invoke((DISPID) 0, IID_NULL, 0, DISPATCH_METHOD, &DispParams, &Var, NULL, NULL);
    }
    

    【讨论】:

    • 谢谢。您的解决方案有效。实际上,我不认为这是作弊。主界面没有单独的事件接口,而是为 javascript 提供了一种注册回调的方法。
    猜你喜欢
    • 2018-01-05
    • 2012-07-27
    • 1970-01-01
    • 2010-12-16
    • 2013-06-21
    • 2011-04-04
    • 2011-04-28
    • 2011-06-28
    • 2011-08-05
    相关资源
    最近更新 更多