programacion - Diseño COM de C++. Composición vs herencia múltiple
herencia programacion (3)
La herencia múltiple es una forma muy común de hacer interfaces COM, así que sí es posible.
Sin embargo, QueryInterface aún debe emitir el puntero para cada interfaz. Una propiedad interesante de la herencia múltiple es que el puntero puede ajustarse para cada tipo de clase: un puntero a IDispatch no tendrá el mismo valor que un puntero a IDocHostUIHandler, aunque ambos apuntan al mismo objeto. También asegúrese de que una QueryInterface for IUnknown siempre devuelva el mismo puntero; dado que todas las interfaces derivan de IUnknown obtendrás un reparto ambiguo si intentas lanzar directamente a él, pero eso también significa que puedes utilizar cualquier interfaz como IUnknown, solo elige la primera en la lista principal.
Estoy tratando de incorporar un control de navegador en mi aplicación (IWebBrowser2). Necesito implementar IDispatch, IDocHostShowUI, IDocHostUIHandler, etc. para que esto funcione. Estoy haciendo esto en API pura C ++ / Win32. No estoy usando ATL, MFC o cualquier otro marco.
Tengo una clase principal, llamada TWebf, que crea una ventana Win32 para poner el control del navegador y hace todas las llamadas OLE necesarias para que funcione. También se usa para controlar el control del navegador, con métodos como Actualizar (), Atrás (), Adelante (), etc.
En este momento esto se implementa con la composición. TWebf tiene clases que implementan todas las diferentes interfaces (IDispatch, IDocHostShowUI ...) como miembros (stack asignado). Lo primero que hace TWebf en su constructor es darle a todos esos miembros un puntero a sí mismo ( dispatch.webf = this;
etc). QueryInterface, AddRef y Release se implementan como llamadas a esos métodos en TWebf para todas las implementaciones de interfaz (llamando a return webf->QueryInterface(riid, ppv);
por ejemplo)
No me gusta esta dependencia circular entre TWebf y las clases que implementan las interfaces. TWebf tiene un miembro de TDispatch que tiene un miembro de TWebf que tiene un ...
Así que estaba pensando en resolver esto con herencia múltiple en su lugar. Eso también simplificaría QueryInterface para que siempre sea capaz de devolver this
.
Un boceto UMLish de lo que quiero sería algo como esto: (Haga clic para una vista más grande)
http://img827.imageshack.us/img827/3269/lsactivedesktopumlsmall.png
Como se puede ver en el uml, quiero proporcionar implementaciones mínimas de todas las interfaces, por lo que solo tengo que anular esos métodos en las interfaces. De hecho, quiero hacer algo sustancial en TWebf.
¿Es posible mi "implementación de herencia múltiple"? ¿Es una buena idea? ¿Es la mejor solución?
EDITAR:
Para una discusión futura, aquí está la implementación actual de QueryInterface en TWebf
HRESULT STDMETHODCALLTYPE TWebf::QueryInterface(REFIID riid, void **ppv)
{
*ppv = NULL;
if (riid == IID_IUnknown) {
*ppv = this;
} else if (riid == IID_IOleClientSite) {
*ppv = &clientsite;
} else if (riid == IID_IOleWindow || riid == IID_IOleInPlaceSite) {
*ppv = &site;
} else if (riid == IID_IOleInPlaceUIWindow || riid == IID_IOleInPlaceFrame) {
*ppv = &frame;
} else if (riid == IID_IDispatch) {
*ppv = &dispatch;
} else if (riid == IID_IDocHostUIHandler) {
*ppv = &uihandler;
}
if (*ppv != NULL) {
AddRef();
return S_OK;
}
return E_NOINTERFACE;
}
EDICION 2:
Intenté implementar esto solo para algunas de las interfaces. Tener TWebf heredado de IUnknown y TOleClientSite parece funcionar bien, pero cuando agregué TDispatch a la lista de herencia, dejó de funcionar.
Además de la warning C4584: ''TWebf'' : base-class ''IUnknown'' is already a base-class of ''TDispatch''
advertencia warning C4584: ''TWebf'' : base-class ''IUnknown'' is already a base-class of ''TDispatch''
. También recibo errores de tiempo de ejecución. El error de tiempo de ejecución es "Ubicación de lectura de violación de acceso 0x00000000"
El error de tiempo de ejecución ocurre en una línea que trata con IOleClientSite, no IDispatch por algún motivo. No sé por qué sucede esto, o si realmente tiene que ver con la herencia múltiple o no. Alguna pista a alguien?
EDIT 3:
Una mala implementación de QueryInterface parece haber sido el motivo de la excepción de tiempo de ejecución. Como Mark Ransom señaló correctamente, este puntero debe enviarse antes de asignarse a * ppv, y se necesita cuidado especial cuando se solicita IUnknown. Leer ¿Por qué necesito exactamente un upcast explícito al implementar QueryInterface en un objeto con herencia múltiple para una excelente explicación de eso?
¿Por qué exactamente obtuve ese error de tiempo de ejecución específico que todavía no sé?
La herencia múltiple tiene un par de limitaciones
Si dos interfaces piden una implementación de una función con el mismo nombre / firma, es imposible proporcionar dos comportamientos diferentes usando herencia múltiple. En algunos casos, quiere la misma implementación, pero en otros no.
Habrá múltiples interfaces IUnknown en la tabla virtual de su clase que pueden sumarse a un uso de memoria adicional. Comparten las mismas implementaciones, lo cual es bueno.
Sería sustancialmente más fácil permanecer con la composición. MI tiene muchas dificultades, como la herencia virtual, y sufre mucho de mantenibilidad. Si tiene que pasar esto a las clases compuestas como miembro de datos, lo ha hecho mal. Lo que debe hacer es pasar esto en llamadas de método si necesitan acceder a los otros métodos provistos. Como usted controla todas las llamadas de método al objeto compuesto, no debería ser un problema insertar el puntero adicional. Esto hace MUCHA la vida, MUCHO más fácil para el mantenimiento y otras operaciones.