简体   繁体   中英

COM - extending an “interface”?

I've been looking at the BHO tutorial in C++ here: http://www.codeproject.com/Articles/37044/Writing-a-BHO-in-Plain-C

The COM classes CClassFactory and CObjectWithSite must implement IUnknown. CClassfactory also must implement IClassFactory and CObjectWithSite must also implement IObjectWitSite. A single CUnknown class is created so that both can inherit from it and not have to implement IUnknown by themselves.

CUnknown is declared as follows:

template <typename T>
class CUnknown : public T

CClassFactory is declared like this:

class CClassFactory : public CUnknown<IClassFactory>

And CObjectWithSite is declared:

class CObjectWithSite : public CUnknown<IObjectWithSite>

Why is CUnknown extending the type parameter T? Why must we pass the other interfaces to the class constructor?

First, you must understand that COM is a binary standard which has IUnknown at the base of every interface pointer .

An interface pointer is the entry point to an object, which you never see in COM. Although new objects may be created with eg CoCreateInstance or some other COM method ( CoCreateInstance will ultimately call IClassFactory::CreateInstance ), what you actually obtain is a refcounted, possibly proxied, interface pointer . Only the server knows about the actual object .

In Visual C++, and I think in some other Windows C++ compilers, classes are (or may optionally be, in the case of other compilers) implemented using vtables, which are structs of function pointers, where each function pointer points to a virtual method. It's no coincidence that interface pointers are pointers to a vtable pointer, or rather, pointers to a struct with one field, a pointer to a vtable .

Visual C++ class as a struct
----------------------------
struct vtable *
<fields>

When a class inherits multiple "incompatible classes", you get multiple vtables, as many as the incompatibilities (let's define "incompatible classes" as "a classe that is not strictly a superclass/subclass of the other", ie one class's hierarchy forks or they don't have anything in common).

Visual C++ class as a struct
----------------------------
struct vtable1 *
struct vtable2 *
...
struct vtableN *
<fields>

For instance, if you inherit IUnknown and IClassFactory , you get 1 vtable, since IClassFactory inherits from IUnknown , so the first three entries of IUnknown are shared with the first three entries of IClassFactory . In fact, IClassFactory 's vtable is a valid IUnknown vtable.

class CFoo : IClassFactory
--------------------------
struct IClassFactory_vtable * -\
<fields>                       |
                               |
IClassFactory_vtable <---------/   (IUnknown_vtable)
--------------------               -----------------
QueryInterface                     QueryInterface
AddRef                             AddRef
Release                            Release
CreateInstance
LockServer

The inheritance diagram:

IUnknown
    ^
    |
IClassfactory
    ^
    |
  CFoo

But if you inherit eg IDispatch and IConnectionPointContainer , you get 2 vtables, for which the first three entries of each vtable point to the same IUnknown methods, but they are nonetheless two distinct structs.

class CBar : SomethingElse, IDispatch, IConnectionPointContainer
----------------------------------------------------------------
struct SomethingElse_vtable * -----------------------------> ...
struct IDispatch_vtable * ------------------------\
struct IConnectionPointContainer_vtable * --------|--------\
<fields>                                          |        |
                   /------------------------------/        |
                   |                                       |
IDispatch_vtable <-/    IConnectionPointContainer_vtable <-/   (IUnknown_vtable)
----------------        --------------------------------       -----------------
QueryInterface     =    QueryInterface                         QueryInterface
AddRef             =    AddRef                                 AddRef
Release            =    Release                                Release
GetTypeInfoCount        EnumConnectionPoints
GetTypeInfo             FindConnectionPoint
GetIDsOfNames
Invoke

You can search for "diamond problem" to better understand this approach.

SomethingElse
      ^         IUnknown
      |             ^
      |             |
      |       /-----+-----\
      |       |           |
      |  IDispatch     IConnectionPointContainer
      |       ^           ^
      |       |           |
      |       \-----+-----/
      |             |
      \----+-------/
           |
         CBar

However, each vtable is a valid IUnknown vtable. So, when you implement QueryInterface , you must choose which serves as your default IUnknown vtable by casting this to one of the interface pointer types, because static_cast<IUnknown*>(this) is ambiguous in this case.

In the linked article, CUnknown::QueryInterface always returns (void*)this , which is wrong if your first inherited class isn't a COM interface, or if you inherit more than one COM interface hierarchy that you intend to return in QueryInterface . Its author made QueryInterface only know how to handle a class that inherits a single COM interface hierarchy as its first inheritance.

You have to provide the various interface IDs for as many interfaces in the inheritance hierarchy. For instance, if you implement IClassFactory2 , your QueryInterface could return the same vtable when asked for IID_IUnknown , IID_ClassFactory and IID_ClassFactory2 . As far as I know, there is not enough introspection in VC++ to get all IIDs from an interface pointer type, although there is an extension to get a specified IID, __uuidof .

As such, you must provide the full set of IIDs (or interface types to be provided to __uuidof ).

ATL, for instance, will use the first provided interface pointer mapping as the IUnknown interface pointer. It's an arbitrary choice, but a consistent one which makes sure that the identity QueryInterface rule is followed, by always returning the same interface pointer for IID_IUnknown .

Summary

So finally, to answer your specific question, CUnknown extends type T so the given interface pointer's vtable is shared with IUnknown 's vtable. Or rather, that CUnknown<T> ends up implementing the boring IUnknown methods.

If you happen to provide a class that doesn't inherit from IUnknown , or more specifically, which doesn't have compatible virtual methods in the inheritance chain, you actually get QueryInterface , AddRef and Release at the end of the class's first vtable, instead of having all COM interfaces point to those three methods.


PS: CClassFactory could also be templated, so you could reuse it with any COM class.

When you master the "basics" of COM, you should definitely take a look at ATL, as it generally takes the template approach when possible and the macro approach when not.

Unfortunately, you won't understand ATL if you don't understand COM. You can just as easily do something that works well, something that works by coincidence or accident, and something that doesn't work and ends up being hard to debug, without knowing COM. What ATL does is saving you from the trouble of implementing lots of boilerplate correctly .

IMHO the question is moot because CUknown as listed on site is incorrect. Specifically:

template <typename T> STDMETHODIMP CUnknown<T>::QueryInterface(REFIID riid,void **ppvObject)
{
...
  if(IsEqualIID(riid,supported_iids[n])) {
    (*ppvObject)=(void*)this;
...

This implementation does not return a pointer to the desired interface, but a pointer to the start of this . This is incorrect if this implements multiple interfaces (via C++ inheritance). Furthermore, this implementation will always return the same pointer no matter what IID is requested, which cannot possibly be correct for anything more than a trivial object that implements one and only one interface. Not even going into esoteric like COM aggregation and 'outer unknown'.

I recommend you stick with tried and true frameworks like ATL and use CComObject instead. You can look into your SDK atlbase.inl for a correct QueryInterface implementation (see AtlInternalQueryInterface code).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM