简体   繁体   中英

ATL COM events for javascript

I created an ATL COM server component (exe) some time ago. It exposed a few normal COM APIs (derived from IDispatch) and also fired a few COM events. The event mechanism was implemented using ATL IConnectionPointContainer. This COM server was initially used by a simple C# application, which directly added a reference to the COM server. Everything, the APIs and events, works fine in C# app.

Then came the requirement for the COM server to able to used with javascript in a webpage (IE). I therefore added IProvideClassInfo2, IObjectSafety implementation to the original COM class. However, the COM event never worked. Please refer to the IDL, COM class header file and event firing code below.

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 class header:

// 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-generated event firing code:

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

In the javascript code, the COM object is created using

var CtrlObj = new ActiveXObject('ProgID_of_Ctrl')

'ProgID_of_Ctrl' is mapped to __uuidof(Ctrl). In the IE debugger, the object created is of the type ICtrl. The COM APIs are visible, but the COM events are not. Any attempt to use CtrlObj.attachEvent() will result in javascript error. I'd expect CtrlObj should be of coclass (Ctrl) type, as was in the case of C# app. Whether there are any errors in COM_MAP section? Any comments and help are appreciated.

-CodeFarmer

From what I've read, you should be using the OBJECT tag and the SCRIPT for tag to hook up ATL/COM events in HTML. Something like this:

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

Because you're using JScript, I sometimes like to cheat and make an IDispatch VARIANT property simulate the behavior of an event. In the following JScript code snippet note how OnCtrlEvent1 and OnCtrlEvent2 are being assigned to functions:

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

The trickery is done by handling it as a property in the IDL. Where those JScript functions are being passed to us as VARIANTs containing invokable IDispatch interfaces. Here's my 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;
    };
};

Here's my Ctrl.h where you see the JScript functions will be saved in VARIANT members:

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)

In Ctrl.cpp the trickery is we look for the JScript function's IDispatch interfaces in those VARIANTs and with our 'knowledge' of the parameters, we invoke each event with the right parameters:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM