TN065: Compatibilidad con una interfaz dual para los servidores de automatización OLE
Nota:
La nota técnica siguiente no se ha actualizado desde que se incluyó por primera vez en la documentación en línea. Como resultado, algunos procedimientos y temas podrían estar obsoletos o ser incorrectos. Para obtener información más reciente, se recomienda buscar el tema de interés en el índice de la documentación en línea.
En esta nota se describe cómo incluir compatibilidad con la interfaz dual en una aplicación de servidor de automatización OLE basada en MFC. En el ejemplo de ACDUAL se ilustra la compatibilidad con la interfaz dual; el código de ejemplo de esta nota lo hemos tomado de ACDUAL. Las macros descritas en esta nota, como DECLARE_DUAL_ERRORINFO, DUAL_ERRORINFO_PART y IMPLEMENT_DUAL_ERRORINFO, forman parte del ejemplo de ACDUAL y se pueden encontrar en MFCDUAL.H.
Interfaces duales
Aunque la automatización OLE permite implementar una interfaz IDispatch
, una interfaz VTBL o una interfaz dual (que abarca ambas), Microsoft recomienda encarecidamente implementar interfaces duales para todos los objetos de automatización OLE expuestos. Usar interfaces duales reporta unas ventajas muy significativas frente a usar interfaces exclusivamente IDispatch
o VTBL:
Los enlaces pueden tener lugar en tiempo de compilación a través de la interfaz VTBL o en tiempo de ejecución a través de
IDispatch
.Los controladores de automatización OLE que pueden usar la interfaz VTBL pueden beneficiarse de un mejor rendimiento.
Los controladores de automatización OLE existentes que usan la interfaz
IDispatch
seguirán funcionando.La interfaz VTBL es más fácil de llamar desde C++.
Las interfaces duales son necesarias para la compatibilidad con las características de compatibilidad de objetos de Visual Basic.
Adición de compatibilidad de interfaz dual a una clase basada en CCmdTarget
En realidad, una interfaz dual es simplemente una interfaz personalizada derivada de IDispatch
. La manera más sencilla de implementar la compatibilidad de interfaz dual en una clase basada en CCmdTarget
es implementar primero la interfaz de envío normal en la clase mediante MFC y ClassWizard y agregar la interfaz personalizada más adelante. La mayor parte de la implementación de la interfaz personalizada sencillamente volverá a delegar en la implementación de IDispatch
de MFC.
En primer lugar, modifique el archivo ODL del servidor para definir interfaces duales para los objetos. Para definir una interfaz dual, debe usar una instrucción de interfaz, en lugar de la instrucción DISPINTERFACE
que generan los asistentes de Visual C++. En vez de quitar la instrucción DISPINTERFACE
existente, agregue una instrucción de interfaz nueva. Al conservar la forma de DISPINTERFACE
, puede seguir usando ClassWizard para agregar propiedades y métodos al objeto, pero deben agregarse propiedades y métodos equivalentes a la instrucción de interfaz.
Una instrucción de interfaz de una interfaz dual debe tener los atributos OLEAUTOMATION y DUAL, y la interfaz debe derivarse de IDispatch
. Puede usar el ejemplo de GUIDGEN para crear un IID para la interfaz dual:
[ uuid(0BDD0E81-0DD7-11cf-BBA8-444553540000), // IID_IDualAClick
oleautomation,
dual
]
interface IDualAClick : IDispatch
{
};
Una vez implementada la instrucción de interfaz, empiece a agregar las entradas de métodos y propiedades. En el caso de las interfaces duales, debe reorganizar las listas de parámetros para que los métodos y las funciones de descriptor de acceso de las propiedades de la interfaz dual devuelvan un valor HRESULT y pasen sus valores devueltos como parámetros con los atributos [retval,out]
. Recuerde que, en el caso de las propiedades, deberá agregar una función de acceso tanto de lectura (propget
) como de escritura (propput
) con el mismo identificador. Por ejemplo:
[propput, id(1)] HRESULT text([in] BSTR newText);
[propget, id(1)] HRESULT text([out, retval] BSTR* retval);
Una vez definidos los métodos y las propiedades, debe agregar una referencia a la instrucción de interfaz en la instrucción de coclase. Por ejemplo:
[ uuid(4B115281-32F0-11cf-AC85-444553540000) ]
coclass Document
{
dispinterface IAClick;
[default] interface IDualAClick;
};
Una vez actualizado el archivo ODL, use el mecanismo de asignación de interfaz de MFC para definir una clase de implementación para la interfaz dual de la clase de objeto y realizar las entradas correspondientes en el mecanismo QueryInterface
de MFC. Se necesita una entrada en el bloque INTERFACE_PART
por cada entrada de la instrucción de interfaz de ODL, además de las entradas de una interfaz de envío. Cada entrada de ODL con el atributo propput necesita una función denominada put_propertyname
. Cada entrada con el atributo propget necesita una función denominada get_propertyname
.
Para definir una clase de implementación para la interfaz dual, agregue un bloque DUAL_INTERFACE_PART
a la definición de clase del objeto. Por ejemplo:
BEGIN_DUAL_INTERFACE_PART(DualAClick, IDualAClick)
STDMETHOD(put_text)(THIS_ BSTR newText);
STDMETHOD(get_text)(THIS_ BSTR FAR* retval);
STDMETHOD(put_x)(THIS_ short newX);
STDMETHOD(get_x)(THIS_ short FAR* retval);
STDMETHOD(put_y)(THIS_ short newY);
STDMETHOD(get_y)(THIS_ short FAR* retval);
STDMETHOD(put_Position)(THIS_ IDualAutoClickPoint FAR* newPosition);
STDMETHOD(get_Position)(THIS_ IDualAutoClickPoint FAR* FAR* retval);
STDMETHOD(RefreshWindow)(THIS);
STDMETHOD(SetAllProps)(THIS_ short x, short y, BSTR text);
STDMETHOD(ShowWindow)(THIS);
END_DUAL_INTERFACE_PART(DualAClick)
Para conectar la interfaz dual al mecanismo QueryInterface de MFC, agregue una entrada INTERFACE_PART
al mapa de interfaz:
BEGIN_INTERFACE_MAP(CAutoClickDoc, CDocument)
INTERFACE_PART(CAutoClickDoc, DIID_IAClick, Dispatch)
INTERFACE_PART(CAutoClickDoc, IID_IDualAClick, DualAClick)
END_INTERFACE_MAP()
Luego, debe rellenar la implementación de la interfaz. En su mayor parte, podrá delegar en la implementación IDispatch
de MFC existente. Por ejemplo:
STDMETHODIMP_(ULONG) CAutoClickDoc::XDualAClick::AddRef()
{
METHOD_PROLOGUE(CAutoClickDoc, DualAClick)
return pThis->ExternalAddRef();
}
STDMETHODIMP_(ULONG) CAutoClickDoc::XDualAClick::Release()
{
METHOD_PROLOGUE(CAutoClickDoc, DualAClick)
return pThis->ExternalRelease();
}
STDMETHODIMP CAutoClickDoc::XDualAClick::QueryInterface(
REFIID iid,
LPVOID* ppvObj)
{
METHOD_PROLOGUE(CAutoClickDoc, DualAClick)
return pThis->ExternalQueryInterface(&iid, ppvObj);
}
STDMETHODIMP CAutoClickDoc::XDualAClick::GetTypeInfoCount(
UINT FAR* pctinfo)
{
METHOD_PROLOGUE(CAutoClickDoc, DualAClick)
LPDISPATCH lpDispatch = pThis->GetIDispatch(FALSE);
ASSERT(lpDispatch != NULL);
return lpDispatch->GetTypeInfoCount(pctinfo);
}
STDMETHODIMP CAutoClickDoc::XDualAClick::GetTypeInfo(
UINT itinfo,
LCID lcid,
ITypeInfo FAR* FAR* pptinfo)
{
METHOD_PROLOGUE(CAutoClickDoc, DualAClick)
LPDISPATCH lpDispatch = pThis->GetIDispatch(FALSE);
ASSERT(lpDispatch != NULL);
return lpDispatch->GetTypeInfo(itinfo, lcid, pptinfo);
}
STDMETHODIMP CAutoClickDoc::XDualAClick::GetIDsOfNames(
REFIID riid,
OLECHAR FAR* FAR* rgszNames,
UINT cNames,
LCID lcid,
DISPID FAR* rgdispid)
{
METHOD_PROLOGUE(CAutoClickDoc, DualAClick)
LPDISPATCH lpDispatch = pThis->GetIDispatch(FALSE);
ASSERT(lpDispatch != NULL);
return lpDispatch->GetIDsOfNames(riid, rgszNames, cNames, lcid, rgdispid);
}
STDMETHODIMP CAutoClickDoc::XDualAClick::Invoke(
DISPID dispidMember,
REFIID riid,
LCID lcid,
WORD wFlags,
DISPPARAMS FAR* pdispparams,
VARIANT FAR* pvarResult,
EXCEPINFO FAR* pexcepinfo,
UINT FAR* puArgErr)
{
METHOD_PROLOGUE(CAutoClickDoc, DualAClick)
LPDISPATCH lpDispatch = pThis->GetIDispatch(FALSE);
ASSERT(lpDispatch != NULL);
return lpDispatch->Invoke(dispidMember, riid, lcid,
wFlags, pdispparams, pvarResult, pexcepinfo, puArgErr);
}
Con los métodos del objeto y las funciones de descriptor de acceso de las propiedades, debe rellenar la implementación. Por lo general, el método y las funciones de propiedades pueden volver a delegar en los métodos generados mediante ClassWizard. Sin embargo, si configura propiedades para acceder a las variables directamente, debe escribir el código para obtener/colocar el valor en la variable. Por ejemplo:
STDMETHODIMP CAutoClickDoc::XDualAClick::put_text(BSTR newText)
{
METHOD_PROLOGUE(CAutoClickDoc, DualAClick)
// MFC automatically converts from Unicode BSTR to
// Ansi CString, if necessary...
pThis->m_str = newText;
return NOERROR;
}
STDMETHODIMP CAutoClickDoc::XDualAClick::get_text(BSTR* retval)
{
METHOD_PROLOGUE(CAutoClickDoc, DualAClick)
// MFC automatically converts from Ansi CString to
// Unicode BSTR, if necessary...
pThis->m_str.SetSysString(retval);
return NOERROR;
}
Paso de punteros a interfaces duales
Pasar un puntero de interfaz dual no es sencillo, especialmente si hay que llamar a CCmdTarget::FromIDispatch
. FromIDispatch
solo funciona en punteros IDispatch
de MFC. Una manera de solucionar esto es consultar el puntero IDispatch
original configurado por MFC y pasar ese puntero a las funciones que lo necesiten. Por ejemplo:
STDMETHODIMP CAutoClickDoc::XDualAClick::put_Position(
IDualAutoClickPoint FAR* newPosition)
{
METHOD_PROLOGUE(CAutoClickDoc, DualAClick)
LPDISPATCH lpDisp = NULL;
newPosition->QueryInterface(IID_IDispatch, (LPVOID*)&lpDisp);
pThis->SetPosition(lpDisp);
lpDisp->Release();
return NOERROR;
}
Antes de volver a pasar un puntero a través del método de interfaz dual, es posible que tenga que convertirlo de puntero IDispatch
de MFC a puntero de interfaz dual. Por ejemplo:
STDMETHODIMP CAutoClickDoc::XDualAClick::get_Position(
IDualAutoClickPoint FAR* FAR* retval)
{
METHOD_PROLOGUE(CAutoClickDoc, DualAClick)
LPDISPATCH lpDisp;
lpDisp = pThis->GetPosition();
lpDisp->QueryInterface(IID_IDualAutoClickPoint, (LPVOID*)retval);
return NOERROR;
}
Registro de la biblioteca de tipos de la aplicación
AppWizard no genera código para registrar la biblioteca de tipos de una aplicación de servidor de automatización OLE con el sistema. Aunque hay otras maneras de registrar la biblioteca de tipos, conviene que la aplicación registre la biblioteca de tipos cuando actualice su información de tipos OLE, es decir, siempre que la aplicación se ejecute de forma independiente.
Para registrar la biblioteca de tipos de la aplicación cada vez que la aplicación se ejecute de forma independiente:
Incluya AFXCTL.H en el estándar (incluido el archivo de encabezado STDAFX.H) para acceder a la definición de la función
AfxOleRegisterTypeLib
.En la función
InitInstance
de la aplicación, busque la llamada aCOleObjectFactory::UpdateRegistryAll
. Agregue una llamada aAfxOleRegisterTypeLib
tras esta llamada, especificando el LIBID correspondiente a la biblioteca de tipos, junto con el nombre de la biblioteca de tipos:// When a server application is launched stand-alone, it is a good idea // to update the system registry in case it has been damaged. m_server.UpdateRegistry(OAT_DISPATCH_OBJECT); COleObjectFactory::UpdateRegistryAll(); // DUAL_SUPPORT_START // Make sure the type library is registered or dual interface won't work. AfxOleRegisterTypeLib(AfxGetInstanceHandle(), LIBID_ACDual, _T("AutoClik.TLB")); // DUAL_SUPPORT_END
Modificación de la configuración de compilación del proyecto para dar cabida a cambios en la biblioteca de tipos
Para modificar la configuración de compilación de un proyecto de modo que MkTypLib genere un archivo de encabezado que contenga definiciones UUID cada vez que se vuelva a generar la biblioteca de tipos:
En el menú Compilar, haga clic en Configuración y seleccione el archivo ODL en la lista de archivos de cada configuración.
Haga clic en la pestaña OLE Types (Tipos OLE) y especifique un nombre de archivo en el campo Output header (Encabezado de salida). Elija un nombre de archivo que no se use aún en el proyecto, ya que MkTypLib sobrescribirá cualquier archivo existente. Haga clic en Aceptar para cerrar el cuadro de diálogo Configuración de compilación.
Para agregar las definiciones uuID del archivo de encabezado generado por MkTypLib al proyecto:
Incluya el archivo de encabezado generado por MkTypLib en el estándar, incluido el archivo de encabezado stdafx.h.
Cree un archivo nuevo, INITIIDS.CPP, y agréguelo al proyecto. En este archivo, incluya el archivo de encabezado generado por MkTypLib después de incluir OLE2.H e INITGUID.H:
// initIIDs.c: defines IIDs for dual interfaces // This must not be built with precompiled header. #include <ole2.h> #include <initguid.h> #include "acdual.h"
En el menú Compilar, haga clic en Configuración y seleccione INITIIDS.CPP en la lista de archivos de cada configuración.
Haga clic en la pestaña C++, haga clic en la categoría Encabezados precompilados y seleccione el botón de radio No usar encabezados precompilados. Haga clic en Aceptar para cerrar el cuadro de diálogo Configuración de compilación.
Especificación del nombre de clase de objeto correcto en la biblioteca de tipos
Los asistentes que vienen incluidos con Visual C++ usan incorrectamente el nombre de clase de implementación para especificar la coclase en el archivo ODL del servidor para las clases susceptibles de crearse mediante OLE. Si bien esto funcionará, es probable que el nombre de la clase de implementación no sea el nombre de clase que se pretende que usen los usuarios del objeto. Para especificar el nombre correcto, abra el archivo ODL, busque cada instrucción de coclase y reemplace el nombre de clase de implementación por el nombre externo correcto.
Tenga en cuenta que, cuando una instrucción de coclase se cambia, los nombres de variable de los CLSID del archivo de encabezado generado por MkTypLib cambiarán en consecuencia. Tendrá que actualizar el código para que use los nuevos nombres de variable.
Control de excepciones y las interfaces de error de automatización
Los métodos y las funciones de descriptor de acceso de propiedad del objeto de automatización pueden producir excepciones. Si es así, deberá controlarlas en la implementación de la interfaz dual y pasar información sobre la excepción al controlador a través de la interfaz de control de errores de automatización OLE, IErrorInfo
. Esta interfaz proporciona información de error detallada y contextualizada a través de las interfaces tanto IDispatch
como VTBL. Para indicar que hay un controlador de errores disponible, debe implementar la interfaz ISupportErrorInfo
.
Para ilustrar el mecanismo de control de errores, supongamos que las funciones generadas por ClassWizard usadas para implementar la compatibilidad con el envío estándar producen excepciones. La implementación de IDispatch::Invoke
de MFC normalmente detecta estas excepciones y las convierte en una estructura EXCEPTINFO que se devuelve a través de la llamada a Invoke
. Sin embargo, si se usa la interfaz VTBL, el usuario es el responsable de detectar las excepciones por sí mismo. Este es un de protección de los métodos de interfaz dual:
STDMETHODIMP CAutoClickDoc::XDualAClick::put_text(BSTR newText)
{
METHOD_PROLOGUE(CAutoClickDoc, DualAClick)
TRY_DUAL(IID_IDualAClick)
{
// MFC automatically converts from Unicode BSTR to
// Ansi CString, if necessary...
pThis->m_str = newText;
return NOERROR;
}
CATCH_ALL_DUAL
}
CATCH_ALL_DUAL
se encarga de devolver el código de error correcto cuando se produce una excepción. CATCH_ALL_DUAL
convierte una excepción de MFC en información de control de errores de automatización OLE mediante la interfaz ICreateErrorInfo
(hay una macro CATCH_ALL_DUAL
de ejemplo en el archivo MFCDUAL.H del ejemplo de ACDUAL. La función a la que llama para controlar excepciones, DualHandleException
, está en el archivo MFCDUAL.CPP). CATCH_ALL_DUAL
determina el código de error que se va a devolver en función del tipo de excepción que se haya producido:
COleDispatchException: en este caso,
HRESULT
se construye mediante el siguiente código:hr = MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, (e->m_wCode + 0x200));
Esto crea un elemento
HRESULT
específico de la interfaz que provocó la excepción. El código de error se desplaza por 0x200 para evitar conflictos con losHRESULT
definidos por el sistema para las interfaces OLE estándar.CMemoryException : en este caso, se devuelve
E_OUTOFMEMORY
.Cualquier otra excepción (en este caso,
E_UNEXPECTED
) se devuelve.
Para indicar que se usa el controlador de errores de automatización OLE, también debe implementar la interfaz ISupportErrorInfo
.
En primer lugar, agregue código a la definición de clase de automatización para reflejar que admite ISupportErrorInfo
.
En segundo lugar, agregue código al mapa de interfaz de la clase de automatización para asociar la clase de implementación ISupportErrorInfo
con el mecanismo QueryInterface
de MFC. La instrucción INTERFACE_PART
coincide con la clase definida para ISupportErrorInfo
.
Por último, implemente la clase definida para admitir ISupportErrorInfo
.
(El ejemplo de ACDUAL contiene tres macros para realizar estos tres pasos: DECLARE_DUAL_ERRORINFO
, DUAL_ERRORINFO_PART
y IMPLEMENT_DUAL_ERRORINFO
, todos ellos incluidos en MFCDUAL.H).
En el siguiente ejemplo se implementa una clase definida para admitir ISupportErrorInfo
. CAutoClickDoc
es el nombre de la clase de automatización y IID_IDualAClick
, el IID de la interfaz origen de los errores notificados a través del objeto de error de automatización OLE:
STDMETHODIMP_(ULONG) CAutoClickDoc::XSupportErrorInfo::AddRef()
{
METHOD_PROLOGUE(CAutoClickDoc, SupportErrorInfo)
return pThis->ExternalAddRef();
}
STDMETHODIMP_(ULONG) CAutoClickDoc::XSupportErrorInfo::Release()
{
METHOD_PROLOGUE(CAutoClickDoc, SupportErrorInfo)
return pThis->ExternalRelease();
}
STDMETHODIMP CAutoClickDoc::XSupportErrorInfo::QueryInterface(
REFIID iid,
LPVOID* ppvObj)
{
METHOD_PROLOGUE(CAutoClickDoc, SupportErrorInfo)
return pThis->ExternalQueryInterface(&iid, ppvObj);
}
STDMETHODIMP CAutoClickDoc::XSupportErrorInfo::InterfaceSupportsErrorInfo(
REFIID iid)
{
METHOD_PROLOGUE(CAutoClickDoc, SupportErrorInfo)
return (iid == IID_IDualAClick) S_OK : S_FALSE;
}