向 VBScript (ATL) 公开 COM 事件

2022-01-14 00:00:00 events com c++ vbscript atl

我使用ATL 简单对象"向导在 C++ 中使用 ATL 构建了一个 COM 服务器 DLL.我遵循了 Microsoft 的 ATLDLLCOMServer 示例.一切正常,除了一件事:我没有收到 VBScript 中的 COM 事件.我确实收到了 C# 中的事件.在早期的基于 MFC 的实现中,我曾在 VBScript 中作为 ActiveX 控件工作.

I have built a COM server DLL in C++ with ATL by using the "ATL simple object" wizard. I followed Microsoft's ATLDLLCOMServer example. Everything works well except for one thing: I do not receive COM events in VBScript. I do receive the events in C#. I had events working in VBScript in an earlier MFC-based implementation as ActiveX control.

我的控件是这样定义的:

My control is defined like this:

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

[...]

在 VBScript 中,我像这样使用 COM 对象:

In VBScript I use the COM object like this:

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

IDL 的相关部分如下所示:

The relevant parts of the IDL look like this:

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

问题:SetACL_MessageEvent 永远不会被调用.

The problem: SetACL_MessageEvent never gets called.

我尝试过的:

  • 阅读 这篇知识库文章后,我添加了IProvideClassInfo2 的实现,但这并没有帮助.
  • 此FAQ 提到不应将传出接口定义为双接口.我从接口定义中删除了双重",但这没有帮助.
  • 我发现 this SO question 似乎描述了一个类似的问题.从未给出答案,只有一种解决方法.
  • After reading this KB article I added the implementation of IProvideClassInfo2, but that did not help.
  • This FAQ mentions that outgoing interface should not defined as a dual interfaces. I removed the "dual" from my interface definition, but that did not help.
  • I found this SO question that seems to describe a similar problem. An answer was never given, only a workaround.

更新 1:

我现在知道失败的地方,但不知道为什么:在 _ISetACLCOMServerEvents_CP.h 中的触发事件方法中,调用 Invoke 失败并显示 E_UNEXPECTED.这是一行:

I know now where it is failing, but not why: in the fire event method in _ISetACLCOMServerEvents_CP.h the call to Invoke fails with E_UNEXPECTED. This is the line:

hr = pConnection->Invoke(1, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &params, NULL, NULL, NULL);

解决方案:

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

The problem was that I called the event firing function from a background thread in the COM object, in other words from the wrong apartment.

推荐答案

IProvideClassInfo2 的实现应该足够了(尽管我的印象是即使没有它也应该可以工作).我从模板创建了一个简约的 ATL 项目作为示例.这是 ATL DLL + COM 类 + 一个方法和一个事件 + IProvideClassInfo2:

Implementation of IProvideClassInfo2 should be sufficient (though I was under impression that it should work even without it). I created a minimalistic ATL project from template as a sample. This is ATL DLL + COM Class + a method and an event + IProvideClassInfo2:

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

Source code [SVN, Browser Friendly] + Win32 Binary.

测试.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")

而方法本身是:

// IFoo
    STDMETHOD(Method)(BSTR sText) throw()
    {
        ATLTRACE(atlTraceCOM, 4, _T("sText "%s"
"), CString(sText));
        ATLVERIFY(SUCCEEDED(Fire_Event(sText)));
        return S_OK;
    }

执行此操作后,输出为:

And having executed this, the output is:

我认为如果您从后台线程触发事件,您的示例项目中的事件可能会出现问题.您还可以使用调试器单步执行您的项目,并检查 Fire_ 调用中是否有任何错误.

I thought you might be having issues with events in your sample project if you are firing them from a background thread. You can also step your project with debugger and check if you have any errors in the Fire_ call.

相关文章