mirror of
git://source.winehq.org/git/wine.git
synced 2024-10-31 10:41:12 +00:00
398 lines
17 KiB
Text
398 lines
17 KiB
Text
|
<chapter id="ole">
|
||
|
<title>COM/OLE in Wine</title>
|
||
|
|
||
|
<sect1 id="ole-architecture">
|
||
|
<title>COM/OLE Architecture in Wine</title>
|
||
|
|
||
|
<para>
|
||
|
The section goes into detail about how COM/OLE2 are
|
||
|
implemented in Wine.
|
||
|
</para>
|
||
|
</sect1>
|
||
|
|
||
|
<sect1 id="ole-binary">
|
||
|
<title>Using Binary OLE components in Wine</title>
|
||
|
<para>
|
||
|
This section describes how to import pre-compiled COM/OLE
|
||
|
components...
|
||
|
</para>
|
||
|
</sect1>
|
||
|
|
||
|
<sect1 id="com-writing">
|
||
|
<title>Writing OLE Components for Wine</title>
|
||
|
|
||
|
<para>
|
||
|
Based on the comments in <filename>wine/include/wine/obj_base.h</filename>.
|
||
|
</para>
|
||
|
<para>
|
||
|
This section describes how to create your own natively
|
||
|
compiled COM/OLE components.
|
||
|
</para>
|
||
|
|
||
|
<sect2>
|
||
|
<title>Macros to define a COM interface</title>
|
||
|
|
||
|
<para>
|
||
|
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.
|
||
|
</para>
|
||
|
<para>
|
||
|
It is based on the following assumptions:
|
||
|
</para>
|
||
|
<itemizedlist>
|
||
|
<listitem>
|
||
|
<para>
|
||
|
all COM interfaces derive from IUnknown, this should not
|
||
|
be a problem.
|
||
|
</para>
|
||
|
</listitem>
|
||
|
<listitem>
|
||
|
<para>
|
||
|
the header file only defines the interface, the actual
|
||
|
fields are defined separately in the C file implementing
|
||
|
the interface.
|
||
|
</para>
|
||
|
</listitem>
|
||
|
</itemizedlist>
|
||
|
<para>
|
||
|
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.
|
||
|
</para>
|
||
|
<para>
|
||
|
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.
|
||
|
</para>
|
||
|
<para>
|
||
|
Let's take Direct3D as an example:
|
||
|
</para>
|
||
|
<programlisting>#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</programlisting>
|
||
|
<para>
|
||
|
Comments:
|
||
|
</para>
|
||
|
<para>
|
||
|
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'.
|
||
|
</para>
|
||
|
<para>
|
||
|
ICOM_METHODS defines the methods specific to this
|
||
|
interface. It is then aggregated with the inherited methods
|
||
|
to form ICOM_IMETHODS.
|
||
|
</para>
|
||
|
<para>
|
||
|
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).
|
||
|
</para>
|
||
|
<para>
|
||
|
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.
|
||
|
</para>
|
||
|
<para>
|
||
|
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.
|
||
|
</para>
|
||
|
<para>
|
||
|
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.
|
||
|
</para>
|
||
|
<para>
|
||
|
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.
|
||
|
</para>
|
||
|
<para>
|
||
|
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.
|
||
|
</para>
|
||
|
</sect2>
|
||
|
|
||
|
<sect2>
|
||
|
<title>Bindings in C</title>
|
||
|
|
||
|
<para>
|
||
|
In C this gives:
|
||
|
</para>
|
||
|
<programlisting>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</programlisting>
|
||
|
<para>
|
||
|
Comments:
|
||
|
</para>
|
||
|
<para>
|
||
|
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.
|
||
|
</para>
|
||
|
<para>
|
||
|
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.
|
||
|
</para>
|
||
|
<para>
|
||
|
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.
|
||
|
</para>
|
||
|
<para>
|
||
|
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.
|
||
|
</para>
|
||
|
<para>
|
||
|
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.
|
||
|
</para>
|
||
|
</sect2>
|
||
|
|
||
|
<sect2>
|
||
|
<title>Bindings in C++</title>
|
||
|
<para>
|
||
|
And in C++ (with gcc's g++):
|
||
|
</para>
|
||
|
<programlisting>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); };
|
||
|
};</programlisting>
|
||
|
<para>
|
||
|
Comments:
|
||
|
</para>
|
||
|
<para>
|
||
|
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.
|
||
|
</para>
|
||
|
<para>
|
||
|
Of course in C++ we use inheritance so that we don't have to
|
||
|
duplicate the method definitions.
|
||
|
</para>
|
||
|
<para>
|
||
|
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).
|
||
|
</para>
|
||
|
<para>
|
||
|
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.
|
||
|
</para>
|
||
|
<para>
|
||
|
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.
|
||
|
</para>
|
||
|
<para>
|
||
|
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.
|
||
|
</para>
|
||
|
</sect2>
|
||
|
|
||
|
<sect2>
|
||
|
<title>Implementing a COM interface.</title>
|
||
|
|
||
|
<para>
|
||
|
This continues the above example. This example assumes that
|
||
|
the implementation is in C.
|
||
|
</para>
|
||
|
<programlisting>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
|
||
|
};</programlisting>
|
||
|
<para>
|
||
|
Comments:
|
||
|
</para>
|
||
|
<para>
|
||
|
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.
|
||
|
</para>
|
||
|
<para>
|
||
|
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.
|
||
|
</para>
|
||
|
<para>
|
||
|
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.
|
||
|
</para>
|
||
|
<para>
|
||
|
Finally we initialize the virtual table.
|
||
|
</para>
|
||
|
</sect2>
|
||
|
</sect1>
|
||
|
</chapter>
|
||
|
|
||
|
<!-- Keep this comment at the end of the file
|
||
|
Local variables:
|
||
|
mode: sgml
|
||
|
sgml-parent-document:("wine-doc.sgml" "set" "book" "part" "chapter" "")
|
||
|
End:
|
||
|
-->
|