Compartir a través de


TN065: compatibilidad de Dual-Interface con 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 incorrectos. Para obtener la información más reciente, se recomienda buscar el tema de interés en el índice de documentación en línea.

En esta nota se describe cómo agregar compatibilidad de doble interfaz a una aplicación de servidor OLE Automation basada en MFC. El ejemplo de ACDUAL muestra la compatibilidad con la interfaz dual y el código de ejemplo de esta nota se toma de ACDUAL. Las macros descritas en esta nota, como DECLARE_DUAL_ERRORINFO, DUAL_ERRORINFO_PART y IMPLEMENT_DUAL_ERRORINFO, forman parte del ejemplo ACDUAL y se pueden encontrar en MFCDUAL.H.

Interfaces duales

Aunque OLE Automation permite implementar una IDispatch interfaz, una interfaz VTBL o una interfaz dual (que abarca ambos), Microsoft recomienda encarecidamente implementar interfaces duales para todos los objetos de automatización OLE expuestos. Las interfaces duales tienen ventajas significativas con IDispatchrespecto a interfaces de solo VTBL o de solo VTBL:

  • El enlace puede tener lugar en tiempo de compilación a través de la interfaz VTBL o en tiempo de ejecución a través IDispatchde .

  • Los controladores de automatización OLE que pueden usar la interfaz VTBL pueden beneficiarse de un rendimiento mejorado.

  • Los controladores de OLE Automation existentes que usan la IDispatch interfaz 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.

Agregar compatibilidad con Dual-Interface a una clase CCmdTarget-Based

Una interfaz dual es realmente una interfaz personalizada derivada de IDispatch. La manera más sencilla de implementar la compatibilidad con la interfaz dual en una CCmdTargetclase basada en es implementar primero la interfaz de distribución normal en la clase mediante MFC y ClassWizard y, a continuación, agregar la interfaz personalizada más adelante. En la mayor parte, la implementación de la interfaz personalizada simplemente se delegará en la implementación de MFC IDispatch .

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 interface, en lugar de la DISPINTERFACE instrucción que generan los asistentes de Visual C++. En lugar de quitar la instrucción existente DISPINTERFACE , agregue una nueva instrucción de interfaz. Al conservar el DISPINTERFACE formulario, puede seguir usando ClassWizard para agregar propiedades y métodos al objeto, pero debe agregar las propiedades y métodos equivalentes a la instrucción interface.

Una instrucción de interfaz para una interfaz dual debe tener los atributos OLEAUTOMATION y DUAL , y la interfaz debe derivarse de IDispatch. Puede usar el ejemplo GUIDGEN para crear un IID para la interfaz dual:

[ uuid(0BDD0E81-0DD7-11cf-BBA8-444553540000), // IID_IDualAClick
    oleautomation,
    dual
]
interface IDualAClick : IDispatch
    {
    };

Una vez que tenga la instrucción de interfaz en su lugar, empiece a agregar entradas para los métodos y propiedades. Para las interfaces duales, debe reorganizar las listas de parámetros para que los métodos y las funciones de descriptor de acceso de propiedad de la interfaz dual devuelvan un HRESULT y pasen sus valores devueltos como parámetros con los atributos [retval,out]. Recuerde que para las propiedades, deberá agregar una función de acceso de lectura (propget) y 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 interface en la instrucción coclass. 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 hacer las entradas correspondientes en el mecanismo de QueryInterface MFC. Necesita una entrada en el INTERFACE_PART bloque para cada entrada de la instrucción de interfaz del ODL, además de las entradas de una interfaz de distribución. 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 DUAL_INTERFACE_PART bloque a la definición de clase de 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 INTERFACE_PART entrada al mapa de interfaz:

BEGIN_INTERFACE_MAP(CAutoClickDoc, CDocument)
    INTERFACE_PART(CAutoClickDoc, DIID_IAClick, Dispatch)
    INTERFACE_PART(CAutoClickDoc, IID_IDualAClick, DualAClick)
END_INTERFACE_MAP()

A continuación, debe rellenar la implementación de la interfaz. En su mayor parte, podrá delegar en la implementación de MFC IDispatch 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);
}

Para los métodos del objeto y las funciones de descriptor de acceso de propiedad, debe rellenar la implementación. Por lo general, las funciones de método y propiedad pueden delegar de nuevo a los métodos generados mediante ClassWizard. Sin embargo, si configura propiedades para acceder directamente a variables, debe escribir el código para obtener o 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 de Dual-Interface

Pasar el puntero de doble interfaz no es sencillo, especialmente si necesita llamar CCmdTarget::FromIDispatcha . FromIDispatch solo funciona en los punteros de IDispatch MFC. Una manera de solucionar esto es consultar el puntero original IDispatch configurado por MFC y pasar ese puntero a las funciones que lo necesitan. 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 doble interfaz, es posible que deba convertirlo desde el puntero MFC IDispatch al puntero de doble interfaz. 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 OLE Automation con el sistema. Aunque hay otras maneras de registrar la biblioteca de tipos, es conveniente que la aplicación registre la biblioteca de tipos cuando actualiza su información de tipo OLE, es decir, siempre que la aplicación se ejecute de forma independiente.

Para registrar la biblioteca de tipos de la aplicación siempre que la aplicación se ejecute de forma independiente:

  • Incluya AFXCTL. H en el estándar incluye el archivo de encabezado STDAFX. H, para acceder a la definición de la AfxOleRegisterTypeLib función.

  • En la función de la InitInstance aplicación, busque la llamada a COleObjectFactory::UpdateRegistryAll. Después de esta llamada, agregue una llamada a AfxOleRegisterTypeLib, 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
    

Modificar la configuración de compilación del proyecto para dar cabida a los cambios de la biblioteca de tipos

Para modificar la configuración de compilación de un proyecto para que mkTypLib genere un archivo de encabezado que contenga definiciones UUID cada vez que se vuelva a generar la biblioteca de tipos:

  1. En el menú Compilar , haga clic en Configuración y, a continuación, seleccione el archivo ODL de la lista de archivos para cada configuración.

  2. Haga clic en la pestaña Tipos OLE y especifique un nombre de archivo en el campo Nombre de archivo de encabezado de salida . Use un nombre de archivo que no esté ya usado por 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:

  1. Incluya el archivo de encabezado generado por MkTypLib en el estándar incluye el archivo de encabezado stdafx.h.

  2. Cree un nuevo archivo, 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"
    
  3. En el menú Compilar , haga clic en Configuración y, a continuación, seleccione INITIIDS. CPP de la lista de archivos para cada configuración.

  4. 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.

Especificar el nombre de clase de objeto correcto en la biblioteca de tipos

Los asistentes enviados 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 OLE-creatables. Aunque esto funcionará, es probable que el nombre de la clase de implementación no sea el nombre de clase que desea que usen los usuarios del objeto. Para especificar el nombre correcto, abra el archivo ODL, busque cada instrucción coclase y reemplace el nombre de clase de implementación por el nombre externo correcto.

Tenga en cuenta que cuando se cambia la instrucción coclass, los nombres de variable de CLSIDen el archivo de encabezado generado por MkTypLib cambiarán en consecuencia. Tendrá que actualizar el código para usar los nuevos nombres de variable.

Control de excepciones y las interfaces de error de Automation

Los métodos y las funciones de descriptor de acceso de propiedades del objeto de automatización pueden producir excepciones. Si es así, debe controlarlos 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 OLE Automation, IErrorInfo. Esta interfaz proporciona información detallada sobre errores contextuales a través de IDispatch interfaces VTBL y detalladas. Para indicar que hay disponible un controlador de errores, debe implementar la ISupportErrorInfo interfaz .

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 MFC de IDispatch::Invoke normalmente detecta estas excepciones y las convierte en una estructura EXCEPTINFO que se devuelve a través de la Invoke llamada. Sin embargo, cuando se usa la interfaz VTBL, usted es responsable de detectar las excepciones usted mismo. Como ejemplo de protección de los métodos de doble interfaz:

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 OLE Automation mediante la ICreateErrorInfo interfaz . (Una macro de ejemplo CATCH_ALL_DUAL está en el archivo MFCDUAL. H en el 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 produjo:

  • COleDispatchException : en este caso, HRESULT se construye con el código siguiente:

    hr = MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, (e->m_wCode + 0x200));
    

    Esto crea una HRESULT interfaz específica de la interfaz que provocó la excepción. El código de error se desplaza por 0x200 para evitar conflictos con s definidos por HRESULTel sistema para interfaces OLE estándar.

  • CMemoryException : en este caso, E_OUTOFMEMORY se devuelve.

  • Cualquier otra excepción: en este caso, E_UNEXPECTED se devuelve.

Para indicar que se usa el controlador de errores de OLE Automation, también debe implementar la ISupportErrorInfo interfaz .

En primer lugar, agregue código a la definición de clase de automatización para mostrar 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 con el ISupportErrorInfo mecanismo de QueryInterface MFC. La INTERFACE_PART instrucción coincide con la clase definida para ISupportErrorInfo.

Por último, implemente la clase definida para admitir ISupportErrorInfo.

(El ejemplo de ACDUAL contiene tres macros para ayudar a realizar estos tres pasos, DECLARE_DUAL_ERRORINFO, DUAL_ERRORINFO_PARTy IMPLEMENT_DUAL_ERRORINFO, todos incluidos en MFCDUAL.H).)

En el ejemplo siguiente se implementa una clase definida para admitir ISupportErrorInfo. CAutoClickDoc es el nombre de la clase de automatización y IID_IDualAClick es el IID de la interfaz que es el 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;
}

Consulte también

Notas técnicas por número
Notas Técnicas por Categoría