8

Suppose I have a COM object which users can access via a call such as:

Set s = CreateObject("Server")

What I'd like to be able to do is allow the user to specify an event handler for the object, like so:

Function ServerEvent

   MsgBox "Event handled"

End Function

s.OnDoSomething = ServerEvent

Is this possible and, if so, how do I expose this in my type library in C++ (specifically BCB 2007)?

3 Answers 3

4

This is how I did it just recently. Add an interface that implements IDispatch and a coclass for that interface to your IDL:

[
    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;
};

This is the declaration of the CServerEvents class:

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;        
};

The key here is the implementation of the IConnectionPointImpl and IConnectionPointContainerImpl interfaces and the connection point map. The definition of the OnServerEvent method looks like this:

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;
}

You need to provide a way for your client to specify their handler for your events. You can do this with a dedicated method like "SetHandler" or something, but I prefer to make the handler an argument to the method that is called asynchronously. This way, the user only has to call one method:

STDMETHOD(DoSomethingAsynchronous)( IServerEvents *pCallback );

Store the pointer to the IServerEvents, and then when you want to fire your event, just call the method:

m_pCallback->OnServerEvent();

As for the VB code, the syntax for dealing with events is a little different than what you suggested:

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

I hope this helps.

Sign up to request clarification or add additional context in comments.

3 Comments

The code you provided is VB syntax, not VBScript. The "As" keyword, and "WithEvents" are not available in VBScript.
@1800 INFORMATION, Right you are. I have used both VB and VBScript, but it has been a long time since I have done anything with VBScript. I had forgotten or wasn't aware of that particular difference.
@JeffHillman, the solution provided is for C++. Now I have a similar requirement, but for ActiveX COM developed via C# (we have source code for that). Do you know there is similar approach for C#/.Net world?
2

I'm a little hazy on the details, but maybe the link below might help:

http://msdn.microsoft.com/en-us/library/ms974564.aspx

It looks like your server object needs to implement IProvideClassInfo and then you call ConnectObject in your VBScript code. See also:

http://blogs.msdn.com/ericlippert/archive/2005/02/15/373330.aspx

Comments

1

I ended up following the technique described here.

2 Comments

This is probably the best answer of the lot
That link seems dead now.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.