ATL COM события для JavaScript - PullRequest
       6

ATL COM события для JavaScript

0 голосов
/ 07 февраля 2012

Я создал компонент сервера ATL COM (exe) некоторое время назад. Он показал несколько обычных COM-API (полученных из IDispatch), а также запустил несколько COM-событий. Механизм событий был реализован с использованием ATL IConnectionPointContainer. Этот COM-сервер изначально использовался простым приложением C #, которое напрямую добавляло ссылку на COM-сервер. Все, API и события, прекрасно работает в приложении C #.

Затем появилось требование, чтобы COM-сервер мог использовать javascript на веб-странице (IE). Поэтому я добавил 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. API-интерфейсы COM видны, а события COM - нет. Любая попытка использовать CtrlObj.attachEvent () приведет к ошибке JavaScript. Я ожидаю, что CtrlObj должен быть коклассного (Ctrl) типа, как это было в случае приложения C #. Есть ли какие-либо ошибки в разделе COM_MAP? Любые комментарии и помощь приветствуются.

-CodeFarmer

1 Ответ

1 голос
/ 07 февраля 2012

Из того, что я прочитал, вы должны использовать тег OBJECT и тег SCRIPT for для подключения событий ATL / COM в HTML. Примерно так:

<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 передаются нам как VARIANT, содержащие вызываемые интерфейсы IDispatch. Вот мой 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 хитрость заключается в том, что мы ищем интерфейсы IDispatch функции JScript в этих VARIANT и с нашим «знанием» параметров мы вызываем каждое событие с правильными параметрами:

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