                              Wine Documentation                               
Prev                      Chapter 9. COM/OLE in Wine                       Next
-------------------------------------------------------------------------------

9.3. Writing OLE Components for Wine

Based on the comments in wine/include/wine/obj_base.h.

This section describes how to create your own natively compiled COM/OLE
components.

9.3.1. Macros to define a COM interface

The goal of the following set of definitions is to provide a way to use the
same header file definitions to provide both a C interface and a C++ object
oriented interface to COM interfaces. The type of interface is selected
automatically depending on the language but it is always possible to get the C
interface in C++ by defining CINTERFACE.

It is based on the following assumptions:

  * all COM interfaces derive from IUnknown, this should not be a problem.
   
  * the header file only defines the interface, the actual fields are defined
    separately in the C file implementing the interface.
   
The natural approach to this problem would be to make sure we get a C++ class
and virtual methods in C++ and a structure with a table of pointer to functions
in C. Unfortunately the layout of the virtual table is compiler specific, the
layout of g++ virtual tables is not the same as that of an egcs virtual table
which is not the same as that generated by Visual C+. There are workarounds to
make the virtual tables compatible via padding but unfortunately the one which
is imposed to the WINE emulator by the Windows binaries, i.e. the Visual C++
one, is the most compact of all.

So the solution I finally adopted does not use virtual tables. Instead I use
inline non virtual methods that dereference the method pointer themselves and
perform the call.

Let's take Direct3D as an example:

#define ICOM_INTERFACE IDirect3D
#define IDirect3D_METHODS \
    ICOM_METHOD1(HRESULT,Initialize,    REFIID,) \
    ICOM_METHOD2(HRESULT,EnumDevices,   LPD3DENUMDEVICESCALLBACK,, LPVOID,) \
    ICOM_METHOD2(HRESULT,CreateLight,   LPDIRECT3DLIGHT*,, IUnknown*,) \
    ICOM_METHOD2(HRESULT,CreateMaterial,LPDIRECT3DMATERIAL*,, IUnknown*,) \
    ICOM_METHOD2(HRESULT,CreateViewport,LPDIRECT3DVIEWPORT*,, IUnknown*,) \
    ICOM_METHOD2(HRESULT,FindDevice,    LPD3DFINDDEVICESEARCH,, LPD3DFINDDEVICERESULT,)
#define IDirect3D_IMETHODS \
    IUnknown_IMETHODS \
    IDirect3D_METHODS
ICOM_DEFINE(IDirect3D,IUnknown)
#undef ICOM_INTERFACE

#ifdef ICOM_CINTERFACE
// *** IUnknown methods *** //
#define IDirect3D_QueryInterface(p,a,b) ICOM_CALL2(QueryInterface,p,a,b)
#define IDirect3D_AddRef(p)             ICOM_CALL (AddRef,p)
#define IDirect3D_Release(p)            ICOM_CALL (Release,p)
// *** IDirect3D methods *** //
#define IDirect3D_Initialize(p,a)       ICOM_CALL1(Initialize,p,a)
#define IDirect3D_EnumDevices(p,a,b)    ICOM_CALL2(EnumDevice,p,a,b)
#define IDirect3D_CreateLight(p,a,b)    ICOM_CALL2(CreateLight,p,a,b)
#define IDirect3D_CreateMaterial(p,a,b) ICOM_CALL2(CreateMaterial,p,a,b)
#define IDirect3D_CreateViewport(p,a,b) ICOM_CALL2(CreateViewport,p,a,b)
#define IDirect3D_FindDevice(p,a,b)     ICOM_CALL2(FindDevice,p,a,b)
#endif

Comments:

The ICOM_INTERFACE macro is used in the ICOM_METHOD macros to define the type
of the 'this' pointer. Defining this macro here saves us the trouble of having
to repeat the interface name everywhere. Note however that because of the way
macros work, a macro like ICOM_METHOD1 cannot use 'ICOM_INTERFACE##_VTABLE'
because this would give 'ICOM_INTERFACE_VTABLE' and not 'IDirect3D_VTABLE'.

ICOM_METHODS defines the methods specific to this interface. It is then
aggregated with the inherited methods to form ICOM_IMETHODS.

ICOM_IMETHODS defines the list of methods that are inheritable from this
interface. It must be written manually (rather than using a macro to generate
the equivalent code) to avoid macro recursion (which compilers don't like).

The ICOM_DEFINE finally declares all the structures necessary for the
interface. We have to explicitly use the interface name for macro expansion
reasons again. Inherited methods are inherited in C by using the
IDirect3D_METHODS macro and the parent's Xxx_IMETHODS macro. In C++ we need
only use the IDirect3D_METHODS since method inheritance is taken care of by the
language.

In C++ the ICOM_METHOD macros generate a function prototype and a call to a
function pointer method. This means using once 't1 p1, t2 p2, ...' and once
'p1, p2' without the types. The only way I found to handle this is to have one
ICOM_METHOD macro per number of parameters and to have it take only the type
information (with const if necessary) as parameters. The 'undef ICOM_INTERFACE'
is here to remind you that using ICOM_INTERFACE in the following macros will
not work. This time it's because the ICOM_CALL macro expansion is done only
once the 'IDirect3D_Xxx' macro is expanded. And by that time ICOM_INTERFACE
will be long gone anyway.

You may have noticed the double commas after each parameter type. This allows
you to put the name of that parameter which I think is good for documentation.
It is not required and since I did not know what to put there for this example
(I could only find doc about IDirect3D2), I left them blank.

Finally the set of 'IDirect3D_Xxx' macros is a standard set of macros defined
to ease access to the interface methods in C. Unfortunately I don't see any way
to avoid having to duplicate the inherited method definitions there. This time
I could have used a trick to use only one macro whatever the number of
parameters but I prefered to have it work the same way as above.

You probably have noticed that we don't define the fields we need to actually
implement this interface: reference count, pointer to other resources and
miscellaneous fields. That's because these interfaces are just that:
interfaces. They may be implemented more than once, in different contexts and
sometimes not even in Wine. Thus it would not make sense to impose that the
interface contains some specific fields.

9.3.2. Bindings in C

In C this gives:

typedef struct IDirect3DVtbl IDirect3DVtbl;
struct IDirect3D {
    IDirect3DVtbl* lpVtbl;
};
struct IDirect3DVtbl {
    HRESULT (*fnQueryInterface)(IDirect3D* me, REFIID riid, LPVOID* ppvObj);
    ULONG (*fnAddRef)(IDirect3D* me);
    ULONG (*fnRelease)(IDirect3D* me);
    HRESULT (*fnInitialize)(IDirect3D* me, REFIID a);
    HRESULT (*fnEnumDevices)(IDirect3D* me, LPD3DENUMDEVICESCALLBACK a, LPVOID b);
    HRESULT (*fnCreateLight)(IDirect3D* me, LPDIRECT3DLIGHT* a, IUnknown* b);
    HRESULT (*fnCreateMaterial)(IDirect3D* me, LPDIRECT3DMATERIAL* a, IUnknown* b);
    HRESULT (*fnCreateViewport)(IDirect3D* me, LPDIRECT3DVIEWPORT* a, IUnknown* b);
    HRESULT (*fnFindDevice)(IDirect3D* me, LPD3DFINDDEVICESEARCH a, LPD3DFINDDEVICERESULT b);
}; 

#ifdef ICOM_CINTERFACE
// *** IUnknown methods *** //
#define IDirect3D_QueryInterface(p,a,b) (p)->lpVtbl->fnQueryInterface(p,a,b)
#define IDirect3D_AddRef(p)             (p)->lpVtbl->fnAddRef(p)
#define IDirect3D_Release(p)            (p)->lpVtbl->fnRelease(p)
// *** IDirect3D methods *** //
#define IDirect3D_Initialize(p,a)       (p)->lpVtbl->fnInitialize(p,a)
#define IDirect3D_EnumDevices(p,a,b)    (p)->lpVtbl->fnEnumDevice(p,a,b)
#define IDirect3D_CreateLight(p,a,b)    (p)->lpVtbl->fnCreateLight(p,a,b)
#define IDirect3D_CreateMaterial(p,a,b) (p)->lpVtbl->fnCreateMaterial(p,a,b)
#define IDirect3D_CreateViewport(p,a,b) (p)->lpVtbl->fnCreateViewport(p,a,b)
#define IDirect3D_FindDevice(p,a,b)     (p)->lpVtbl->fnFindDevice(p,a,b)
#endif

Comments:

IDirect3D only contains a pointer to the IDirect3D virtual/jump table. This is
the only thing the user needs to know to use the interface. Of course the
structure we will define to implement this interface will have more fields but
the first one will match this pointer.

The code generated by ICOM_DEFINE defines both the structure representing the
interface and the structure for the jump table. ICOM_DEFINE uses the parent's
Xxx_IMETHODS macro to automatically repeat the prototypes of all the inherited
methods and then uses IDirect3D_METHODS to define the IDirect3D methods.

Each method is declared as a pointer to function field in the jump table. The
implementation will fill this jump table with appropriate values, probably
using a static variable, and initialize the lpVtbl field to point to this
variable.

The IDirect3D_Xxx macros then just derefence the lpVtbl pointer and use the
function pointer corresponding to the macro name. This emulates the behavior of
a virtual table and should be just as fast.

This C code should be quite compatible with the Windows headers both for code
that uses COM interfaces and for code implementing a COM interface.

9.3.3. Bindings in C++

And in C++ (with gcc's g++):

typedef struct IDirect3D: public IUnknown {
    private: HRESULT (*fnInitialize)(IDirect3D* me, REFIID a);
    public: inline HRESULT Initialize(REFIID a) { return ((IDirect3D*)t.lpVtbl)->fnInitialize(this,a); };
    private: HRESULT (*fnEnumDevices)(IDirect3D* me, LPD3DENUMDEVICESCALLBACK a, LPVOID b);
    public: inline HRESULT EnumDevices(LPD3DENUMDEVICESCALLBACK a, LPVOID b)
        { return ((IDirect3D*)t.lpVtbl)->fnEnumDevices(this,a,b); };
    private: HRESULT (*fnCreateLight)(IDirect3D* me, LPDIRECT3DLIGHT* a, IUnknown* b);
    public: inline HRESULT CreateLight(LPDIRECT3DLIGHT* a, IUnknown* b)
        { return ((IDirect3D*)t.lpVtbl)->fnCreateLight(this,a,b); };
    private: HRESULT (*fnCreateMaterial)(IDirect3D* me, LPDIRECT3DMATERIAL* a, IUnknown* b);
    public: inline HRESULT CreateMaterial(LPDIRECT3DMATERIAL* a, IUnknown* b)
        { return ((IDirect3D*)t.lpVtbl)->fnCreateMaterial(this,a,b); };
    private: HRESULT (*fnCreateViewport)(IDirect3D* me, LPDIRECT3DVIEWPORT* a, IUnknown* b);
    public: inline HRESULT CreateViewport(LPDIRECT3DVIEWPORT* a, IUnknown* b)
        { return ((IDirect3D*)t.lpVtbl)->fnCreateViewport(this,a,b); };
    private:  HRESULT (*fnFindDevice)(IDirect3D* me, LPD3DFINDDEVICESEARCH a, LPD3DFINDDEVICERESULT b);
    public: inline HRESULT FindDevice(LPD3DFINDDEVICESEARCH a, LPD3DFINDDEVICERESULT b)
        { return ((IDirect3D*)t.lpVtbl)->fnFindDevice(this,a,b); };
};

Comments:

In C++ IDirect3D does double duty as both the virtual/jump table and as the
interface definition. The reason for this is to avoid having to duplicate the
mehod definitions: once to have the function pointers in the jump table and
once to have the methods in the interface class. Here one macro can generate
both. This means though that the first pointer, t.lpVtbl defined in IUnknown,
must be interpreted as the jump table pointer if we interpret the structure as
the interface class, and as the function pointer to the QueryInterface method,
t.fnQueryInterface, if we interpret the structure as the jump table.
Fortunately this gymnastic is entirely taken care of in the header of IUnknown.

Of course in C++ we use inheritance so that we don't have to duplicate the
method definitions.

Since IDirect3D does double duty, each ICOM_METHOD macro defines both a
function pointer and a non-virtual inline method which dereferences it and
calls it. This way this method behaves just like a virtual method but does not
create a true C++ virtual table which would break the structure layout. If you
look at the implementation of these methods you'll notice that they would not
work for void functions. We have to return something and fortunately this seems
to be what all the COM methods do (otherwise we would need another set of
macros).

Note how the ICOM_METHOD generates both function prototypes mixing types and
formal parameter names and the method invocation using only the formal
parameter name. This is the reason why we need different macros to handle
different numbers of parameters.

Finally there is no IDirect3D_Xxx macro. These are not needed in C++ unless the
CINTERFACE macro is defined in which case we would not be here.

This C++ code works well for code that just uses COM interfaces. But it will
not work with C++ code implement a COM interface. That's because such code
assumes the interface methods are declared as virtual C++ methods which is not
the case here.

9.3.4. Implementing a COM interface.

This continues the above example. This example assumes that the implementation
is in C.

typedef struct _IDirect3D {
    void* lpVtbl;
    // ...
 } _IDirect3D;

static ICOM_VTABLE(IDirect3D) d3dvt;

// implement the IDirect3D methods here

int IDirect3D_fnQueryInterface(IDirect3D* me)
{
    ICOM_THIS(IDirect3D,me);
    // ...
}

// ...

static ICOM_VTABLE(IDirect3D) d3dvt = {
    ICOM_MSVTABLE_COMPAT_DummyRTTIVALUE
    IDirect3D_fnQueryInterface,
    IDirect3D_fnAdd,
    IDirect3D_fnAdd2,
    IDirect3D_fnInitialize,
    IDirect3D_fnSetWidth
};

Comments:

We first define what the interface really contains. This is the _IDirect3D
structure. The first field must of course be the virtual table pointer.
Everything else is free.

Then we predeclare our static virtual table variable, we will need its address
in some methods to initialize the virtual table pointer of the returned
interface objects.

Then we implement the interface methods. To match what has been declared in the
header file they must take a pointer to a IDirect3D structure and we must cast
it to an _IDirect3D so that we can manipulate the fields. This is performed by
the ICOM_THIS macro.

Finally we initialize the virtual table.

-------------------------------------------------------------------------------
Prev                                  Home                                 Next
Using Binary OLE                       Up                       Wine and OpenGL
components in Wine                                                             
