Implementing COM objects in Wine

This document is a guideline for implementing COM objects in Wine. It assumes that you know how COM works and that you have a general sense about how to use it in C. (Read the articles about "COM in plain C" by Jeff Glatt on www.codeproject.com for a refresh).

The purpose of this document is to describe conventions used in the Wine tree that we consider best practices; we expect new code to follow them.

  • vtbl pointer in the object
    • All COM objects need to have a pointer to the interface vtbl. We use the fact that in C interfaces are defined as a struct containing nothing but a pointer to the vtbl. Thus we may store an interface instance in the object implementing this interface. If you're implementing an interface called IMyInterface, the object should contain a field called IMyInterface_iface of type IMyInterface. Example:
      typedef struct {
          IMyInterface IMyInterface_iface;
          IMyOtherInterface IMyOtherInterface_iface;
      
          /* other fields */
      } MyObject;
      
      When allocating the object, the initialization of the vtbls should look like:
      obj->IMyInterface_iface.lpVtbl = &MyInterfaceVtbl;
      obj->IMyOtherInterface_iface.lpVtbl = &MyOtherInterfaceVtbl;
        
  • Getting 'This' from an interface pointer
    • For each interface we need an inline function that calculates the object pointer from the interface pointer and its vtbl offset in the object structure. These functions are identical modulo object and interface names:
      static inline MyObject *impl_from_IMyInterface(IMyInterface *iface)
      {
          return CONTAINING_RECORD(iface, MyObject, IMyInterface_iface);
      }
      
      To get the 'This' pointer in a function implementing the interface, we simply have to call this function:
      static HRESULT WINAPI MyObject_MyFunction(IMyInterface *iface, int arg1, IMyInterface *arg2)
      {
         MyObject *This = impl_from_IMyInterface(iface);
         /* function implementation*/
      }
      

      NOTE: It's sometimes useful to get the object from other interfaces like arg2 in the above example. Someone may pass us his own interface implementation. In this case some other techniques like a special QueryInterface call or a vtbl pointer check is needed. If you're absolutely sure that the interface is also implemented by our object or that MS code is broken in the same way you should check it anyway, for example:

      MyObject *unsafe_impl_from_IMyInterface(IMyInterface *iface)
      {
          if (!iface)
              return NULL;
          assert(iface->lpVtbl == &MyInterfaceVtbl);
      
          return impl_from_IMyInterface(iface);
      }
      
  • Getting interface pointers from objects
    • If you have an object pointer and you need a pointer to the interface implemented by it, use the address of its appropriate interface instance, for example:
      IOleObject_SetClientSite(ole_obj, &This->IOleClientSite_iface);
      

      or, more common, code in QueryInterface implementation:

      if (IsEqualGUID(riid, &IID_IMyInterface))
          *ret_iface = &This->IMyInterface_iface;
      
  • Creating the COM object
    • Use QueryInterface to return the correct interface when creating the COM object. The following code takes also care of deleting the object in case of failure (unsupported REFIID).

      MyObject *obj;
      HRESULT hr;
      
      ...
      obj->IMyInterface_iface.lpVtbl = &MyInterfaceVtbl;
      obj->ref = 1;
      ...
      
      hr = IMyInterface_QueryInterface(&obj->IMyInterface_iface, riid, ret_iface);
      IMyInterface_Release(&obj->IMyInterface_iface);
      
      return hr;
         
  • Don't use the PMYINTERFACE nor LPMYINTERFACE types
    • In addition to the normal issues with the PJUNK / LPJUNK types it hides the fact that this are COM interfaces.

COM aggregation

Some objects need to support aggregation. Note that most objects don't need it, so before implementing it, make sure that it's really needed for the object. To support COM aggregation in an object, we need to implement 'outer' IUnknown that we return from CreateInstance implementation if caller asks for an aggregated object. The proper way to add it is:

  • Implement IUnknown interface separated from other interfaces and store 'outer' IUnknown inside the object. Call that inner IUnknown instance IUnknown_inner to make it clear that it's not a regular interface implementation:
    • typedef struct {
          IUnknown IUnknown_inner;
          IMyInterface IMyInterface_iface;
          IMyOtherInterface IMyOtherInterface_iface;
      
          IUnknown *outer_unk;
          LONG ref;
      
          /* other fields */
      } MyObject;
        
  • Inner IUnknown's implementation should contain the usual QueryInterface, AddRef and Release (names of function and implementation details are just an example, not a part of the guideline):

    • static HRESULT WINAPI MyObject_QueryInterface(IUnknown *iface, REFIID riid, void **ret_iface)
      {
          MyObject *This = impl_from_IUnknown(iface);
      
          if (IsEqualGUID(&IID_IUnknown, riid)) {
              TRACE("(%p)->(IID_IUnknown %p)\n", This, ret_iface);
              *ret_iface = &This->IUnknown_inner;
          } else if (IsEqualGUID(&IID_IMyInterface, riid)) {
              TRACE("(%p)->(IID_IMyInterface %p)\n", This, ret_iface);
              *ret_iface = &This->IMyInterface_iface;
          } else if (IsEqualGUID(&IID_IMyOtherInterface, riid)) {
              TRACE("(%p)->(IID_IMyOtherInterface %p)\n", This, ret_iface);
              *ret_iface = &This->IMyOtherInterface_iface;
          } else {
              WARN("(%p)->(%s %p)\n", This, debugstr_guid(riid), ret_iface);
              *ret_iface = NULL;
              return E_NOINTERFACE;
          }
      
          IUnknown_AddRef((IUnknown*)*ret_iface);
          return S_OK;
      }
      
      static ULONG WINAPI MyObject_AddRef(IUnknown *iface)
      {
          MyObject *This = impl_from_IUnknown(iface);
          LONG ref = InterlockedIncrement(&This->ref);
      
          TRACE("(%p) ref=%d\n", This, ref);
      
          return ref;
      }
      
      static ULONG WINAPI MyObject_Release(IUnknown *iface)
      {
          MyObject *This = impl_from_IUnknown(iface);
          LONG ref = InterlockedDecrement(&This->ref);
      
          TRACE("(%p) ref=%d\n", This, ref);
      
          if (!ref) {
              /* destroy code */
          }
      
          return ref;
      }
        
  • CreateInstance implementation should set outer_unk pointer to the provided outer or our own IUnknown, depending on arguments:

    • MyObjectFactory_CreateInstance(IClassFactory *iface, IUnknown *pUnkOuter, REFIID riid, void **ret_iface)
      {
          /* allocation and initialization of the object */
      
          if (pUnkOuter) {
              if (!IsEqualGUID(&IID_IUnknown, riid))
                  return E_INVALIDARG;
              obj->outer_unk = pUnkOuter;
          } else {
              obj->outer_unk = &obj->IUnknown_inner;
          }
      
          /* ... */
      }
        
  • All other interfaces implementations should forward QueryInterface, AddRef and Release calls to outer_unk stored in the object, for example (note that for complex objects the code may look different, but the behavior should be the same):

    • static HRESULT WINAPI MyInterface_QueryInterface(IMyInterface *iface, REFIID riid, void **ret_iface)
      {
          MyObject *This = impl_from_IMyInterface(iface);
          return IUnknown_QueryInterface(This->outer_unk, riid, ret_iface);
      }
      
      static ULONG WINAPI MyInterface_AddRef(IMyInterface *iface)
      {
          MyObject *This = impl_from_IMyInterface(iface);
          return IUnknown_AddRef(This->outer_unk);
      }
      
      static ULONG WINAPI MyInterface_Release(IMyInterface *iface)
      {
          MyObject *This = impl_from_IMyInterface(iface);
          return IUnknown_Release(This->outer_unk);
      }
        


CategoryDevelopment

COMGuideline (last edited 2014-02-04 20:40:03 by MichaelStefaniuc)