2012-07-29 115 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);