2012-02-07 50 views
0

我前段时间创建了一个ATL COM服务器组件(exe)。它暴露了一些正常的COM API(派生自IDispatch),并且还触发了一些COM事件。事件机制是使用ATL IConnectionPointContainer实现的。这个COM服务器最初由一个简单的C#应用​​程序使用,该应用程序直接向COM服务器添加一个引用。所有的API和事件都可以在C#应用程序中正常工作。javascript的ATL COM事件

然后来到COM服务器的要求能够在网页(IE)中与JavaScript一起使用。因此,我将IProvideClassInfo2,IObjectSafety实现添加到原始COM类。但是,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#应用程序的情况一样。 COM_MAP部分中是否有错误?任何意见和帮助表示赞赏。

-CodeFarmer

回答

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挂羊头卖狗肉是我们要寻找的JScript的功能的IDispatch接口在这些VARIANTs和我们的“知识”的参数,我们援引每个事件与正确的参数:

#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); 
} 
+0

谢谢。你的解决方案有效我并不认为它是一个骗子。主界面不是提供单独的事件界面,而是为JavaScript提供了一种注册回调的方法。 – CodeFarmer 2012-02-07 05:06:20