4

「ATLシンプルオブジェクト」ウィザードを使用して、ATLを使用してC++でCOMサーバーDLLを構築しました。MicrosoftのATLDLLCOMServerの例に従いました。VBScriptでCOMイベントを受信しないという1つのことを除いて、すべてがうまく機能します。私はC#でイベントを受け取ります。以前のMFCベースの実装のVBScriptで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()

[...]

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が呼び出されることはありません。

私が試したこと:

  • このKB記事を読んだ後、IProvideClassInfo2の実装を追加しましたが、それは役に立ちませんでした。
  • このFAQには、発信インターフェイスをデュアルインターフェイスとして定義するべきではないことが記載されています。インターフェイス定義から「デュアル」を削除しましたが、それは役に立ちませんでした。
  • 同様の問題を説明しているように見えるこのSOの質問を見つけました。答えは決して与えられず、回避策だけでした。

更新1:

どこで失敗しているのかはわかりましたが、理由はわかりません。_ISetACLCOMServerEvents_CP.hのfire eventメソッドで、Invokeの呼び出しがE_UNEXPECTEDで失敗します。これは次の行です。

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

解決:

問題は、COMオブジェクトのバックグラウンドスレッドから、つまり間違ったアパートメントからイベント発生関数を呼び出したことです。

4

2 に答える 2

5

の実装はIProvideClassInfo2十分なはずです(それがなくても機能するはずだという印象を受けましたが)。サンプルとしてテンプレートからミニマルなATLプロジェクトを作成しました。これは、ATL DLL + COMクラス+メソッドとイベントです+ IProvideClassInfo2

ソースコード[ SVNブラウザフレンドリー] + Win32Binary

テスト.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;
    }

そしてこれを実行すると、出力は次のようになります。

ここに画像の説明を入力してください

バックグラウンドスレッドからイベントを起動している場合は、サンプルプロジェクトのイベントで問題が発生している可能性があると思いました。デバッガーを使用してプロジェクトをステップ実行し、Fire_呼び出しでエラーが発生していないかどうかを確認することもできます。

于 2012-07-30T16:28:07.163 に答える
0

VBScript関数をIDispatch*ポインターに変えることができます。たとえば、オブジェクトのonreadystatechangeハンドラー。Microsoft.XMLHTTP

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);
于 2013-03-21T00:06:23.790 に答える