Dispatch interfaces as connection point interfaces


Last time, we learned about how connection points work. One special case of this is where the connection interface is a dispatch interface.

Dispatch interfaces are, as the name suggests, COM interfaces based on IDispatch. The IDispatch interface is the base interface for OLE automation objects, and if you want your connection point interface to be usable from script, you probably should make it a dispatch interface.

I'm assuming you know how IDispatch works. The short version is that script that wants to invoke a method or property calls Get­IDs­Of­Names to get the dispatch ID for the method or property it wants to access, and it uses the type library to figure out things like the parameters and return value. Once the scripting engine figures out how the method or property expects to be called, it can call IDispatch::Invoke passing the dispatch ID and a DISPPARAMS structure that holds the parameters.

Nowadays, this sort of thing goes by the fancy name of reflection, but back in the OLE Automation days, it was simply all in a day's work. You kids think you invented everything.

Just like as with regular connection point interfaces, a dispatch interface used as a connection point interface consists of events which are formally implemented as methods.

dispinterface DWidgetEvents
{
 [id(WDISPID_RENAMED)]
 HRESULT Renamed([in] BSTR oldName, [in] BSTR newName);
...
};

You declare that your object is a source of events for this interface by noting it in your object declaration. (Thanks, Medinoc for noting the error in the original version of this article.)

coclass Widget
{
 [default] interface IWidget;
 [default, source] dispinterface DWidgetEvents;
}

A client registers with the connection point with the DIID_DWidget­Events interface. By convention, dispatch interfaces usually end with the word Events and are often prefixed with the letter D, and the interface ID symbol begins with DIID rather than simply IID. These conventions are not universally adhered-to, so don't freak out if you see people who don't follow them. (If you declare your dispatch interface in an IDL file, then the MIDL compiler will generate the dispatch interface ID with the DIID prefix for you.)

Now, formally, when the connection point wants to invoke the Renamed method, it calls Get­IDs­Of­Names to get the ID for the method called L"Renamed", and asks for the type library to figure out what the parameters are. But this is frequently just pointless busy-work: The connection point often already knows the answer, since the connection point already knows what interface it is talking to. It doesn't need to do any "reflection" since the connection point already knows what the IDs and calling conventions are. In the same way, your C# code doesn't need to use reflection to call a method on an object whose assembly you already have referenced in your project. (The Get­IDs­Of­Names exists not for connection points, but rather to assist dynamically-typed languages, where you can try to invoke any method on any object, and the method is looked up at run time.)

In other words, the connection point already knows that the ID for the method Rename is WDISPID_RENAMED, and that it takes two BSTR parameters, because that was part of the contract for registering with the connection point in the first place.

This means that in practice, the only method on the client that is ever called is IDispatch::Invoke.

Here is a template base class that I use for my connection point interface implementations of dispatch interfaces. We'll discuss the pieces afterwards:

template<typename DispInterface>
class CDispInterfaceBase : public DispInterface
{
public:
 CDispInterfaceBase() : m_cRef(1), m_dwCookie(0) { }

 /* IUnknown */
 IFACEMETHODIMP QueryInterface(REFIID riid, void **ppv)
 {
  *ppv = nullptr;
  HRESULT hr = E_NOINTERFACE;
  if (riid == IID_IUnknown || riid == IID_IDispatch ||
      riid == __uuidof(DispInterface))
  {
   *ppv = static_cast<DispInterface *>
          (static_cast<IDispatch*>(this));
   AddRef();
   hr = S_OK;
  }
  return hr;
 }

 IFACEMETHODIMP_(ULONG) AddRef()
 {
  return InterlockedIncrement(&m_cRef);
 }

 IFACEMETHODIMP_(ULONG) Release()
 {
  LONG cRef = InterlockedDecrement(&m_cRef);
  if (cRef == 0) delete this;
  return cRef;
 }

 // *** IDispatch ***
 IFACEMETHODIMP GetTypeInfoCount(UINT *pctinfo)
 {
  *pctinfo = 0;
  return E_NOTIMPL;
 }

 IFACEMETHODIMP GetTypeInfo(UINT iTInfo, LCID lcid,
                            ITypeInfo **ppTInfo)
 {
  *ppTInfo = nullptr;
  return E_NOTIMPL;
 }

 IFACEMETHODIMP GetIDsOfNames(REFIID, LPOLESTR *rgszNames,
                              UINT cNames, LCID lcid,
                              DISPID *rgDispId)
 {
  return E_NOTIMPL;
 }

 IFACEMETHODIMP Invoke(
    DISPID dispid, REFIID riid, LCID lcid, WORD wFlags,
    DISPPARAMS *pdispparams, VARIANT *pvarResult,
    EXCEPINFO *pexcepinfo, UINT *puArgErr)
 {
  if (pvarResult) VariantInit(pvarResult);
  return SimpleInvoke(dispid, pdispparams, pvarResult);
 }

 // Derived class must implement SimpleInvoke
 virtual HRESULT SimpleInvoke(DISPID dispid,
    DISPPARAMS *pdispparams, VARIANT *pvarResult) = 0;

public:
 HRESULT Connect(IUnknown *punk)
 {
  HRESULT hr = S_OK;
  CComPtr<IConnectionPointContainer> spcpc;
  if (SUCCEEDED(hr)) {
   hr = punk->QueryInterface(IID_PPV_ARGS(&spcpc));
  }
  if (SUCCEEDED(hr)) {
  hr = spcpc->FindConnectionPoint(__uuidof(DispInterface), &m_spcp);
  }
  if (SUCCEEDED(hr)) {
  hr = m_spcp->Advise(this, &m_dwCookie);
  }
  return hr;
 }

 void Disconnect()
 {
  if (m_dwCookie) {
   m_spcp->Unadvise(m_dwCookie);
   m_spcp.Release();
   m_dwCookie = 0;
  }
 }

private:
 LONG m_cRef;
 CComPtr<IConnectionPoint> m_spcp;
 DWORD m_dwCookie;
};

First, a distraction: Our Query­Interface implementation performs a double-cast of this to IDispatch, then to the templated interface. This ensures that the templated interface pointer and IDispatch are compatible. It would be bad if somebody tried to use this Query­Interface implementation with something unrelated to IDispatch. (Yes, I could've used std::is_base_of, but I'm an old-timer who grew up before TR1.)

The bulk of the class merely stubs out all the methods of IDispatch, save for IDispatch::Invoke, which does a little grunt work (initializing the result VARIANT) and then leaves the derived class to do the heavy lifting.

Finally, there are two public methods Connect and Disconnect. These perform the Advise and Unadvise calls we saw yesterday. To simplify things for our caller, we save the IConnection­Pointer we registered against so that the caller doesn't have to pass it back in when disconnecting.

Exercise: Is the m_spcp.Release() call in Disconnect really necessary? (Assuming that Connect is called at most once.)

This helper template class makes writing dispatch interface connection point clients really simple, since all you have to do is implement Simple­Invoke in the form of a switch statement on the dispatch IDs you care about:

class CWidgetClient : public CDispInterfaceBase
{
public:
 CWidgetClient() { }

 HRESULT SimpleInvoke(
    DISPID dispid, DISPPARAMS *pdispparams, VARIANT *pvarResult)
{
 switch (dispid) {
 case WDISPID_RENAMED:
  HeyLookItGotRenamed(pdispparams->rgvarg[0].bstrVal,
                      pdispparams->rgvarg[1].bstrVal);
  break;
 }
 return S_OK;
};

In the Simple­Invoke method, we switch on the dispatch ID, and if we see one we like, we extract the parameters from the pdispparams.

Update: Commenter parkrrr points out a huge gotcha with the DISP­PARAMS structure: The parameters are passed in reverse order. I don't know why. They just are.

Next time, we'll start hooking up events to our Little Program so it can update when the user navigates an Explorer or Internet Explorer window.

Warning! Managed code! The CLR understands the connection point/dispatch interface convention and exposes a dispatch event to managed code in the form of a CLR event and corresponding delegate. For example, our Renamed event is exposed as an event called Renamed, with delegate type DWidget­Events_Renamed­Event­Handler. You can listen on the event the way you listen to any other CLR event: widget.Renamed += this.OnRenamed;.

Note: I completely ignored the subject of dual interfaces. You can read about those if you like, but we won't need to know about them for the job at hand.

Comments (18)
  1. parkrrrr says:

    One thing I've never understood about IDispatch::Invoke but that nevertheless appears to be true is that the parameters in pdispparams are in reverse order. Thus, you're calling HeyLookItGotRenamed( newName, oldName ) which may or may not have been your intention.

    [Excellent point! I've called it out in the article as an update. -Raymond]
  2. Jerome says:

    Thanks for all the work you've put into the articles recently. The more technical articles are always fascinating.

  3. John Doe says:

    Rant: OLE type libraries are reflection if your mirrors show people as stick figures. And you have to tell the mirrors what to reflect.

    Sigh: I feel kind of sorry for C++ programmers that do such boilerplate code for a living. No wonder SLOCs are used on them for productivity evaluations and effort estimates.

    Exercise: you could set it to NULL with the same effect, or even destroy the smart pointer. But in essence, you need the actual Release to happen in Disconnect to break the potential and likely cycles you've warned us about.

  4. James says:

    Is it Widget or WidgetClient that implements DWidgetEvents?  Shouldn't the IDL show Widget implementing IConnectionPointContainer?

  5. Medinoc says:

    If this is only about dispinterfaces and not dual interfaces, why make the class a template? From what I understand, dispinterfaces don't actually exist in C++, so the only interface the class will ever see is IDispatch itself, isn't it?

    (of course, it becomes useful when dual interfaces appear).

    [Dispinterfaces are interfaces which derive from IDispatch and add no new methods. But they are still separate interfaces with separate IIDs. Otherwise you wouldn't be able to tell different dispinterfaces apart, which is a problem if you have an object that supports multiple dispinterfaces. -Raymond]
  6. Medinoc says:

    @James, @Raymond: Maybe DWidgetEvents should be declared [source, default] instead of just [default].

    [You're right. -Raymond]
  7. Exercise: Is the m_spcp.Release() call in Disconnect really necessary? (Assuming that Connect is called at most once.)

    This depends on the lifetime of the class itself. But in the case of

    int wmain()

    {

    CoInitialize(nullptr);

    MyDispInterface disp; //based on CDispInterfaceBase

    … do stuff

    CoUninitialize();

    return 0;

    }

    Then it is needed. The call to Release will cause the contained smart pointer to release its interface right away. If this didn't happen then it would occur when disp is destroyed at the end of the function. This is bad because CoUninitialize would have run, force unloaded any COM servers left in the process leaving the address of release unallocated. When release is called you would then get an access violation instead.

    This is based on experience -_-;.

    [This is a general problem when you mix RAII and non-RAII: Object destruction and cleanup no longer run in the right order. It's not a problem specific to this class, so let's assume that's not going on. -Raymond]
  8. Adam Rosenfield says:

    Who's responsible for validating that the correct argument types got passed into SimpleDispatch() via this reflection-like system?  In other words, is CWidgetClient::SimpleDispatch() guaranteed that pdispparams contains exactly two parameters of type BSTR when dispid is WDISPID_RENAMED?  Or is this just another case of the error checking being omitted for expository purposes?

    [It is the caller's responsibility to pass correct parameters. -Raymond]
  9. ChuckOp says:

    I shuddered when I read "dual interfaces".  I brought back memories (nightmares?) of creating IAccessible as a dual interface supporting IDispatch.  Weirdly, If I search Bing with "COM dual interfaces" the IAccessible MSDN documentation is the first result.

  10. Joshua says:

    [It is the caller's responsibility to pass correct parameters. -Raymond]

    Hmmm. If the parameters are not checked when marshaling to a privileged COM server, this leads to an interesting potential security problem. Probably yet another UAC bypass and not much else.

  11. foo says:

    For the exercise, I would go with what Crescens2k says. This doesn't magically solve everything though, since the developer of a class that derives from CDispInterfaceBase may very well put a call to Disconnect() in its destructor just to be helpful, so a crash would still occur on program termination (in the Unadvise call) if someone did a Connect and then just let the derived object's destructor cleanup on scope exit (after CoUninitialize had been called).

    @Joshua. I'm not sure I understand. It's true that if the COM marshalling code or the privileged service or the caller are buggy or don't obey the ground rules of programming (blogs.msdn.com/…/555511.aspx) then sure, you could have a security error. But you would get this anyway. VARIANT arguments make checking types, etc more of a pain, but not impossible.

  12. Jo says:

    I guess it is begins with DIID rather than simply *IID*  (spelling mistake)

  13. [This is a general problem when you mix RAII and non-RAII: Object destruction and cleanup no longer run in the right order. It's not a problem specific to this class, so let's assume that's not going on. -Raymond]

    If that is the case then no, the Release in unadvise isn't really necessary. The interface pointer is wrapped in a smart pointer, and the destructor for this is guaranteed to run when the dispinterface class goes out of scope.

    As shamelessly stolen from atlcomcli.h

    ~CComPtrBase() throw()

       {

           if (p)

               p->Release();

       }

    The smart pointer releases the contained interface on cleanup.

    [The dispinterface class is a COM object, so it is on the heap, not on the stack. It therefore does not go out of scope. -Raymond]
  14. [The dispinterface class is a COM object, so it is on the heap, not on the stack. It therefore does not go out of scope. -Raymond]

    Well, I also assumed that since you were using smart pointers everywhere else, one would be used for the dispinterface too.

    [Sure, but the object is reference-counted, and one of the references is given to an external component (the connection point). The smart pointer can release only its own reference. -Raymond]
  15. foo says:

    So maybe the reason for doing the Release in Disconnect is just to make the Connect/Disconnect methods fully complement one another and work as named. ie: Before calling Connect you are not connected to the server (m_spcp is not set). So having m_spcp still around after calling Disconnect (thus still connected) could be unexpected by users of the class.

    If the server were misbehaved and still called back to clients after they returned from calling Unadvise then you could have potential problems, making it best to Release the thing in the Disconnect method. But I'm guessing since that's not allowed it shouldn't considered here. (On the other hand, it is possible to receive callbacks before the call to Un/Advise completes.)

  16. James says:

    class CWidgetClient : public CDispInterfaceBase

    should probably be changed to

    class CWidgetClient : public CDispInterfaceBase<DWidgetEvents>

  17. [Sure, but the object is reference-counted, and one of the references is given to an external component (the connection point). The smart pointer can release only its own reference. -Raymond]

    I have a feeling that we are just going around in circles. So I'll just spell everything out in one post.

    When the dispinterface is created, it will have one reference. This is the client's reference so it can do stuff like call Connect and Disconnect. After the call to connect, the dispinterface should have two references, one for the client, the other for the connection point, and the server will have one for the at least one. This is because the call to IConnectionPoint::Advise calls QueryInterface to get the desired interface pointer, and of course the dispinterface has a reference to the connection point.

    Now, the call to Disconnect calls IConnectionPoint::Unadvise, and this calls Release on the stored dispinterface pointer. That means the only other reference is held by the client, when the client releases its reference to the dispinterface, then that will trigger the destructor for the class to be called, and this will cause the contained smart pointer for IConnectionPoint to be destroyed too. This will call Release on the contained interface pointer and that will remove the last reference to the server.

    [That was the logic I was looking for. Just saying "The reference is released when the destructor is run" is not enough, because there might be a reference cycle that prevents the destructor from running. Saying "When the smart pointer releases the object" is still not enough, because there might be references aside from the one in the smart pointer. You have to rule out both before you're done. -Raymond]
  18. foo says:

    Meh, I took it as a common understanding that Unadvise calls Release on the client pointer handed it in Advise.

Comments are closed.

Skip to main content