| <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 (that is a COM interface that is | |
| implemented on the remote computer), 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 | |
| to 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> | |
| currently 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 | |
| Microsoft's. 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 and 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 feed 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 Microsoft's, 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 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 of | |
| 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 forced 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: | |
| --> |