Reputation: 1153
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?
Upvotes: 0
Views: 1312
Reputation: 8135
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 e.g. 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", i.e. 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 e.g. 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
.
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.
Upvotes: 5
Reputation: 294307
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).
Upvotes: 3