Hi all,
I've sent a patch for ddraw COM management, I did not have any comments and the patch has been rejected. Could someone tell me if something is wrong or lacking?
Christian.
PS: I've joined a small doc that explains how to use objects with multiple interfaces. This docs is based on what has been done before with the ddraw code plus the enhancement I did.
--------------------------------------------------------------------------------- - COM OBJECTS MANAGEMENT - ---------------------------------------------------------------------------------
1) Definitions: ---------------
a) Object classes
An object class is a structure wich defines the data of an object and all the interfaces that act on the object data
Example :
struct ObjectClass { INTERFACE 1; INTERFACE 2; ............ INTERFACE N;
DATA; } ;
typedef struct ObjectClass ObjectClass;
b) Objects
An object is a instance of an object class.
Example :
An object is created as follows :
ObjectClass* object = malloc(sizeof(ObjectClass),...)
c) Interfaces
An interface is a way for an application to interact with an object. It contains a pointer to a virtual table plus some other data than can help objects management.
An interface is a structure, which is used internally, that can be defined with the INTERFACE macro :
#define INTERFACE(iface) \ struct { \ iface interface; \ /* VTABLE */ LPVOID object; \ /* pointer to the object the interface belongs to */ }
To add a field to the interface, just add it in this macro.
For example, we could embed the version number inside an interface or a magic number to check if the interface's pointer points to a real interface. And, off course, modify some macros or create new ones to make use of them.
2) Implementing COM objects: ---------------------------
a) Declaring a COM object with multiple interfaces
To declare interfaces in a COM object the ICOM_VFIELD_MULTI must be used for each interface as shown below:
struct IDirect3DImpl { /* IUnknown fields */ ICOM_VFIELD_MULTI(IDirect3D); ICOM_VFIELD_MULTI(IDirect3D2); ICOM_VFIELD_MULTI(IDirect3D3); ICOM_VFIELD_MULTI(IDirect3D7); DWORD ref; /* IDirect3D fields */ IDirectDrawImpl* ddraw; LPVOID private; };
b) Creating an object
Just declare an pointer to an object and allocate the memory for it.
Example:
IDirect3DImpl* object = malloc(sizeof(IDirect3DImpl),...)
c) Initializing interfaces
It is done with the ICOM_INIT_INTERFACE(object_pointer, interface_name, virtual_table)
ICOM_INIT_INTERFACE(object,IDirect3D, VTABLE_IDirect3D); ICOM_INIT_INTERFACE(object,IDirect3D2,VTABLE_IDirect3D2); ICOM_INIT_INTERFACE(object,IDirect3D3,VTABLE_IDirect3D3); ICOM_INIT_INTERFACE(object,IDirect3D7,VTABLE_IDirect3D4);
d) Retreive an object from within an interface's function
If we consider the function :
IDirect3D_AddRef(IDirect3D iface,.......)
To retrieve a pointer to an object within the function, both followings macro are used :
1) ICOM_OBJECT(object_class,iface)
This macro return an pointer to the object, the value is of object_class* type
Example:
IDirect3DImpl* d3d = ICOM_OBJECT(IDirect3DImpl,iface);
2) ICOM_THIS_OBJECT(object class,iface)
It simply declares a This pointer and initialize it with the object address.
IDirect3DImpl* This = ICOM_OBJECT(IDirect3DImpl,iface);
e) Sharing functions between multiple interfaces
In the case where a function does no have to do specific actions depending on the interface, you can share directly a function.
Example :
If we consider the function :
IDirect3D_AddRef(IDirect3D iface,.......)
The different virtual tables would be :
VTABLE_IDirect3D = {......,IDirect3D_AddRef,....); VTABLE_IDirect3D2 = {......,XCAST()IDirect3D_AddRef,....); VTABLE_IDirect3D3 = {......,XCAST()IDirect3D_AddRef,....); VTABLE_IDirect3D7 = {......,XCAST()IDirect3D_AddRef,....);
Note that this is possible because the ICOM_OBJECT can retreive the object's pointer without knowing the interface involved.
f) Thunking interface
Interface thunking enable changing from one interface to another.
The following macro COM_INTERFACE_CAST(impltype, ifnamefrom, ifnameto, ifaceptr) performs this task.
impltype = object class ifnamefrom = interface type we have ifnamefrom = interface type we want ifaceptr = pointer to the interface to cast return = pointer to the casted interface
Interface thunking is a another way to shared a single function across different interfaces but as shown below this is required only when structures must be translated, interfaces returned casted or to handle prototype change :
Example:
You want to share the IDirect3D_CreateDevice across all IDirect3DX interfaces (with X above 1 because a device in Direct3D is created in a different way).
IDirect3D2_CreateDevice(IDirect3D2* iface,.....) is the only one implementation of the function.
The others are thunks to the IDirect3D2_CreateDevice.
IDirect3D3_CreateDevice(IDirect3D3* iface, .....,LPDIRECT3DDEVICE3 *lpdevice, LPUNKNOWN lpUnk) { IDirect3DDevice3 *dev; IDirect3D_CreateDevice(ICOM_INTERFACE_CAST(IDirect3DImpl,IDirect3D2,IDirect3D3,iface),.....,&dev); *lpdevice = ICOM_INTERFACE_CAST((IDirect3DImpl,IDirect3D3,IDirect3D2,device) }
IDirect3D7_CreateDevice(IDirect3D7* iface, .....,LPDIRECT3DDEVICE7 *lpdevice) { IDirect3DDevice2 *dev; IDirect3D_CreateDevice(ICOM_INTERFACE_CAST(IDirect3DImpl,IDirect3D2,IDirect3D7,iface),.....,&dev); *lpdevice = ICOM_INTERFACE_CAST((IDirect3DImpl,IDirect3D7,IDirect3D2,dev) }
Here, we have to deal with a returned interface and a prototype change.
The VTABLE should be:
VTABLE_IDirect3D2 = {......,IDirect3D2_CreateDevice,....); VTABLE_IDirect3D3 = {......,IDirect3D3_CreateDevice,....); VTABLE_IDirect3D7 = {......,IDirect3D7_CreateDevice,....);
Note that there are few functions that need this, for the others, thunks are not necessary and the way described in e) must be preferred.
g) Getting an specific interface of an object
With the ICOM_INTERFACE(implobj, ifacename) macro, any interfaces can be retreive from an object.
implobj = pointer to the object ifacename = interface name we want return = pointer to an interface
Example:
IDirect3DImpl* object;
............
IDirect3D3* iface_d3d3 = ICOM_INTERFACE(object,IDirect3D3)
h) Calling an COM object functions from another
If a function is shared across multiple interfaces, you should write:
IDirect3D3_CreateDevice(IDirect3D* iface,.....) { ICOM_THIS_OBJECT(iface); ................. IDirect3D_AddRef(ICOM_INTERFACE(This,IDirect3D),.....); ................. }
If you know the nature of iface (i.e. thunking is used or the function is used in only one interface), you can write :
IDirect3D3_CreateDevice(IDirect3D* iface,.....) { ICOM_THIS_OBJECT(iface); ................. IDirect3D3_AddRef(iface,.....); ................. }
Christian Costa titan.costa@wanadoo.fr writes:
I've sent a patch for ddraw COM management, I did not have any comments and the patch has been rejected. Could someone tell me if something is wrong or lacking?
Well, I was hoping some of the COM experts would comment on that. If I understand it right you are avoiding writing some thunking routines for older interfaces, at the cost of an extra pointer access in every function. I'm not convinced it's a good trade-off, but I'd like to hear other opinions.
On Thu, Nov 14, 2002 at 04:35:00PM -0800, WINE wrote:
Christian Costa titan.costa@wanadoo.fr writes:
I've sent a patch for ddraw COM management, I did not have any comments and the patch has been rejected. Could someone tell me if something is wrong or lacking?
Well, I was hoping some of the COM experts would comment on that. If I understand it right you are avoiding writing some thunking routines for older interfaces, at the cost of an extra pointer access in every function. I'm not convinced it's a good trade-off, but I'd like to hear other opinions.
I do not really see the need for it either. The implementation functions know the interface they get passed, so the offset to the vtable ptr within the object is constant and can very easily be calculated by the compiler.
As for increased function sharing and reduced thunks usage... True, but the number of functions is not really annoying or problematic.
Ciao, Marcus
Well, I was hoping some of the COM experts would comment on that. If I understand it right you are avoiding writing some thunking routines for older interfaces, at the cost of an extra pointer access in every function. I'm not convinced it's a good trade-off, but I'd like to hear other opinions.
Well, in the other case, the thunked interface will also have a performance trade-off as it will introduce extra pointer arithmetic and function calls on each COM call. Of course, the 'dominant' one will not have any performance penalty at all.
As for increased function sharing and reduced thunks usage... True, but the number of functions is not really annoying or problematic.
Well, in some cases like in D3DDevice where you have 34 methods that are shared between different interfaces, it starts to get a little bit annoying to write 34 thunks :-)
In any case, for the moment I rewrote most of the COM part for the Direct3D code using Christian's patch (as it made my life much easier :-) ). Now, if it won't go into the tree, I could add thunking to it (should not be that hard). Just take a decision soon so as to not have a 11 klines patch lying in my tree for too long...
Lionel
On Fri, Nov 15, 2002 at 09:53:33AM +0100, Lionel Ulmer wrote:
Well, I was hoping some of the COM experts would comment on that. If I understand it right you are avoiding writing some thunking routines for older interfaces, at the cost of an extra pointer access in every function. I'm not convinced it's a good trade-off, but I'd like to hear other opinions.
Well, in the other case, the thunked interface will also have a performance trade-off as it will introduce extra pointer arithmetic and function calls on each COM call. Of course, the 'dominant' one will not have any performance penalty at all.
As for increased function sharing and reduced thunks usage... True, but the number of functions is not really annoying or problematic.
Well, in some cases like in D3DDevice where you have 34 methods that are shared between different interfaces, it starts to get a little bit annoying to write 34 thunks :-)
In any case, for the moment I rewrote most of the COM part for the Direct3D code using Christian's patch (as it made my life much easier :-) ). Now, if it won't go into the tree, I could add thunking to it (should not be that hard). Just take a decision soon so as to not have a 11 klines patch lying in my tree for too long...
If this makes your work easier I would just say go for it.
Ciao, Marcus
Lionel Ulmer lionel.ulmer@free.fr writes:
In any case, for the moment I rewrote most of the COM part for the Direct3D code using Christian's patch (as it made my life much easier :-) ). Now, if it won't go into the tree, I could add thunking to it (should not be that hard). Just take a decision soon so as to not have a 11 klines patch lying in my tree for too long...
I think I prefer if you write the thunks. Sure it's a bit annoying, but it only has to be done once, they don't really need to be maintained afterwards; and I'm afraid that if we add the "easy" macros they will get used everywhere, not just where it saves 34 thunks, and we will lose performance without good reason.
On 14 Nov 2002, Alexandre Julliard wrote:
Christian Costa titan.costa@wanadoo.fr writes:
I've sent a patch for ddraw COM management, I did not have any comments and the patch has been rejected. Could someone tell me if something is wrong or lacking?
Well, I was hoping some of the COM experts would comment on that. If I understand it right you are avoiding writing some thunking routines for older interfaces, at the cost of an extra pointer access in every function. I'm not convinced it's a good trade-off, but I'd like to hear other opinions.
Though it's not really in my interest to, I suppose I could comment: if you study the MS definitions in ddrawi.h hard enough, then you can conclude that this particular overhead exist in real DirectDraw interfaces as well (so perhaps Direct3D 8 broke so hard with earlier versions to get rid of this overhead). But I still agree with your objection - apps using newer (more powerful) interfaces generally expect better performance, and thus ought to get less overhead than older interfaces (and that's how it is in WineX). But then again, that overhead is probably negligible compared to other factors.