将 COM 事件暴露给 VBScript (ATL) [英] Exposing COM events to VBScript (ATL)
问题描述
我使用ATL 简单对象"向导在 C++ 中使用 ATL 构建了一个 COM 服务器 DLL.我遵循了 Microsoft 的 ATLDLLCOMServer 示例.除了一件事:我没有在 VBScript 中接收 COM 事件,一切都运行良好.我确实在 C# 中接收事件.我在早期的基于 MFC 的实现中将事件作为 ActiveX 控件在 VBScript 中工作.
我的控件定义如下:
class ATL_NO_VTABLE CSetACLCOMServer :public CComObjectRootEx,public CComCoClass<CSetACLCOMServer, &CLSID_SetACLCOMServer>,public IConnectionPointContainerImpl,public CProxy_ISetACLCOMServerEvents,public IDispatchImpl,public IProvideClassInfo2Impl<&CLSID_SetACLCOMServer, &DIID__ISetACLCOMServerEvents, &LIBID_SetACLCOMLibrary>/* 需要 VBS 中的事件支持 */{民众:BEGIN_COM_MAP(CSetACLCOMServer)COM_INTERFACE_ENTRY(ISetACLCOMServer)COM_INTERFACE_ENTRY(IDispatch)COM_INTERFACE_ENTRY(IConnectionPointContainer)COM_INTERFACE_ENTRY(IPProvideClassInfo)COM_INTERFACE_ENTRY(IPProvideClassInfo2)END_COM_MAP()BEGIN_CONNECTION_POINT_MAP(CSetACLCOMServer)CONNECTION_POINT_ENTRY(__uuidof(_ISetACLCOMServerEvents))END_CONNECTION_POINT_MAP()[...]
在 VBScript 中,我像这样使用 COM 对象:
set objSetACL = WScript.CreateObject("SetACL.SetACL", "SetACL_")' 从 SetACL COM 服务器捕获并打印作为事件传递(触发)的消息.' 此函数的名称后缀(MessageEvent)必须与事件名称相同' 由 SetACL 定义.' 前缀 (SetACL_) 可以在调用 WScript.CreateObject 时自由设置sub SetACL_MessageEvent(消息)WScript.Echo 消息结束子
IDL 的相关部分如下所示:
<预><代码>[目的,uuid(E1B57CA5-FD7F-4304-BC0A-8BEBDE231D53),双,不可扩展,指针默认(唯一),helpstring("SetACL COM 接口")]接口 ISetACLCOMServer : IDispatch{};[uuid(00D4DCD3-02B9-4A71-AB61-2283504620C8),版本(1.0),帮助字符串(SetACL 类型库")]library SetACLCOMLibrary{importlib("stdole2.tlb");[uuid(35F76182-7F52-4D6A-BD6E-1317345F98FB),帮助字符串(SetACL 事件接口")]调度接口 _ISetACLCOMServerEvents{特性:方法:[id(1), helpstring("接收将出现在命令行版本屏幕上的字符串消息")]void MessageEvent([in] BSTR message);};[uuid(13379563-8F21-4579-8AC7-CBCD488735DB),帮助字符串(SetACL COM 服务器"),]coclass SetACLCOMServer{[默认] 接口 ISetACLCOMServer;[默认,源]调度接口_ISetACLCOMServerEvents;};};问题:永远不会调用 SetACL_MessageEvent.
我尝试过的:
- 阅读这篇知识库文章后,我添加了IPprovideClassInfo2 的实现,但这没有帮助.
- 此常见问题解答 提到传出接口不应定义为双接口.我从接口定义中删除了双重",但这没有帮助.
- 我发现 this SO question 似乎描述了一个类似的问题.从未给出答案,只是一种解决方法.
更新 1:
我现在知道它在哪里失败了,但不知道为什么:在 _ISetACLCOMServerEvents_CP.h 的火事件方法中,调用 Invoke 失败并显示 E_UNEXPECTED.这是一行:
hr = pConnection->Invoke(1, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, ¶ms, NULL, NULL, NULL);
解决方案:
问题是我从 COM 对象的后台线程调用了事件触发函数,换句话说,是从错误的单元调用.
IProvideClassInfo2
的实现应该足够了(尽管我的印象是即使没有它它也应该工作).我从模板创建了一个简约的 ATL 项目作为示例.这是 ATL DLL + COM 类 + 一个方法和一个事件 + IProvideClassInfo2
:
测试.vbs
如下:
功能测试_事件(文本)WScript.Echo "事件:" + 文本结束函数昏暗测试Set Test = WScript.CreateObject("AlaxInfo.VbsEvents.Foo", "Test_")Test.Method("事件乐趣")
而方法本身是:
//IFooSTDMETHOD(Method)(BSTR sText) throw(){ATLTRACE(atlTraceCOM, 4, _T("sText "%s"
"), CString(sText));ATLVERIFY(成功(Fire_Event(sText)));返回 S_OK;}
执行此操作后,输出为:
我认为如果您从后台线程触发事件,您的示例项目中的事件可能会出现问题.您还可以使用调试器步进您的项目,并检查 Fire_
调用中是否有任何错误.
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()
[...]
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
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;
};
};
The problem: SetACL_MessageEvent never gets called.
What I have tried:
- 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.
UPDATE 1:
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, ¶ms, NULL, NULL, NULL);
Solution:
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.
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
:
Source code [SVN, Browser Friendly] + Win32 Binary.
Test .vbs
is the following:
Function Test_Event(Text)
WScript.Echo "Event: " + Text
End Function
Dim Test
Set Test = WScript.CreateObject("AlaxInfo.VbsEvents.Foo", "Test_")
Test.Method("Event Fun")
And the method itself is:
// 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:
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.
这篇关于将 COM 事件暴露给 VBScript (ATL)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!