2010-07-06 9 views
5

Supongamos que tengo una clase que implementa dos o más interfaces COM (exactamente como here):Está utilizando la conversión implícita para un upcast en lugar de QueryInterface() legal con herencia múltiple?

class CMyClass : public IInterface1, public IInterface2 { 
}; 

QueryInterface() debe devolver el mismo puntero para cada solicitud de la misma interfaz (se necesita una conversión hacia arriba explícita de ajuste de puntero adecuado) :

if(iid == __uuidof(IUnknown)) { 
    *ppv = static_cast<IInterface1*>(this); 
    //call Addref(), return S_OK 
} else if(iid == __uuidof(IInterface1)) { 
    *ppv = static_cast<IInterface1*>(this); 
    //call Addref(), return S_OK 
} else if(iid == __uuidof(IInterface2)) { 
    *ppv = static_cast<IInterface2*>(this); 
    //call Addref(), return S_OK 
} else { 
    *ppv = 0; 
    return E_NOINTERFACE; 
} 

ahora hay dos IUnknown s en el objeto - una es la base del IInterface1 y el otro es la base de IInterface2. Y .

Vamos a suponer que llamé QueryInterface() para IInterface2 - el puntero devuelto será diferente del puntero devuelto cuando llamo para QueryInterface()IUnknown. Hasta aquí todo bien. Luego puedo pasar el IInterface2* obtenido en cualquier función que acepte IUnknown* y gracias a la conversión implícita de C++ se aceptará el puntero, pero no será el mismo puntero que QueryInterface() para IUnknown* recuperaría. De hecho, si esa función llama al QueryInterface() para IUnknown inmediatamente después de ser llamada, recuperará un puntero diferente.

¿Es esto legal en términos de COM? ¿Cómo manejo situaciones cuando tengo un puntero a un objeto heredado de forma múltiple y permito un upcast implícito?

+0

Hablando en términos prácticos, nunca he visto ningún código que haga uso de las reglas de identidad COM. Dicho eso, el otro IUnknown * NO es legal, DEBES elegir uno para regresar de QueryInterface. En términos de su propio uso interno y en C++ del objeto de implementación de objetos COM, si no está haciendo el COM de todos modos, cualquier cosa que esté haciendo es legal C++, pero no legitimage COM. –

+0

@Chris Becke: supongo que se requiere identidad para implementar alguna funcionalidad tipo caché. – sharptooth

Respuesta

3

COM no tiene reglas con respecto a la identidad de la interfaz, solo de la identidad del objeto. La primera regla de QI dice que un QI en IID_Unknown en dos punteros de interfaz debe devolver el mismo puntero si están implementados por el mismo objeto . Su implementación de QI hace esto correctamente.

Sin una garantía para la identidad de la interfaz, un método COM no puede suponer que obtiene el mismo puntero IUnknown que recuperará cuando llame a QI en ese puntero. Entonces, si la identidad del objeto necesita ser probada, se requiere una QI por separado.

2

Parece que hay un pequeño malentendido. Las interfaces IInterface1 y IInterface2 son abstractas. No hay QueryInterface() por separado para IInterface1 y IInterface2. Solo hay una declaración, que la clase CMyClass implementará todos los métodos desde IInterface1 y IInterface2 (el conjunto de métodos de CMyClass es el conjunto de métodos IInterface1 y IInterface1 juntos).

Así se implementa en la clase CMyClassunoQueryInterface(), unoAddRef()y unoRelease() método. En el QueryInterface() que lances el puntero a instancia de la clase CMyClass a static_cast<IUnknown*>.

ACTUALIZADO: Hola! Tuve que alejarme inmediatamente después de escribir mi respuesta. Solo ahora podía leer todas las demás respuestas y agregar algo a mi respuesta.

OK. Usted dice que si lanzas IInterface1 a IUnknown y si lanzas IInterface2 a IUnknown recibe dos punteros diferentes. ¡Tienes razón! Pero, sin embargo, no importa. Si compara el contenido de ambos punteros (cuyas direcciones tienen QueryInterface(), AddRef() y Release() en ambos casos) verá que ambos punteros tienen el mismo contenido. ¡Así que también estoy en lo correcto!

Hay un montón de buenos ejemplos de cómo implementar OCM de la pura C.En este caso, necesita definir una estructura estática con punteros a las funciones virtuales como QueryInterface(), AddRef() y Release() y devolver el puntero de dicha estructura como resultado de QueryInterface(). Se muestra una vez más, que solo el contenido que usted devuelve es importante para COM y no un puntero (no es importante qué variable devuelve).

Una pequeña observación más. En algunos comentarios, escribe sobre la posibilidad de tener muchas implementaciones de los métodos QueryInterface(), AddRef() y Release(). No estoy de acuerdo aquí. La razón es que las interfaces son clases abstractas puras y si define una clase que implementa algunas interfaces, no tiene una herencia de clase típica. Solo tiene una clase con una implementación de todas las funciones desde todas las interfaces. Si hace esto en C++, el compilador crea y llena los vtables estáticos con los punteros correspondientes a la única implementación de las funciones QueryInterface(), AddRef(), Release() y así sucesivamente, pero todos los cuadros apuntan a las mismas funciones.

Para reducir la cantidad de tablas vitae Microsoft introdujo __declspec(novtable) o ATL_NO_VTABLE, pero no es la parte de sus preguntas.

+0

Sí, lo sé. Pero hay un problema formal. Cuando implícitamente upcast de 'IInterface2' a' IUnknown' obtengo un puntero diferente del que obtengo de 'QI()'. Toda la pregunta es sobre una cosa: ¿es ese '' desconocido'' legal 'en términos de COM? – sharptooth

+0

Hay muchas implementaciones de estos métodos, incluso si todos menos uno reenvían la llamada a la versión única que lo implementa. –

2

Como Hans señala, su implementación de QueryInterface es correcta.

Es responsabilidad del usuario de un objeto COM utilizar QueryInterface en todo momento. La razón es exactamente para evitar el escenario que ha señalado: que al lanzar un puntero IInterface1 * o IInterface2 * al puntero IUnknown * se obtendrán valores físicos diferentes.

En C++, no es posible para el implementador evitar que el usuario haga mal.

Si va a causar un fallo en una aplicación depende de si la aplicación se preocupa sobre la comparación de objetos COM para la identidad.

MSDN: The Rules of the Component Object Model

la identidad del objeto. Se requiere que cualquier llamada a QueryInterface en cualquier interfaz para una instancia de objeto determinada para la interfaz específica IUnknown siempre debe devolver el mismo valor del puntero física . Esto permite llamar QueryInterface (IID_IUnknown, ...) en cualquier dos interfaces y comparando los resultados para determinar si apuntan a la misma instancia de un objeto (la misma identidad de los objetos COM).

Como Oleg señala, el fracaso de la identidad del objeto tendrá un efecto más bien limitado, debido a que la vocación de los miembros de la interfaz COM es esencialmente afectadas - cada entrada de tabla virtual apuntará a la misma dirección de la función si la firma de la función coincide .

Todas las implementaciones de punteros inteligentes COM usan QueryInterface cuando se transfieren a una interfaz diferente, o cuando la interfaz actual es dudosa. Sus operadores de comparación utilizan automáticamente QueryInterface(IID_IUnknown, ...) en cada puntero de entrada para obtener punteros físicos IUnknown * que se pueden comparar directamente. La falla de la identidad del objeto comenzará a afectar su aplicación si opta por utilizar punteros sin procesar en su aplicación.

Un caso especial en el que la falla no se manifiesta es cuando la clase no tiene ninguna herencia de diamantes. Sin embargo, la conversión implícita siempre es ilegal en COM, independientemente de si bloquea una aplicación o no.

+0

He leído las reglas, pero ... Dicen que 'QueryInterface()', nada más, debe seguir el requisito de identidad. ¿Cómo significa eso realmente que una subida implícita es ilegal? 'QI()' es 'QI()', las conversiones C++ son conversiones C++. – sharptooth

+0

No estoy de acuerdo. La cita habla sobre * identidad de objeto *, no es lo mismo que identidad de interfaz. –

+1

@sharptooth: cuando piense en COM, trate de mantenerse alejado de la idea de que IInterface1 hereda de IUnknown. Una forma más precisa es decir: IInterface1 contiene los miembros AddRef, Release y QueryInterface, así como sus propias funciones. – rwong

0

Si tiene interface IInterface1 : IDispatch y luego interface IInterface2 : IDispatchQI para IUnknown en IInterface1 y IInterface2 debe devolver el mismo puntero por regla general la identidad del objeto

pero ...

QI para IDispatch en IInterface1 puede devolver una aplicación diferente en comparación a QI para IDispatch en IInterface2.

Entonces la respuesta es (otra vez) depende. Negativo para upcasting a IUnknown, podría ser positivo para la revalorización a cualquier otra cosa.