C vs. C++ vs. ...
This specification documents COM interfaces using C++ syntax as a notation but (again) does not mean COM requires that programmers use C++, or any other particular language. COM is based on a binary interoperability standard, rather than a language interoperability standard. Any language supporting "structure" or "record" types containing double-indirected access to a table of function pointers is suitable.
However, this is not to say all languages are created equal. It is certainly true that since the binary vtbl standard is exactly what most C++ compilers generate on PC and many RISC platforms, C++ is a convenient language to use over a language such as C.
That being said, COM can declare interface declarations for both C++ and C (and for other languages if the COM implementor desires). The C++ definition of an interface, which in general is of the form:
interface ISomeInterface
{
virtual RET_T MemberFunction(ARG1_T arg1, ARG2_T arg2 /*, etc */);
[Other member functions]
...
};
then the corresponding C declaration of that interface looks like
typedef struct ISomeInterface
{
ISomeInterfaceVtbl * pVtbl;
} ISomeInterface;
typedef struct ISomeInterfaceVtbl ISomeInterfaceVtbl;
struct ISomeInterfaceVtbl
{
RET_T (*MemberFunction)(ISomeInterface * this, ARG1_T arg1,
ARG2_T arg2 /*, etc */);
[Other member functions]
} ;
This example also illustrates the algorithm for determining the signature of C form of an interface function given the corresponding C++ form of the interface function:
- Use the same argument list as that of the member function, but add an initial parameter which is the pointer to the interface. This initial parameter is a pointer to a C type of the same name as the interface.
- Define a structure type which is a table of function pointers corresponding to the vtbl layout of the interface. The name of this structure type should be the name of the interface followed by Vtbl. Members in this structure have the same names as the member functions of the interface.
The C form of interfaces, when instantiated, generates exactly the same binary structure as a C++ interface does when some C++ class inherits the function signatures (but no implementation) from an interface and overrides each virtual function.
These structures show why C++ is more convenient for the object implementor because C++ will automatically generate the vtbl and the object structure pointing to it in the course of instantiating an object. A C object implementor must define and object structure with the pVtbl field first, explicitly allocate both object structure and interface Vtbl structure, explicitly fill in the fields of the Vtbl structure, and explicitly point the pVtbl field in the object structure to the Vtbl structure. Filling the Vtbl structure need only occur once in an application which then simplifies later object allocations. In any case, once the C program has done this explicit work the binary structure is indistinguishable from what C++ would generate.
On the client side of the picture there is also a small difference between using C and C++. Suppose the client application has a pointer to an ISomeInterface on some object in the variable psome. If the client is compiled using C++, then the following line of code would call a member function in the interface:
psome->MemberFunction(arg1, arg2, /* other parameters */);
A C++ compiler, upon noting that the type of psome is an ISomeInterface * will know to actually perform the double indirection through the hidden pVtbl pointer and will remember to push the psome pointer itself on the stack so the implementation of MemberFunction knows which object to work with. This is, in fact, what C++ compilers do for any member function call; C++ programmers just never see it.
What C++ actually does is be expressed in C as follows:
psome->lpVtbl->MemberFunction(psome, arg1, arg2, /* other parameters */);
This is, in fact, how a client written in C would make the same call. These two lines of code show why C++ is more convenient—there is simply less typing and therefore fewer chances to make mistakes. The resulting source code is somewhat cleaner as well. The key point to remember, however, is that how the client calls an interface member depends solely on the language used to implement the client and is completely unrelated to the language used to implement the object. The code shown above to call an interface function is the code necessary to work with the interface binary standard and not the object itself.