将COM事件暴露给VBScript(ATL)

Hel*_*ein 4 c++ com vbscript events atl

我使用"ATL简单对象"向导在C++中使用ATL构建了一个COM服务器DLL.我关注了微软的ATLDLLCOMServer示例.一切都很好,除了一件事:我没有在VBScript中收到COM事件.我确实收到了C#中的事件.我在VBScript中使用早期基于MFC的实现中的事件作为ActiveX控件.

我的控件定义如下:

class ATL_NO_VTABLE CSetACLCOMServer :
    public CComObjectRootEx<CComSingleThreadModel>,
    public CComCoClass<CSetACLCOMServer, &CLSID_SetACLCOMServer>,
    public IConnectionPointContainerImpl<CSetACLCOMServer>,
    public CProxy_ISetACLCOMServerEvents<CSetACLCOMServer>,
    public IDispatchImpl<ISetACLCOMServer, &IID_ISetACLCOMServer, &LIBID_SetACLCOMLibrary, /*wMajor =*/ 1, /*wMinor =*/ 0>,
    public IProvideClassInfo2Impl<&CLSID_SetACLCOMServer, &DIID__ISetACLCOMServerEvents, &LIBID_SetACLCOMLibrary>   /* Required for event support in VBS */
{
public:

BEGIN_COM_MAP(CSetACLCOMServer)
    COM_INTERFACE_ENTRY(ISetACLCOMServer)
    COM_INTERFACE_ENTRY(IDispatch)
    COM_INTERFACE_ENTRY(IConnectionPointContainer)
   COM_INTERFACE_ENTRY(IProvideClassInfo)
   COM_INTERFACE_ENTRY(IProvideClassInfo2)
END_COM_MAP()

BEGIN_CONNECTION_POINT_MAP(CSetACLCOMServer)
    CONNECTION_POINT_ENTRY(__uuidof(_ISetACLCOMServerEvents))
END_CONNECTION_POINT_MAP()

[...]
Run Code Online (Sandbox Code Playgroud)

在VBScript中我使用COM对象,如下所示:

set objSetACL = WScript.CreateObject("SetACL.SetACL", "SetACL_")

' Catch and print messages from the SetACL COM server which are passed (fired) as events.
' The name postfix of this function (MessageEvent) must be identical to the event name 
' as defined by SetACL.
' The prefix (SetACL_) can be set freely in the call to WScript.CreateObject
sub SetACL_MessageEvent (Message)
    WScript.Echo Message
end sub
Run Code Online (Sandbox Code Playgroud)

IDL的相关部分如下所示:

[
    object,
    uuid(E1B57CA5-FD7F-4304-BC0A-8BEBDE231D53),
    dual,
    nonextensible,
    pointer_default(unique),
   helpstring("SetACL COM Interface")
]
interface ISetACLCOMServer : IDispatch
{
};

[
    uuid(00D4DCD3-02B9-4A71-AB61-2283504620C8),
    version(1.0),
   helpstring ("SetACL Type Library")
]
library SetACLCOMLibrary
{
    importlib("stdole2.tlb");

   [
        uuid(35F76182-7F52-4D6A-BD6E-1317345F98FB),
      helpstring ("SetACL Event Interface")
    ]
    dispinterface _ISetACLCOMServerEvents
    {
        properties:
        methods:
         [id(1), helpstring("Receives string messages that would appear on the screen in the command line version")]
         void MessageEvent([in] BSTR message);
   };

   [
        uuid(13379563-8F21-4579-8AC7-CBCD488735DB),
      helpstring ("SetACL COM Server"),
    ]
    coclass SetACLCOMServer
    {
        [default] interface ISetACLCOMServer;
        [default, source] dispinterface _ISetACLCOMServerEvents;
    };
};
Run Code Online (Sandbox Code Playgroud)

问题:SetACL_MessageEvent不会被调用.

我尝试过的:

  • 阅读这篇知识库文章后,我添加了IProvideClassInfo2的实现,但这没有用.
  • FAQ提到输出接口不应定义为双接口.我从界面定义中删除了"双重",但这没有帮助.
  • 我发现这个SO问题似乎描述了类似的问题.答案从未给出,只有一个解决方法.

更新1:

我现在知道它失败的原因,但不是原因:在_ISetACLCOMServerEvents_CP.h中的fire事件方法中,对Invoke的调用因E_UNEXPECTED而失败.这是一行:

hr = pConnection->Invoke(1, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &params, NULL, NULL, NULL);
Run Code Online (Sandbox Code Playgroud)

解:

问题是我从COM对象中的后台线程调用事件触发函数,换句话说,从错误的单元调用.

Rom*_* R. 5

实施IProvideClassInfo2应该足够了(虽然我的印象是即使没有它也应该有效).我从模板中创建了一个简约的ATL项目作为样本.这是ATL DLL + COM Class +一个方法和一个事件+ IProvideClassInfo2:

源代码[ SVN,浏览器友好 ] + Win32二进制文件.

测试.vbs如下:

Function Test_Event(Text)
  WScript.Echo "Event: " + Text
End Function

Dim Test
Set Test = WScript.CreateObject("AlaxInfo.VbsEvents.Foo", "Test_")
Test.Method("Event Fun")
Run Code Online (Sandbox Code Playgroud)

方法本身是:

// IFoo
    STDMETHOD(Method)(BSTR sText) throw()
    {
        ATLTRACE(atlTraceCOM, 4, _T("sText \"%s\"\n"), CString(sText));
        ATLVERIFY(SUCCEEDED(Fire_Event(sText)));
        return S_OK;
    }
Run Code Online (Sandbox Code Playgroud)

执行此操作后,输出结果为:

在此输入图像描述

如果您从后台线程触发它们,我认为您可能在示例项目中遇到事件问题.您还可以使用调试器来执行项目,并检查Fire_调用中是否有任何错误.