880 lines
40 KiB
Plaintext
880 lines
40 KiB
Plaintext
<chapter id="ole">
|
|
<title>COM in Wine</title>
|
|
|
|
<sect1 id="com-writing">
|
|
<title>Writing COM Components for Wine</title>
|
|
|
|
<para>
|
|
This section describes how to create your own natively
|
|
compiled COM 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 work arounds 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 in-line 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 preferred 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 dereference 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 method 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>
|
|
|
|
<sect1 id="dcom-1">
|
|
<title>A brief introduction to DCOM in Wine</title>
|
|
|
|
<para>
|
|
This section explains the basic principles behind DCOM remoting as used by InstallShield and others.
|
|
</para>
|
|
|
|
<sect2>
|
|
<title>BASICS</title>
|
|
|
|
<para>
|
|
The basic idea behind DCOM is to take a COM object and make it location
|
|
transparent. That means you can use it from other threads, processes and
|
|
machines without having to worry about the fact that you can't just
|
|
dereference the interface vtable pointer to call methods on it.
|
|
</para>
|
|
|
|
<para>
|
|
You might be wondering about putting threads next to processes and
|
|
machines in that last paragraph. You can access thread safe objects from
|
|
multiple threads without DCOM normally, right? Why would you need RPC
|
|
magic to do that?
|
|
</para>
|
|
|
|
<para>
|
|
The answer is of course that COM doesn't assume that objects actually
|
|
are thread-safe. Most real-world objects aren't, in fact, for various
|
|
reasons. What these reasons are isn't too important here, though, it's
|
|
just important to realize that the problem of thread-unsafe objects is
|
|
what COM tries hard to solve with its apartment model. There are also
|
|
ways to tell COM that your object is truly thread-safe (namely the
|
|
free-threaded marshaller). In general, no object is truly thread-safe if
|
|
it could potentially use another not so thread-safe object, though, so
|
|
the free-threaded marshaller is less used than you'd think.
|
|
</para>
|
|
|
|
<para>
|
|
For now, suffice it to say that COM lets you "marshal" interfaces into
|
|
other "apartments". An apartment (you may see it referred to as a
|
|
context in modern versions of COM) can be thought of as a location, and
|
|
contains objects.
|
|
</para>
|
|
|
|
<para>
|
|
Every thread in a program that uses COM exists in an apartment. If a
|
|
thread wishes to use an object from another apartment, marshalling and
|
|
the whole DCOM infrastructure gets involved to make that happen behind
|
|
the scenes.
|
|
</para>
|
|
|
|
<para>
|
|
So. Each COM object resides in an apartment, and each apartment
|
|
resides in a process, and each process resides in a machine, and each
|
|
machine resides in a network. Allowing those objects to be used
|
|
from <emphasis>any</emphasis> of these different places is what DCOM
|
|
is all about.
|
|
</para>
|
|
|
|
<para>
|
|
The process of marshalling refers to taking a function call in an
|
|
apartment and actually performing it in another apartment. Let's say you
|
|
have two machines, A and B, and on machine B there is an object sitting
|
|
in a DLL on the hard disk. You want to create an instance of that object
|
|
(activate it) and use it as if you had compiled it into your own
|
|
program. This is hard, because the remote object is expecting to be
|
|
called by code in its own address space - it may do things like accept
|
|
pointers to linked lists and even return other objects.
|
|
</para>
|
|
|
|
<para>
|
|
Very basic marshalling is easy enough to understand. You take a method
|
|
on a remote interface, copy each of its parameters into a buffer, and
|
|
send it to the remote computer. On the other end, the remote server
|
|
reads each parameter from the buffer, calls the method, writes the
|
|
result into another buffer and sends it back.
|
|
</para>
|
|
|
|
<para>
|
|
The tricky part is exactly how to encode those parameters in the buffer,
|
|
and how to convert standard stdcall/cdecl method calls to network
|
|
packets and back again. This is the job of the RPCRT4.DLL file - or the
|
|
Remote Procedure Call Runtime.
|
|
</para>
|
|
|
|
<para>
|
|
The backbone of DCOM is this RPC runtime, which is an implementation
|
|
of <ulink
|
|
url="http://www.opengroup.org/onlinepubs/009629399/toc.htm">DCE
|
|
RPC</ulink>. DCE RPC is not naturally object oriented, so this
|
|
protocol is extended with some new constructs and by assigning new
|
|
meanings to some of the packet fields, to produce ORPC or Object
|
|
RPC. You might see it called MS-RPC as well.
|
|
</para>
|
|
|
|
<para>
|
|
RPC packets contain a buffer containing marshalled data in NDR format.
|
|
NDR is short for "Network Data Representation" and is similar the XDR
|
|
format used in SunRPC (the closest native equivalent on Linux to DCE
|
|
RPC). NDR/XDR are all based on the idea of graph serialization and were
|
|
worked out during the 80s, meaning they are very powerful and can do
|
|
things like marshal doubly linked lists and other rather tricky
|
|
structures.
|
|
</para>
|
|
|
|
<para>
|
|
In Wine, our DCOM implementation is <emphasis>not</emphasis> based on the
|
|
RPC runtime, as while few programs use DCOM even fewer use
|
|
RPC directly so it was developed some time after
|
|
OLE32/OLEAUT32 were. Eventually this will have to be fixed,
|
|
otherwise our DCOM will never be compatible with
|
|
Microsofts. Bear this in mind as you read through the code
|
|
however.
|
|
</para>
|
|
</sect2>
|
|
|
|
<sect2>
|
|
<title>PROXIES AND STUBS</title>
|
|
|
|
<para>
|
|
Manually marshalling and unmarshalling each method call using the NDR
|
|
APIs (NdrConformantArrayMarshall etc) is very tedious work, so the
|
|
Platform SDK ships with a tool called "midl" which is an IDL compiler.
|
|
IDL or the "Interface Definition Language" is a tool designed
|
|
specifically for describing interfaces in a reasonably language neutral
|
|
fashion, though in reality it bears a close resemblence to C++.
|
|
</para>
|
|
|
|
<para>
|
|
By describing the functions you want to expose via RPC in IDL therefore,
|
|
it becomes possible to pass this file to MIDL which spits out a huge
|
|
amount of C source code. That code defines functions which have the same
|
|
prototype as the functions described in your IDL but which internally
|
|
take each argument, marshal it using Ndr, send the packet, and unmarshal
|
|
the return.
|
|
</para>
|
|
|
|
<para>
|
|
Because this code proxies the code from the client to the server, the
|
|
functions are called proxies. Easy, right?
|
|
</para>
|
|
|
|
<para>
|
|
Of course, in the RPC server process at the other end, you need some way
|
|
to unmarshal the RPCs, so you have functions also generated by MIDL
|
|
which are the inverse of the proxies: they accept an NDR buffer, extract
|
|
the parameters, call the real function then marshal the result back.
|
|
They are called stubs, and stand in for the real calling code in the
|
|
client process.
|
|
</para>
|
|
|
|
<para>
|
|
The sort of marshalling/unmarshalling code that MIDL spits out can be
|
|
seen in dlls/oleaut32/oaidl_p.c - it's not exactly what it would look
|
|
like as that file contains DCOM proxies/stubs which are different, but
|
|
you get the idea. Proxy functions take the arguments and feel them to
|
|
the NDR marshallers (or picklers), invoke an NdrProxySendReceive and
|
|
then convert the out parameters and return code. There's a ton of goop
|
|
in there for dealing with buffer allocation, exceptions and so on - it's
|
|
really ugly code. But, this is the basic concept behind DCE RPC.
|
|
</para>
|
|
</sect2>
|
|
|
|
<sect2>
|
|
<title>INTERFACE MARSHALLING</title>
|
|
|
|
<para>
|
|
Standard NDR only knows about C style function calls - they
|
|
can accept and even return structures, but it has no concept
|
|
of COM interfaces. Confusingly DCE RPC <emphasis>does</emphasis> have a
|
|
concept of RPC interfaces which are just convenient ways to
|
|
bundle function calls together into namespaces, but let's
|
|
ignore that for now as it just muddies the water. The
|
|
primary extension made by Microsoft to NDR then was the
|
|
ability to take a COM interface pointer and marshal that
|
|
into the NDR stream.
|
|
</para>
|
|
|
|
<para>
|
|
The basic theory of proxies and stubs and IDL is still here, but it's
|
|
been modified slightly. Whereas before you could define a bunch of
|
|
functions in IDL, now a new "object" keyword has appeared. This tells
|
|
MIDL that you're describing a COM interface, and as a result the
|
|
proxies/stubs it generates are also COM objects.
|
|
</para>
|
|
|
|
<para>
|
|
That's a very important distinction. When you make a call to a remote
|
|
COM object you do it via a proxy object that COM has constructed on the
|
|
fly. Likewise, a stub object on the remote end unpacks the RPC packet
|
|
and makes the call.
|
|
</para>
|
|
|
|
<para>
|
|
Because this is object-oriented RPC, there are a few complications: for
|
|
instance, a call that goes via the same proxies/stubs may end up at a
|
|
different object instance, so the RPC runtime keeps track of "this" and
|
|
"that" in the RPC packets.
|
|
</para>
|
|
|
|
<para>
|
|
This leads naturally onto the question of how we got those proxy/stub
|
|
objects in the first place, and where they came from. You can use the
|
|
CoCreateInstanceEx API to activate COM objects on a remote machine, this
|
|
works like CoCreateInstance API. Behind the scenes, a lot of stuff is
|
|
involved to do this (like IRemoteActivation, IOXIDResolver and so on)
|
|
but let's gloss over that for now.
|
|
</para>
|
|
|
|
<para>
|
|
When DCOM creates an object on a remote machine, the DCOM runtime on
|
|
that machine activates the object in the usual way (by looking it up in
|
|
the registry etc) and then marshals the requested interface back to the
|
|
client. Marshalling an interface takes a pointer, and produces a buffer
|
|
containing all the information DCOM needs to construct a proxy object in
|
|
the client, a stub object in the server and link the two together.
|
|
</para>
|
|
|
|
<para>
|
|
The structure of a marshalled interface pointer is somewhat complex.
|
|
Let's ignore that too. The important thing is how COM proxies/stubs are
|
|
loaded.
|
|
</para>
|
|
</sect2>
|
|
|
|
<sect2>
|
|
<title>COM PROXY/STUB SYSTEM</title>
|
|
|
|
<para>
|
|
COM proxies are objects that implement both the interfaces needing to be
|
|
proxied and also IRpcProxyBuffer. Likewise, COM stubs implement
|
|
IRpcStubBuffer and understand how to invoke the methods of the requested
|
|
interface.
|
|
</para>
|
|
|
|
<para>
|
|
You may be wondering what the word "buffer" is doing in those interface
|
|
names. I'm not sure either, except that a running theme in DCOM is that
|
|
interfaces which have nothing to do with buffers have the word Buffer
|
|
appended to them, seemingly at random. Ignore it and <emphasis>don't let it
|
|
confuse you</emphasis>
|
|
:) This stuff is convoluted enough ...
|
|
</para>
|
|
|
|
<para>
|
|
The IRpc[Proxy/Stub]Buffer interfaces are used to control the proxy/stub
|
|
objects and are one of the many semi-public interfaces used in DCOM.
|
|
</para>
|
|
|
|
<para>
|
|
DCOM is theoretically an internet RFC <ulink
|
|
url="http://www.grimes.demon.co.uk/DCOM/DCOMSpec.htm">[2]</ulink> and is
|
|
specced out, but in reality the only implementation of it apart from
|
|
ours is Microsofts, and as a result there are lots of interfaces
|
|
which <emphasis>can</emphasis> be used if you want to customize or
|
|
control DCOM but in practice are badly documented or not documented at
|
|
all, or exist mostly as interfaces between MIDL generated code and COM
|
|
itself. Don't pay too much attention to the MSDN definitions of these
|
|
interfaces and APIs.
|
|
</para>
|
|
|
|
<para>
|
|
COM proxies and stubs are like any other normal COM object - they are
|
|
registered in the registry, they can be loaded with CoCreateInstance and
|
|
so on. They have to be in process (in DLLs) however. They aren't
|
|
activated directly by COM however, instead the process goes something
|
|
like this:
|
|
|
|
<itemizedlist>
|
|
<listitem> <para> COM receives a marshalled interface packet, and retrieves the IID of
|
|
the marshalled interface from it </para> </listitem>
|
|
|
|
|
|
<listitem> <para> COM looks in
|
|
HKEY_CLASSES_ROOT/Interface/{whatever-iid}/ProxyStubClsId32
|
|
to retrieve the CLSID of another COM object, which
|
|
implements IPSFactoryBuffer. </para> </listitem>
|
|
|
|
<listitem> <para> IPSFactoryBuffer has only two methods, CreateProxy and CreateStub. COM
|
|
calls whichever is appropriate: CreateStub for the server, CreateProxy
|
|
for the client. MIDL will normally provide an implementation of this
|
|
object for you in the code it generates. </para></listitem>
|
|
</itemizedlist>
|
|
|
|
</para>
|
|
|
|
<para>
|
|
Once CreateProxy has been called, the resultant object is QueryInterfaced to
|
|
IRpcProxyBuffer, which only has 1 method, IRpcProxyBuffer::Connect.
|
|
This method only takes one parameter, the IRpcChannelBuffer object which
|
|
encapsulates the "RPC Channel" between the client and server.
|
|
</para>
|
|
|
|
<para>
|
|
On the server side, a similar process is performed - the PSFactoryBuffer
|
|
is created, CreateStub is called, result is QId to IRpcStubBuffer, and
|
|
IRpcStubBuffer::Connect is used to link it to the RPC channel.
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
<sect2>
|
|
<title>RPC CHANNELS</title>
|
|
|
|
<para>
|
|
Remember the RPC runtime? Well, that's not just responsible for
|
|
marshalling stuff, it also controls the connection and protocols between
|
|
the client and server. We can ignore the details of this for now,
|
|
suffice it to say that an RPC Channel is a COM object that implements
|
|
IRpcChannelBuffer, and it's basically an abstraction of different RPC
|
|
methods. For instance, in the case of inter-thread marshalling (not
|
|
covered here) the RPC connection code isn't used, only the NDR
|
|
marshallers are, so IRpcChannelBuffer in that case isn't actually
|
|
implemented by RPCRT4 but rather just by the COM/OLE DLLS.
|
|
</para>
|
|
|
|
<para>
|
|
On this topic, Ove Kaaven says: It depends on the Windows version, I
|
|
think. Windows 95 and Windows NT 4 certainly had very different models
|
|
when I looked. I'm pretty sure the Windows 98 version of RPCRT4 was
|
|
able to dispatch messages directly to individual apartments. I'd be
|
|
surprised if some similar functionality was not added to Windows
|
|
2000. After all, if an object on machine A wanted to use an object on
|
|
machine B in an apartment C, wouldn't it be most efficient if the RPC
|
|
system knew about apartments and could dispatch the message directly
|
|
to it? And if RPC does know how to efficiently dispatch to apartments,
|
|
why should COM duplicate this functionality? There were, however, no
|
|
unified way to tell RPC about them across Windows versions, so in that
|
|
old patch of mine, I let the COM/OLE dlls do the apartment dispatch,
|
|
but even then, the RPC runtime was always involved. After all, it
|
|
could be quite tricky to tell whether the call is merely interthread,
|
|
without involving the RPC runtime...
|
|
</para>
|
|
|
|
<para>
|
|
RPC channels are constructed on the fly by DCOM as part of the
|
|
marshalling process. So, when you make a call on a COM proxy, it goes
|
|
like this:
|
|
</para>
|
|
|
|
<para>
|
|
Your code -> COM proxy object -> RPC Channel -> COM stub object -> Their code
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
<sect2>
|
|
<title>HOW THIS ACTUALLY WORKS IN WINE</title>
|
|
|
|
<para>
|
|
Right now, Wine does not use the NDR marshallers or RPC to implement its
|
|
DCOM. When you marshal an interface in Wine, in the server process a
|
|
_StubMgrThread thread is started. I haven't gone into the stub manager
|
|
here. The important thing is that eventually a _StubReaderThread is
|
|
started which accepts marshalled DCOM RPCs, and then passes them to
|
|
IRpcStubBuffer::Invoke on the correct stub object which in turn
|
|
demarshals the packet and performs the call. The threads started by our
|
|
implementation of DCOM are never terminated, they just hang around until
|
|
the process dies.
|
|
</para>
|
|
|
|
<para>
|
|
Remember that I said our DCOM doesn't use RPC? Well, you might be
|
|
thinking "but we use IRpcStubBuffer like we're supposed to ... isn't
|
|
that provided by MIDL which generates code that uses the NDR APIs?". If
|
|
so pat yourself on the back, you're still with me. Go get a cup of
|
|
coffee.
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
<sect2>
|
|
<title>TYPELIB MARSHALLER</title>
|
|
|
|
<para>
|
|
In fact, the reason for the PSFactoryBuffer layer of indirection is
|
|
because you not all interfaces are marshalled using MIDL generated code.
|
|
Why not? Well, to understand <emphasis>that</emphasis>
|
|
you have to see that one of the
|
|
driving forces behind OLE and by extension DCOM was the development
|
|
Visual Basic. Microsoft wanted VB developers to be first class citizens
|
|
in the COM world, but things like writing IDL and compiling them with a
|
|
C compiler into DLLs wasn't easy enough.
|
|
</para>
|
|
|
|
<para>
|
|
So, type libraries were invented. Actually they were invented as part of
|
|
a parallel line of COM development known as "OLE Automation", but let's
|
|
not get into that here. Type libraries are basically binary IDL files,
|
|
except that despite there being two type library formats neither of them
|
|
can fully express everything expressable in IDL. Anyway, with a type
|
|
library (which can be embedded as a resource into a DLL) you have
|
|
another option beyond compiling MIDL output - you can set the
|
|
ProxyStubClsId32 registry entry for your interfaces to the CLSID of the
|
|
"type library marshaller" or "universal marshaller". Both terms are
|
|
used, but in the Wine source it's called the typelib marshaller.
|
|
</para>
|
|
|
|
<para>
|
|
The type library marshaller constructs proxy and stub objects on the
|
|
fly. It does so by having generic marshalling glue which reads the
|
|
information from the type libraries, and takes the parameters directly
|
|
off the stack. The CreateProxy method actually builds a vtable out of
|
|
blocks of assembly stitched together which pass control to _xCall, which
|
|
then does the marshalling. You can see all this magic in
|
|
dlls/oleaut32/tmarshal.c
|
|
</para>
|
|
|
|
<para>
|
|
In the case of InstallShield, it actually comes with typelibs for all
|
|
the interfaces it needs to marshal (fixme: is this right?), but they
|
|
actually use a mix of MIDL and typelib marshalling. In order to cover up
|
|
for the fact that we don't really use RPC they're all force to go via
|
|
the typelib marshaller - that's what the 1 || hack is for and what the
|
|
"Registering non-automation type library!" warning is about (I think).
|
|
</para>
|
|
</sect2>
|
|
|
|
<sect2>
|
|
<title>WRAPUP</title>
|
|
|
|
<para>
|
|
OK, so there are some (very) basic notes on DCOM. There's a ton of stuff
|
|
I have not covered:
|
|
</para>
|
|
|
|
<itemizedlist>
|
|
<listitem><para> Format strings/MOPs</para></listitem>
|
|
|
|
<listitem><para> Apartments, threading models, inter-thread marshalling</para></listitem>
|
|
|
|
<listitem><para> OXIDs/OIDs, etc, IOXIDResolver</para></listitem>
|
|
|
|
<listitem><para> IRemoteActivation</para></listitem>
|
|
|
|
<listitem><para> Complex/simple pings, distributed garbage collection</para></listitem>
|
|
|
|
<listitem><para> Marshalling IDispatch</para></listitem>
|
|
|
|
<listitem><para> Structure of marshalled interface pointers (STDOBJREFs etc)</para></listitem>
|
|
|
|
<listitem><para> Runtime class object registration (CoRegisterClassObject), ROT</para></listitem>
|
|
|
|
<listitem><para> IRemUnknown</para></listitem>
|
|
|
|
<listitem><para> Exactly how InstallShield uses DCOM</para></listitem>
|
|
</itemizedlist>
|
|
|
|
<para>
|
|
Then there's a bunch of stuff I still don't understand, like ICallFrame,
|
|
interface pointer swizzling, exactly where and how all this stuff is
|
|
actually implemented and so on.
|
|
</para>
|
|
|
|
<para>
|
|
But for now that's enough.
|
|
</para>
|
|
</sect2>
|
|
|
|
<sect2>
|
|
<title>FURTHER READING</title>
|
|
|
|
<para>
|
|
Most of these documents assume you have knowledge only contained in
|
|
other documents. You may have to reread them a few times for it all to
|
|
make sense. Don't feel you need to read these to understand DCOM, you
|
|
don't, you only need to look at them if you're planning to help
|
|
implement it.
|
|
</para>
|
|
|
|
<itemizedlist>
|
|
<listitem><para>
|
|
<ulink url="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/com/htm/cmi_n2p_459u.asp">
|
|
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/com/htm/cmi_n2p_459u.asp</ulink>
|
|
|
|
</para></listitem>
|
|
|
|
|
|
<listitem><para>
|
|
<ulink url="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/com/htm/cmi_q2z_5ygi.asp">
|
|
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/com/htm/cmi_q2z_5ygi.asp</ulink>
|
|
</para></listitem>
|
|
|
|
|
|
<listitem><para>
|
|
<ulink url="http://www.microsoft.com/msj/0398/dcom.aspx">
|
|
http://www.microsoft.com/msj/0398/dcom.aspx</ulink>
|
|
</para></listitem>
|
|
|
|
<listitem><para>
|
|
<ulink url="http://www.microsoft.com/ntserver/techresources/appserv/COM/DCOM/4_ConnectionMgmt.asp">
|
|
http://www.microsoft.com/ntserver/techresources/appserv/COM/DCOM/4_ConnectionMgmt.asp</ulink>
|
|
</para></listitem>
|
|
|
|
|
|
<listitem><para><ulink url="http://www.idevresource.com/com/library/articles/comonlinux.asp">
|
|
http://www.idevresource.com/com/library/articles/comonlinux.asp</ulink>
|
|
|
|
(unfortunately part 2 of this article does not seem to exist anymore, if it was ever written)</para></listitem>
|
|
</itemizedlist>
|
|
</sect2>
|
|
</sect1>
|
|
</chapter>
|
|
|
|
<!-- Keep this comment at the end of the file
|
|
Local variables:
|
|
mode: sgml
|
|
sgml-parent-document:("wine-devel.sgml" "set" "book" "part" "chapter" "")
|
|
End:
|
|
-->
|