将事件处理程序暴露给COM对象的VBScript用户

8
假设我有一个COM对象,用户可以通过类似以下的调用来访问它:
Set s = CreateObject("Server")

我希望能够让用户为对象指定事件处理程序,像这样:

Function ServerEvent

   MsgBox "Event handled"

End Function

s.OnDoSomething = ServerEvent

这是否可行,如果可以,我该如何在我的C++类型库中公开它(特别是BCB 2007)?
3个回答

4

这是我最近的做法。在你的IDL中添加一个实现IDispatch接口的接口和一个该接口的coclass:

[
    object,
    uuid(6EDA5438-0915-4183-841D-D3F0AEDFA466),
    nonextensible,
    oleautomation,
    pointer_default(unique)
]
interface IServerEvents : IDispatch
{
    [id(1)]
    HRESULT OnServerEvent();
}

//...

[
    uuid(FA8F24B3-1751-4D44-8258-D649B6529494),
]
coclass ServerEvents
{
    [default] interface IServerEvents;
    [default, source] dispinterface IServerEvents;
};

这是CServerEvents类的声明:
class ATL_NO_VTABLE CServerEvents :
    public CComObjectRootEx<CComSingleThreadModel>,
    public CComCoClass<CServerEvents, &CLSID_ServerEvents>,
    public IDispatchImpl<IServerEvents, &IID_IServerEvents , &LIBID_YourLibrary, -1, -1>,
    public IConnectionPointContainerImpl<CServerEvents>,
    public IConnectionPointImpl<CServerEvents,&__uuidof(IServerEvents)>
{
public:
    CServerEvents()
    {
    }

    // ...

BEGIN_COM_MAP(CServerEvents)
    COM_INTERFACE_ENTRY(IServerEvents)
    COM_INTERFACE_ENTRY(IDispatch)
    COM_INTERFACE_ENTRY(IConnectionPointContainer)
END_COM_MAP()

BEGIN_CONNECTION_POINT_MAP(CServerEvents)
    CONNECTION_POINT_ENTRY(__uuidof(IServerEvents))
END_CONNECTION_POINT_MAP()

    // ..

    // IServerEvents
    STDMETHOD(OnServerEvent)();

private:
    CRITICAL_SECTION m_csLock;        
};

关键在于实现IConnectionPointImpl和IConnectionPointContainerImpl接口以及连接点映射。OnServerEvent方法的定义如下:

STDMETHODIMP CServerEvents::OnServerEvent()
{
    ::EnterCriticalSection( &m_csLock );

    IUnknown* pUnknown;

    for ( unsigned i = 0; ( pUnknown = m_vec.GetAt( i ) ) != NULL; ++i )
    {       
        CComPtr<IDispatch> spDisp;
        pUnknown->QueryInterface( &spDisp );

        if ( spDisp )
        {
            spDisp.Invoke0( CComBSTR( L"OnServerEvent" ) );
        }
    }

    ::LeaveCriticalSection( &m_csLock );

    return S_OK;
}

您需要为客户端提供一种指定事件处理程序的方式。您可以使用像“SetHandler”这样的专用方法来实现,但我更喜欢将处理程序作为异步调用的方法的参数。这样,用户只需要调用一个方法:

STDMETHOD(DoSomethingAsynchronous)( IServerEvents *pCallback );

存储IServerEvents指针,当您想触发事件时,只需调用该方法:

m_pCallback->OnServerEvent();

关于VB代码,处理事件的语法与您所建议的略有不同:

Private m_server As Server
Private WithEvents m_serverEvents As ServerEvents

Private Sub MainMethod()
    Set s = CreateObject("Server")
    Set m_serverEvents = New ServerEvents

    Call m_searchService.DoSomethingAsynchronous(m_serverEvents)
End Sub

Private Sub m_serverEvents_OnServerEvent()
    MsgBox "Event handled"
End Sub

我希望这能有所帮助。

1
您提供的代码是VB语法,而不是VBScript。在VBScript中,“As”关键字和“WithEvents”不可用。 - 1800 INFORMATION
@1800信息, 没错。我曾经使用过VB和VBScript,但是已经很久没有用VBScript做任何事情了。我忘记了或者不知道那个特定的区别。 - Jeff Hillman
@JeffHillman,您提供的解决方案是针对C++的。现在我有一个类似的需求,但是需要使用C#开发的ActiveX COM(我们有源代码)。您知道是否有类似的方法适用于C#/.Net世界吗? - user908645

2

1

我最终采用了这里描述的技术。


这可能是最好的答案。 - 1800 INFORMATION
3
那个链接现在似乎已经失效了。 - tml

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接