Note: This website is archived. For up-to-date information about D projects and development, please visit wiki.dlang.org.

COM Programming

Introduction

In 1998, on COM's fifth birthday, Don Box bemoaned the state of COM. Coming in for particular criticism was the C++ language mapping, which hadn't kept pace with the language itself. Comparing the ease-of-use available to Java programmers to the hoops through which those those using C++ have to leap, he envisaged being able to write code for COM in a way that was more natural to the language. It never transpired (or rather, it did, but in a vastly different form which we know today as .NET).

The D programming language would fair no better. It doesn't help with lifetime management (despite having built-in garbage collection), interface vending or event handling (despite offering delegates). Which leaves us back in 1998. Or even earlier. In C++, smart pointer techniques can not only provide RAII-like management but also wrap IUnknown-based interfaces. Struct inheritance enables you to extend the base COM types, such as VARIANT, with more features. ATL macros offer a simple way to sink events. D gives us a restrictive version of RAII, and none of the others.

The good news is that D has better templates, proper interfaces and true delegates. Juno combines these to make what would otherwise be tedious and error prone an easier, safer task.

Early Bird or Late Starter?

Some languages, particularly scripting languages, use an approach known as late binding to interface with COM objects. This is when methods and properties are called, and events subscribed to, at run-time by querying the type information present in a type library. Early binding is when COM object members are exposed at compile-time, and is obviously the more efficient of the two approaches. But early binding isn't always available.

That Sinking Feeling

Event handling is a case in point. When you need to respond to events fired by a COM object, you usually have to subclass the event interface, connect it to a source object, and implement (at the very least) the interface's Invoke method. Here's the classic event-sinking code.

class DocumentEventHandler : XMLDOMDocumentEvents {

  // implement IUnknown
  HRESULT QueryInterface(inout GUID riid, void** ppvObj) { ... }
  uint AddRef() { ... }
  uint Release() { ... }

  // implement IDispatch
  HRESULT GetTypeInfoCount(out uint pctinfo) { ... }
  HRESULT GetTypeInfo(uint itinfo, uint lcid, out ITypeInfo pptinfo) { ... }
  HRESULT GetIDsOfNames(inout GUID riid, wchar** rgszNames, uint cNames, uint lcid, int* rgdispid) { ... }
  HRESULT Invoke(int dispidMember, inout GUID riid, uint lcid, ushort wFlags, DISPPARAMS* pdispparams, 
    VARIANT* pvarResult, EXCEPINFO* pexcepinfo, uint* puArgErr) {
    switch (dispidMember) {
      case 0xFFFFFD9F:
        onReadyStateChange();
        return S_OK;
      case 0xC6:
        onDataAvailable();
        return S_OK;
      default:
        break;
    }
    return DISP_E_MEMBERNOTFOUND;
  }

  void onReadyStateChange() { ... }
  void onDataAvailable() { ... }

}

IXMLDOMDocument3 doc;
if (CoCreateInstance(CLSID_DOMDocument60, null, CLSCTX_INPROC_SERVER, &doc) == S_OK) {
  IConnectionPointContainer cpc;
  if (doc.QueryInterface(IID_IConnectionPointContainer, &cpc) == S_OK) {
    IConnectionPoint cp;
    uint cookie;
    if (cpc.FindConnectionPoint(IID_XMLDOMDocumentEvents, cp) == S_OK) {
      if (cp.Advise(new DocumentEventHandler, cookie) == S_OK) {
        // Now you can load the XML document and respond to events in the DocumentEventHandler class above.
        doc.set_async(VARIANT_TRUE);
        VARIANT_BOOL result;
        VARIANT xmlSource;
        xmlSource.vt = VT_BSTR;
        xmlSource.bstrVal = SysAllocString("books.xml");
        doc.load(xmlSource, result);
        VariantClear(xmlSource);

        cp.Unadvise(cookie);
      }
      cp.Release();
    }
    cpc.Release();
  }
  doc.Release();
}

Juno makes this a lot easier, and more natural. Note that you don't need to know the DISPID of the method in order to handle it - you can use its name instead. The translation between the name to a DISPID is taken care of behind the scenes.

// Create an instance of IXMLDOMDocument3.
auto doc = DOMDocument60.coCreate!(IXMLDOMDocument3);
scope(exit) doc.Release();

// Create an event provider instance.
auto events = new EventProvider!(XMLDOMDocumentEvents)(doc);
scope(exit) events.Release();

events.bind("onReadyStateChange", {
  writefln("state changed");
});
events.bind("onDataAvailable", {
  writefln("data available");
});

// Tell the document to load asynchronously.
doc.put_async(com_true);

// Load the XML document.
com_bool result;
doc.load("books.xml".toVariant(true), result);