2012-07-29 118 views
4

我使用ATL简单对象向导在C++中使用ATL构建了一个COM服务器DLL。我遵循微软的ATLDLLCOMServer例子。除了一件事情之外,一切都很好:我没有在VBScript中收到COM事件。我确实收到了C#中的事件。我在早期的基于MFC的实现中使用VBScript中的事件作为ActiveX控件。将COM事件暴露给VBScript(ATL)

我的控制是这样定义的:

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对象是这样的:

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的相关部分是这样的:

[ 
    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永远不会获得calle d。

我曾尝试:

  • 阅读this KB article后,我加入IProvideClassInfo2的实施,但这并没有帮助。
  • 这个FAQ提到输出接口不应该定义为双接口。我从我的界面定义中删除了“双重”,但这没有帮助。
  • 我发现this SO question似乎描述了一个类似的问题。答案从来没有给出,只有一个解决方法。

更新1:

我现在知道它是失败的,但不是原因:在_ISetACLCOMServerEvents_CP.h火灾事件方法进行的调用失败,E_UNEXPECTED。这是该行:

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

解决方案:

问题是,我所谓的事件从COM对象的背景线程发射功能,从错误的公寓等字样。

+0

难道你不需要在vbscript/vba中使用'WithEvents'来响应COM事件吗? – 2012-07-29 22:13:05

+0

不,根据http://msdn.microsoft.com/en-us/library/ms974564.aspx你没有。 – 2012-07-30 08:44:27

+0

我想你在'IProvideClassInfo2Impl'中丢失了第二个参数。我应该是事件接口'IID'。 – 2012-07-30 09:14:55

回答

5

IProvideClassInfo2的实施应该足够了(虽然我觉得它应该工作,即使没有它)。我从模板创建了一个简约的ATL项目作为示例。这是ATL DLL + COM Class +方法和事件+ IProvideClassInfo2

源代码[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\"\n"), CString(sText)); 
     ATLVERIFY(SUCCEEDED(Fire_Event(sText))); 
     return S_OK; 
    } 

和具有执行该时,输出为:

enter image description here

我想你可能会遇到与你的事件有关的问题充足的项目,如果你是从后台线程发射它们。您也可以使用调试器来启动您的项目,并检查Fire_调用中是否有任何错误。

+0

谢谢,你的项目建立并正常工作。我将每个文件与我的相比,但无法找到任何有意义的差异。正如我发现的,在我的项目中调用fire方法的调用失败 - 我已经更新了这个问题。 – 2012-07-30 20:58:39

+0

你会展示你的项目吗?我会检查你的代理类。也许经过一定的修改后,它不再与IDL匹配。 – 2012-07-30 21:40:12

+0

我删除并重新生成了连接点代理类 - 不变。我已经上传了该项目 - 如果您想看看:https://docs.google.com/open?id=0B_otVBEVufIiazV1MzBmb2Y2Y28(通过文件 - >下载下载) – 2012-07-30 22:06:16

0

您可以将您的VBScript函数转换为IDispatch*指针。例如Microsoft.XMLHTTP对象中的onreadystatechange处理程序。

Option Explicit 
Dim xmlhttp 
Function OnReadyStateChange 
    WScript.Echo "ReadyState: " & xmlhttp.readyState 
End Function 
Set xmlhttp = CreateObject("Microsoft.XMLHTTP") 
xmlhttp.onreadystatechange = GetRef("OnReadyStateChange") 
xmlhttp.open "GET", "http://stackoverflow.com", True 
xmlhttp.send 
WScript.Echo "Waiting for 20 seconds to allow OnReadyStateChange events to finish." 
WScript.Sleep 20000 
WScript.Echo "Done!" 

输出:

ReadyState: 1 
Waiting for 20 seconds to allow OnReadyStateChange events to finish. 
ReadyState: 2 
ReadyState: 3 
ReadyState: 4 
Done! 

提取的IDL为C:\Windows\System32\msxml3.dll表明,我们设置了IDispatch*属性:

[ 
    odl, 
    uuid(ED8C108D-4349-11D2-91A4-00C04F7969E8), 
    helpstring("IXMLHTTPRequest Interface"), 
    dual, 
    oleautomation 
] 
interface IXMLHTTPRequest : IDispatch { 
    // ... 
    [id(0x0000000e), propput, helpstring("Register a complete event handler")] 
    HRESULT onreadystatechange([in] IDispatch* rhs); 
}; 

当适应这种方法你的C++应用程序,您的调用代码需要使用IDispatch::Invoke,其DISPID为DISPID_VALUE,例如:

// IDispatch* pOnReadyStateChange <-- set from VBScript. 
HRESULT hr = S_OK; 
CComVariant vResult; 
EXCEPINFO ei = { }; 
DISPPARAMS dispParams = { NULL, 0, 0, 0 }; 
hr = pOnReadyStateChange->Invoke(
    DISPID_VALUE, 
    IID_NULL, 
    0, 
    DISPATCH_METHOD, 
    &dispParams, 
    &vResult, 
    &ei, 
    NULL);