John R. Sheets | d9e064f | 2000-12-13 21:52:37 +0000 | [diff] [blame] | 1 | <chapter id="ole"> |
| 2 | <title>COM/OLE in Wine</title> |
| 3 | |
| 4 | <sect1 id="ole-architecture"> |
| 5 | <title>COM/OLE Architecture in Wine</title> |
| 6 | |
| 7 | <para> |
| 8 | The section goes into detail about how COM/OLE2 are |
| 9 | implemented in Wine. |
| 10 | </para> |
| 11 | </sect1> |
| 12 | |
| 13 | <sect1 id="ole-binary"> |
| 14 | <title>Using Binary OLE components in Wine</title> |
| 15 | <para> |
| 16 | This section describes how to import pre-compiled COM/OLE |
| 17 | components... |
| 18 | </para> |
| 19 | </sect1> |
| 20 | |
| 21 | <sect1 id="com-writing"> |
| 22 | <title>Writing OLE Components for Wine</title> |
| 23 | |
| 24 | <para> |
| 25 | Based on the comments in <filename>wine/include/wine/obj_base.h</filename>. |
| 26 | </para> |
| 27 | <para> |
| 28 | This section describes how to create your own natively |
| 29 | compiled COM/OLE components. |
| 30 | </para> |
| 31 | |
| 32 | <sect2> |
| 33 | <title>Macros to define a COM interface</title> |
| 34 | |
| 35 | <para> |
| 36 | The goal of the following set of definitions is to provide a |
| 37 | way to use the same header file definitions to provide both |
| 38 | a C interface and a C++ object oriented interface to COM |
| 39 | interfaces. The type of interface is selected automatically |
| 40 | depending on the language but it is always possible to get |
| 41 | the C interface in C++ by defining CINTERFACE. |
| 42 | </para> |
| 43 | <para> |
| 44 | It is based on the following assumptions: |
| 45 | </para> |
| 46 | <itemizedlist> |
| 47 | <listitem> |
| 48 | <para> |
| 49 | all COM interfaces derive from IUnknown, this should not |
| 50 | be a problem. |
| 51 | </para> |
| 52 | </listitem> |
| 53 | <listitem> |
| 54 | <para> |
| 55 | the header file only defines the interface, the actual |
| 56 | fields are defined separately in the C file implementing |
| 57 | the interface. |
| 58 | </para> |
| 59 | </listitem> |
| 60 | </itemizedlist> |
| 61 | <para> |
| 62 | The natural approach to this problem would be to make sure |
| 63 | we get a C++ class and virtual methods in C++ and a |
| 64 | structure with a table of pointer to functions in C. |
| 65 | Unfortunately the layout of the virtual table is compiler |
| 66 | specific, the layout of g++ virtual tables is not the same |
| 67 | as that of an egcs virtual table which is not the same as |
| 68 | that generated by Visual C+. There are workarounds to make |
| 69 | the virtual tables compatible via padding but unfortunately |
| 70 | the one which is imposed to the WINE emulator by the Windows |
| 71 | binaries, i.e. the Visual C++ one, is the most compact of |
| 72 | all. |
| 73 | </para> |
| 74 | <para> |
| 75 | So the solution I finally adopted does not use virtual |
| 76 | tables. Instead I use inline non virtual methods that |
| 77 | dereference the method pointer themselves and perform the |
| 78 | call. |
| 79 | </para> |
| 80 | <para> |
| 81 | Let's take Direct3D as an example: |
| 82 | </para> |
| 83 | <programlisting>#define ICOM_INTERFACE IDirect3D |
| 84 | #define IDirect3D_METHODS \ |
| 85 | ICOM_METHOD1(HRESULT,Initialize, REFIID,) \ |
| 86 | ICOM_METHOD2(HRESULT,EnumDevices, LPD3DENUMDEVICESCALLBACK,, LPVOID,) \ |
| 87 | ICOM_METHOD2(HRESULT,CreateLight, LPDIRECT3DLIGHT*,, IUnknown*,) \ |
| 88 | ICOM_METHOD2(HRESULT,CreateMaterial,LPDIRECT3DMATERIAL*,, IUnknown*,) \ |
| 89 | ICOM_METHOD2(HRESULT,CreateViewport,LPDIRECT3DVIEWPORT*,, IUnknown*,) \ |
| 90 | ICOM_METHOD2(HRESULT,FindDevice, LPD3DFINDDEVICESEARCH,, LPD3DFINDDEVICERESULT,) |
| 91 | #define IDirect3D_IMETHODS \ |
| 92 | IUnknown_IMETHODS \ |
| 93 | IDirect3D_METHODS |
| 94 | ICOM_DEFINE(IDirect3D,IUnknown) |
| 95 | #undef ICOM_INTERFACE |
| 96 | |
| 97 | #ifdef ICOM_CINTERFACE |
| 98 | // *** IUnknown methods *** // |
| 99 | #define IDirect3D_QueryInterface(p,a,b) ICOM_CALL2(QueryInterface,p,a,b) |
| 100 | #define IDirect3D_AddRef(p) ICOM_CALL (AddRef,p) |
| 101 | #define IDirect3D_Release(p) ICOM_CALL (Release,p) |
| 102 | // *** IDirect3D methods *** // |
| 103 | #define IDirect3D_Initialize(p,a) ICOM_CALL1(Initialize,p,a) |
| 104 | #define IDirect3D_EnumDevices(p,a,b) ICOM_CALL2(EnumDevice,p,a,b) |
| 105 | #define IDirect3D_CreateLight(p,a,b) ICOM_CALL2(CreateLight,p,a,b) |
| 106 | #define IDirect3D_CreateMaterial(p,a,b) ICOM_CALL2(CreateMaterial,p,a,b) |
| 107 | #define IDirect3D_CreateViewport(p,a,b) ICOM_CALL2(CreateViewport,p,a,b) |
| 108 | #define IDirect3D_FindDevice(p,a,b) ICOM_CALL2(FindDevice,p,a,b) |
| 109 | #endif</programlisting> |
| 110 | <para> |
| 111 | Comments: |
| 112 | </para> |
| 113 | <para> |
| 114 | The ICOM_INTERFACE macro is used in the ICOM_METHOD macros |
| 115 | to define the type of the 'this' pointer. Defining this |
| 116 | macro here saves us the trouble of having to repeat the |
| 117 | interface name everywhere. Note however that because of the |
| 118 | way macros work, a macro like ICOM_METHOD1 cannot use |
| 119 | 'ICOM_INTERFACE##_VTABLE' because this would give |
| 120 | 'ICOM_INTERFACE_VTABLE' and not 'IDirect3D_VTABLE'. |
| 121 | </para> |
| 122 | <para> |
| 123 | ICOM_METHODS defines the methods specific to this |
| 124 | interface. It is then aggregated with the inherited methods |
| 125 | to form ICOM_IMETHODS. |
| 126 | </para> |
| 127 | <para> |
| 128 | ICOM_IMETHODS defines the list of methods that are |
| 129 | inheritable from this interface. It must be written manually |
| 130 | (rather than using a macro to generate the equivalent code) |
| 131 | to avoid macro recursion (which compilers don't like). |
| 132 | </para> |
| 133 | <para> |
| 134 | The ICOM_DEFINE finally declares all the structures |
| 135 | necessary for the interface. We have to explicitly use the |
| 136 | interface name for macro expansion reasons again. Inherited |
| 137 | methods are inherited in C by using the IDirect3D_METHODS |
| 138 | macro and the parent's Xxx_IMETHODS macro. In C++ we need |
| 139 | only use the IDirect3D_METHODS since method inheritance is |
| 140 | taken care of by the language. |
| 141 | </para> |
| 142 | <para> |
| 143 | In C++ the ICOM_METHOD macros generate a function prototype |
| 144 | and a call to a function pointer method. This means using |
| 145 | once 't1 p1, t2 p2, ...' and once 'p1, p2' without the |
| 146 | types. The only way I found to handle this is to have one |
| 147 | ICOM_METHOD macro per number of parameters and to have it |
| 148 | take only the type information (with const if necessary) as |
| 149 | parameters. The 'undef ICOM_INTERFACE' is here to remind |
| 150 | you that using ICOM_INTERFACE in the following macros will |
| 151 | not work. This time it's because the ICOM_CALL macro |
| 152 | expansion is done only once the 'IDirect3D_Xxx' macro is |
| 153 | expanded. And by that time ICOM_INTERFACE will be long gone |
| 154 | anyway. |
| 155 | </para> |
| 156 | <para> |
| 157 | You may have noticed the double commas after each parameter |
| 158 | type. This allows you to put the name of that parameter |
| 159 | which I think is good for documentation. It is not required |
| 160 | and since I did not know what to put there for this example |
| 161 | (I could only find doc about IDirect3D2), I left them blank. |
| 162 | </para> |
| 163 | <para> |
| 164 | Finally the set of 'IDirect3D_Xxx' macros is a standard set |
| 165 | of macros defined to ease access to the interface methods in |
| 166 | C. Unfortunately I don't see any way to avoid having to |
| 167 | duplicate the inherited method definitions there. This time |
| 168 | I could have used a trick to use only one macro whatever the |
| 169 | number of parameters but I prefered to have it work the same |
| 170 | way as above. |
| 171 | </para> |
| 172 | <para> |
| 173 | You probably have noticed that we don't define the fields we |
| 174 | need to actually implement this interface: reference count, |
| 175 | pointer to other resources and miscellaneous fields. That's |
| 176 | because these interfaces are just that: interfaces. They may |
| 177 | be implemented more than once, in different contexts and |
| 178 | sometimes not even in Wine. Thus it would not make sense to |
| 179 | impose that the interface contains some specific fields. |
| 180 | </para> |
| 181 | </sect2> |
| 182 | |
| 183 | <sect2> |
| 184 | <title>Bindings in C</title> |
| 185 | |
| 186 | <para> |
| 187 | In C this gives: |
| 188 | </para> |
| 189 | <programlisting>typedef struct IDirect3DVtbl IDirect3DVtbl; |
| 190 | struct IDirect3D { |
| 191 | IDirect3DVtbl* lpVtbl; |
| 192 | }; |
| 193 | struct IDirect3DVtbl { |
| 194 | HRESULT (*fnQueryInterface)(IDirect3D* me, REFIID riid, LPVOID* ppvObj); |
| 195 | ULONG (*fnAddRef)(IDirect3D* me); |
| 196 | ULONG (*fnRelease)(IDirect3D* me); |
| 197 | HRESULT (*fnInitialize)(IDirect3D* me, REFIID a); |
| 198 | HRESULT (*fnEnumDevices)(IDirect3D* me, LPD3DENUMDEVICESCALLBACK a, LPVOID b); |
| 199 | HRESULT (*fnCreateLight)(IDirect3D* me, LPDIRECT3DLIGHT* a, IUnknown* b); |
| 200 | HRESULT (*fnCreateMaterial)(IDirect3D* me, LPDIRECT3DMATERIAL* a, IUnknown* b); |
| 201 | HRESULT (*fnCreateViewport)(IDirect3D* me, LPDIRECT3DVIEWPORT* a, IUnknown* b); |
| 202 | HRESULT (*fnFindDevice)(IDirect3D* me, LPD3DFINDDEVICESEARCH a, LPD3DFINDDEVICERESULT b); |
| 203 | }; |
| 204 | |
| 205 | #ifdef ICOM_CINTERFACE |
| 206 | // *** IUnknown methods *** // |
| 207 | #define IDirect3D_QueryInterface(p,a,b) (p)->lpVtbl->fnQueryInterface(p,a,b) |
| 208 | #define IDirect3D_AddRef(p) (p)->lpVtbl->fnAddRef(p) |
| 209 | #define IDirect3D_Release(p) (p)->lpVtbl->fnRelease(p) |
| 210 | // *** IDirect3D methods *** // |
| 211 | #define IDirect3D_Initialize(p,a) (p)->lpVtbl->fnInitialize(p,a) |
| 212 | #define IDirect3D_EnumDevices(p,a,b) (p)->lpVtbl->fnEnumDevice(p,a,b) |
| 213 | #define IDirect3D_CreateLight(p,a,b) (p)->lpVtbl->fnCreateLight(p,a,b) |
| 214 | #define IDirect3D_CreateMaterial(p,a,b) (p)->lpVtbl->fnCreateMaterial(p,a,b) |
| 215 | #define IDirect3D_CreateViewport(p,a,b) (p)->lpVtbl->fnCreateViewport(p,a,b) |
| 216 | #define IDirect3D_FindDevice(p,a,b) (p)->lpVtbl->fnFindDevice(p,a,b) |
| 217 | #endif</programlisting> |
| 218 | <para> |
| 219 | Comments: |
| 220 | </para> |
| 221 | <para> |
| 222 | IDirect3D only contains a pointer to the IDirect3D |
| 223 | virtual/jump table. This is the only thing the user needs to |
| 224 | know to use the interface. Of course the structure we will |
| 225 | define to implement this interface will have more fields but |
| 226 | the first one will match this pointer. |
| 227 | </para> |
| 228 | <para> |
| 229 | The code generated by ICOM_DEFINE defines both the structure |
| 230 | representing the interface and the structure for the jump |
| 231 | table. ICOM_DEFINE uses the parent's Xxx_IMETHODS macro to |
| 232 | automatically repeat the prototypes of all the inherited |
| 233 | methods and then uses IDirect3D_METHODS to define the |
| 234 | IDirect3D methods. |
| 235 | </para> |
| 236 | <para> |
| 237 | Each method is declared as a pointer to function field in |
| 238 | the jump table. The implementation will fill this jump table |
| 239 | with appropriate values, probably using a static variable, |
| 240 | and initialize the lpVtbl field to point to this variable. |
| 241 | </para> |
| 242 | <para> |
| 243 | The IDirect3D_Xxx macros then just derefence the lpVtbl |
| 244 | pointer and use the function pointer corresponding to the |
| 245 | macro name. This emulates the behavior of a virtual table |
| 246 | and should be just as fast. |
| 247 | </para> |
| 248 | <para> |
| 249 | This C code should be quite compatible with the Windows |
| 250 | headers both for code that uses COM interfaces and for code |
| 251 | implementing a COM interface. |
| 252 | </para> |
| 253 | </sect2> |
| 254 | |
| 255 | <sect2> |
| 256 | <title>Bindings in C++</title> |
| 257 | <para> |
| 258 | And in C++ (with gcc's g++): |
| 259 | </para> |
| 260 | <programlisting>typedef struct IDirect3D: public IUnknown { |
| 261 | private: HRESULT (*fnInitialize)(IDirect3D* me, REFIID a); |
| 262 | public: inline HRESULT Initialize(REFIID a) { return ((IDirect3D*)t.lpVtbl)->fnInitialize(this,a); }; |
| 263 | private: HRESULT (*fnEnumDevices)(IDirect3D* me, LPD3DENUMDEVICESCALLBACK a, LPVOID b); |
| 264 | public: inline HRESULT EnumDevices(LPD3DENUMDEVICESCALLBACK a, LPVOID b) |
| 265 | { return ((IDirect3D*)t.lpVtbl)->fnEnumDevices(this,a,b); }; |
| 266 | private: HRESULT (*fnCreateLight)(IDirect3D* me, LPDIRECT3DLIGHT* a, IUnknown* b); |
| 267 | public: inline HRESULT CreateLight(LPDIRECT3DLIGHT* a, IUnknown* b) |
| 268 | { return ((IDirect3D*)t.lpVtbl)->fnCreateLight(this,a,b); }; |
| 269 | private: HRESULT (*fnCreateMaterial)(IDirect3D* me, LPDIRECT3DMATERIAL* a, IUnknown* b); |
| 270 | public: inline HRESULT CreateMaterial(LPDIRECT3DMATERIAL* a, IUnknown* b) |
| 271 | { return ((IDirect3D*)t.lpVtbl)->fnCreateMaterial(this,a,b); }; |
| 272 | private: HRESULT (*fnCreateViewport)(IDirect3D* me, LPDIRECT3DVIEWPORT* a, IUnknown* b); |
| 273 | public: inline HRESULT CreateViewport(LPDIRECT3DVIEWPORT* a, IUnknown* b) |
| 274 | { return ((IDirect3D*)t.lpVtbl)->fnCreateViewport(this,a,b); }; |
| 275 | private: HRESULT (*fnFindDevice)(IDirect3D* me, LPD3DFINDDEVICESEARCH a, LPD3DFINDDEVICERESULT b); |
| 276 | public: inline HRESULT FindDevice(LPD3DFINDDEVICESEARCH a, LPD3DFINDDEVICERESULT b) |
| 277 | { return ((IDirect3D*)t.lpVtbl)->fnFindDevice(this,a,b); }; |
| 278 | };</programlisting> |
| 279 | <para> |
| 280 | Comments: |
| 281 | </para> |
| 282 | <para> |
| 283 | In C++ IDirect3D does double duty as both the virtual/jump |
| 284 | table and as the interface definition. The reason for this |
| 285 | is to avoid having to duplicate the mehod definitions: once |
| 286 | to have the function pointers in the jump table and once to |
| 287 | have the methods in the interface class. Here one macro can |
| 288 | generate both. This means though that the first pointer, |
| 289 | t.lpVtbl defined in IUnknown, must be interpreted as the |
| 290 | jump table pointer if we interpret the structure as the |
| 291 | interface class, and as the function pointer to the |
| 292 | QueryInterface method, t.fnQueryInterface, if we interpret |
| 293 | the structure as the jump table. Fortunately this gymnastic |
| 294 | is entirely taken care of in the header of IUnknown. |
| 295 | </para> |
| 296 | <para> |
| 297 | Of course in C++ we use inheritance so that we don't have to |
| 298 | duplicate the method definitions. |
| 299 | </para> |
| 300 | <para> |
| 301 | Since IDirect3D does double duty, each ICOM_METHOD macro |
| 302 | defines both a function pointer and a non-virtual inline |
| 303 | method which dereferences it and calls it. This way this |
| 304 | method behaves just like a virtual method but does not |
| 305 | create a true C++ virtual table which would break the |
| 306 | structure layout. If you look at the implementation of these |
| 307 | methods you'll notice that they would not work for void |
| 308 | functions. We have to return something and fortunately this |
| 309 | seems to be what all the COM methods do (otherwise we would |
| 310 | need another set of macros). |
| 311 | </para> |
| 312 | <para> |
| 313 | Note how the ICOM_METHOD generates both function prototypes |
| 314 | mixing types and formal parameter names and the method |
| 315 | invocation using only the formal parameter name. This is the |
| 316 | reason why we need different macros to handle different |
| 317 | numbers of parameters. |
| 318 | </para> |
| 319 | <para> |
| 320 | Finally there is no IDirect3D_Xxx macro. These are not |
| 321 | needed in C++ unless the CINTERFACE macro is defined in |
| 322 | which case we would not be here. |
| 323 | </para> |
| 324 | <para> |
| 325 | This C++ code works well for code that just uses COM |
| 326 | interfaces. But it will not work with C++ code implement a |
| 327 | COM interface. That's because such code assumes the |
| 328 | interface methods are declared as virtual C++ methods which |
| 329 | is not the case here. |
| 330 | </para> |
| 331 | </sect2> |
| 332 | |
| 333 | <sect2> |
| 334 | <title>Implementing a COM interface.</title> |
| 335 | |
| 336 | <para> |
| 337 | This continues the above example. This example assumes that |
| 338 | the implementation is in C. |
| 339 | </para> |
| 340 | <programlisting>typedef struct _IDirect3D { |
| 341 | void* lpVtbl; |
| 342 | // ... |
| 343 | } _IDirect3D; |
| 344 | |
| 345 | static ICOM_VTABLE(IDirect3D) d3dvt; |
| 346 | |
| 347 | // implement the IDirect3D methods here |
| 348 | |
| 349 | int IDirect3D_fnQueryInterface(IDirect3D* me) |
| 350 | { |
| 351 | ICOM_THIS(IDirect3D,me); |
| 352 | // ... |
| 353 | } |
| 354 | |
| 355 | // ... |
| 356 | |
| 357 | static ICOM_VTABLE(IDirect3D) d3dvt = { |
| 358 | ICOM_MSVTABLE_COMPAT_DummyRTTIVALUE |
| 359 | IDirect3D_fnQueryInterface, |
| 360 | IDirect3D_fnAdd, |
| 361 | IDirect3D_fnAdd2, |
| 362 | IDirect3D_fnInitialize, |
| 363 | IDirect3D_fnSetWidth |
| 364 | };</programlisting> |
| 365 | <para> |
| 366 | Comments: |
| 367 | </para> |
| 368 | <para> |
| 369 | We first define what the interface really contains. This is |
| 370 | the _IDirect3D structure. The first field must of course be |
| 371 | the virtual table pointer. Everything else is free. |
| 372 | </para> |
| 373 | <para> |
| 374 | Then we predeclare our static virtual table variable, we |
| 375 | will need its address in some methods to initialize the |
| 376 | virtual table pointer of the returned interface objects. |
| 377 | </para> |
| 378 | <para> |
| 379 | Then we implement the interface methods. To match what has |
| 380 | been declared in the header file they must take a pointer to |
| 381 | a IDirect3D structure and we must cast it to an _IDirect3D |
| 382 | so that we can manipulate the fields. This is performed by |
| 383 | the ICOM_THIS macro. |
| 384 | </para> |
| 385 | <para> |
| 386 | Finally we initialize the virtual table. |
| 387 | </para> |
| 388 | </sect2> |
| 389 | </sect1> |
| 390 | </chapter> |
| 391 | |
| 392 | <!-- Keep this comment at the end of the file |
| 393 | Local variables: |
| 394 | mode: sgml |
| 395 | sgml-parent-document:("wine-doc.sgml" "set" "book" "part" "chapter" "") |
| 396 | End: |
| 397 | --> |