TN065: suporte para interface dupla para servidores de automação OLE

Observação

A nota técnica a seguir não foi atualizada desde que foi incluída pela primeira vez na documentação online. Como resultado, alguns procedimentos e tópicos podem estar desatualizados ou incorretos. Para obter as informações mais recentes, é recomendável que você pesquise o tópico de interesse no índice de documentação online.

Esta observação discute como adicionar suporte de interface dupla a um aplicativo para servidores de Automação OLE baseado em MFC. O exemplo ACDUAL ilustra o suporte à interface dupla e o código de exemplo nesta anotação é obtido de ACDUAL. As macros descritas nesta nota, como DECLARE_DUAL_ERRORINFO, DUAL_ERRORINFO_PART e IMPLEMENT_DUAL_ERRORINFO, fazem parte do exemplo ACDUAL e podem ser encontradas em MFCDUAL.H.

Interfaces duplas

Embora a Automação OLE permita que você implemente uma interface IDispatch, uma interface VTBL ou uma interface dupla (que abrange ambas), a Microsoft recomenda fortemente que você implemente interfaces duplas para todos os objetos expostos da Automação OLE. As interfaces duplas têm vantagens significativas em relação a interfaces com apenas IDispatch ou apenas VTBL:

  • A associação pode ocorrer no tempo de compilação por meio da interface VTBL ou em tempo de execução por meio de IDispatch.

  • Os controladores de Automação OLE que podem usar a interface VTBL podem se beneficiar de um desempenho aprimorado.

  • Os controladores de Automação OLE existentes que usam a interface IDispatch continuarão a funcionar.

  • A interface VTBL é mais fácil de chamar do C++.

  • As interfaces duplas são necessárias para compatibilidade com recursos de suporte a objetos do Visual Basic.

Adicionar suporte de interface dupla a uma classe baseada em CCmdTarget

Uma interface dupla é, na verdade, apenas uma interface personalizada derivada de IDispatch. A maneira mais simples de implementar o suporte de interface dupla em uma classe baseada em CCmdTarget é primeiro implementar a interface de expedição normal em sua classe usando MFC e ClassWizard e, em seguida, adicionar a interface personalizada mais tarde. Na maioria das vezes, sua implementação de interface personalizada simplesmente delegará de volta à implementação IDispatch do MFC.

Em primeiro lugar, modifique o arquivo ODL do servidor para definir interfaces duplas para seus objetos. Para definir uma interface dupla, você deve usar uma instrução de interface, em vez da instrução DISPINTERFACE gerada pelos assistentes do Visual C++. Em vez de remover a instrução DISPINTERFACE existente, adicione uma nova instrução de interface. Ao manter o formato DISPINTERFACE, você pode continuar a usar ClassWizard para adicionar propriedades e métodos ao objeto, mas deve adicionar as propriedades e métodos equivalentes à instrução de interface.

Uma instrução de interface para uma interface dupla deve ter os atributos OLEAUTOMATION e DUAL e a interface deve ser derivada de IDispatch. Você pode usar o exemplo GUIDGEN para criar um IID para a interface dupla:

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

Depois de ter a instrução de interface em vigor, comece a adicionar entradas para os métodos e as propriedades. Para interfaces duplas, você precisa reorganizar as listas de parâmetros para que suas funções de acessador de propriedade e métodos na interface dupla retornem um HRESULT e passem seus valores retornados como parâmetros com os atributos [retval,out]. Lembre-se de que, para propriedades, você precisará adicionar uma função de acesso de leitura (propget) e gravação (propput) com a mesma ID. Por exemplo:

[propput, id(1)] HRESULT text([in] BSTR newText);
[propget, id(1)] HRESULT text([out, retval] BSTR* retval);

Depois que seus métodos e propriedades forem definidos, você precisará adicionar uma referência à instrução de interface em sua instrução coclass. Por exemplo:

[ uuid(4B115281-32F0-11cf-AC85-444553540000) ]
coclass Document
{
    dispinterface IAClick;
    [default] interface IDualAClick;
};

Depois que o arquivo ODL for atualizado, use o mecanismo de mapa de interface do MFC para definir uma classe de implementação para a interface dupla na classe de objeto e fazer as entradas correspondentes no mecanismo QueryInterface do MFC. Você precisa de uma entrada no bloco INTERFACE_PART para cada entrada na instrução de interface do ODL, além das entradas de uma interface de expedição. Cada entrada ODL com o atributo propput precisa de uma função nomeada como put_propertyname. Cada entrada ODL com o atributo propget precisa de uma função nomeada como get_propertyname.

Para definir uma classe de implementação para sua interface dupla, adicione um bloco DUAL_INTERFACE_PART à sua definição de classe de objeto. Por exemplo:

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 a interface dupla ao mecanismo QueryInterface do MFC, adicione uma entrada INTERFACE_PART ao mapa da interface:

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

Em seguida, você precisará preencher a implementação da interface. Na maior parte do tempo, você poderá delegar à implementação IDispatch do MFC existente. Por exemplo:

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 as funções do acessador de propriedade e métodos do objeto, você precisa preencher a implementação. Suas funções de propriedade e método geralmente podem delegar de volta aos métodos gerados usando ClassWizard. No entanto, se você configurar propriedades para acessar variáveis diretamente, precisará escrever o código para obter/colocar o valor na variável. Por exemplo:

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;
}

Passar ponteiros de interface dupla

Passar o ponteiro de interface dupla não é simples, especialmente se você precisar chamar CCmdTarget::FromIDispatch. FromIDispatch só funciona nos ponteiros IDispatch do MFC. Um modo de contornar isso é consultar o ponteiro IDispatch original configurado pelo MFC e passar esse ponteiro para as funções que precisam dele. Por exemplo:

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 passar um ponteiro de volta pelo método de interface dupla, talvez seja necessário convertê-lo do ponteiro IDispatch do MFC para o ponteiro de interface dupla. Por exemplo:

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;
}

Registrar a biblioteca de tipos do aplicativo

O AppWizard não gera código para registrar a biblioteca de tipos de um aplicativo para servidores de Automação OLE com o sistema. Embora haja outras maneiras de registrar a biblioteca de tipos, é conveniente que o aplicativo registre a biblioteca de tipos quando estiver atualizando suas informações de tipo OLE, ou seja, sempre que o aplicativo for executado em modo autônomo.

Para registrar a biblioteca de tipos do aplicativo sempre que o aplicativo for executado em modo autônomo:

  • Incluir AFXCTL.H em seu padrão adiciona o arquivo de cabeçalho, STDAFX.H, para acessar a definição da função AfxOleRegisterTypeLib.

  • Na função InitInstance do aplicativo, localize a chamada para COleObjectFactory::UpdateRegistryAll. Após essa chamada, adicione uma chamada a AfxOleRegisterTypeLib, especificando o LIBID correspondente à biblioteca de tipos, juntamente com o nome da 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 as configurações de build do projeto para acomodar alterações de biblioteca de tipos

Para modificar as configurações de build de um projeto para que um arquivo de cabeçalho contendo definições UUID seja gerado pelo MkTypLib sempre que a biblioteca de tipos for reconstruída:

  1. No menu Criar, clique em Configurações e selecione o arquivo ODL na lista de arquivos para cada configuração.

  2. Clique na guia Tipos OLE e especifique um nome de arquivo no campo de nome do arquivo do Cabeçalho de saída. Use um nome de arquivo que ainda não seja usado pelo seu projeto, pois o MkTypLib substituirá qualquer arquivo existente. Clique em OK para fechar a caixa de diálogo Configurações de Build.

Para adicionar as definições UUID do arquivo de cabeçalho gerado por MkTypLib ao seu projeto:

  1. Inclua o arquivo de cabeçalho gerado por MkTypLib em seu arquivo de cabeçalho padrão, stdafx.h.

  2. Crie um arquivo, INITIIDS.CPP e adicione-o ao seu projeto. Nesse arquivo, inclua o arquivo de cabeçalho gerado por MkTypLib depois 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. No menu Criar, clique em Configurações e selecione INITIIDS.CPP na lista de arquivos para cada configuração.

  4. Clique na guia C++, clique na categoria Cabeçalhos Pré-compilados e selecione o botão de opção Não usar cabeçalhos pré-compilados. Clique em OK para fechar a caixa de diálogo Configurações de Build.

Especificar o nome de classe de objeto correto na biblioteca de tipos

Os assistentes enviados com o Visual C++ usam incorretamente o nome de classe de implementação para especificar o coclass no arquivo ODL do servidor para classes que podem ser criadas pelo OLE. Embora isso funcione, o nome de classe de implementação provavelmente não é o nome de classe que você deseja que os usuários do objeto usem. Para especificar o nome correto, abra o arquivo ODL, localize cada instrução coclass e substitua o nome de classe de implementação pelo nome externo correto.

Observe que, quando a instrução coclass for alterada, os nomes de variáveis de CLSIDs no arquivo de cabeçalho gerado por MkTypLib serão alterados adequadamente. Você precisará atualizar seu código para usar os novos nomes de variáveis.

Manipular exceções e interfaces de erro de automação

As funções do acessador de propriedade e métodos do objeto de automação podem gerar exceções. Nesse caso, você deve tratá-los em sua implementação de interface dupla e passar informações sobre a exceção de volta para o controlador por meio da interface de tratamento de erros da Automação OLE, IErrorInfo. Essa interface fornece informações detalhadas de erro contextual por meio de interfaces VTBL e IDispatch. Para indicar que um manipulador de erros está disponível, você deve implementar a interface ISupportErrorInfo.

Para ilustrar o mecanismo de tratamento de erros, suponha que as funções geradas por ClassWizard usadas para implementar o suporte de expedição padrão geram exceções. A implementação de IDispatch::Invoke do MFC normalmente captura essas exceções e as converte em uma estrutura EXCEPTINFO retornada por meio da chamada Invoke. No entanto, quando a interface VTBL é usada, você é responsável por capturar as exceções por conta própria. Como exemplo de proteção dos métodos de interface dupla:

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 cuida do retorno do código de erro correto quando ocorre uma exceção. CATCH_ALL_DUAL converte uma exceção do MFC em informações de tratamento de erros do Automação OLE usando a interface ICreateErrorInfo. (Uma macro CATCH_ALL_DUAL de exemplo está no arquivo MFCDUAL. H no exemplo ACDUAL. A função que ele chama para lidar com exceções, DualHandleException, está no arquivo MFCDUAL.CPP.) CATCH_ALL_DUAL determina o código de erro a ser retornado com base no tipo de exceção que ocorreu:

  • COleDispatchException: nesse caso, HRESULT é construído usando o seguinte código:

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

    Isso cria um HRESULT específico para a interface que causou a exceção. O código de erro é deslocado por 0x200 para evitar conflitos com HRESULTs definidos pelo sistema para interfaces OLE padrão.

  • CMemoryException: nesse caso, E_OUTOFMEMORY é retornado.

  • Qualquer outra exceção: nesse caso, E_UNEXPECTED é retornado.

Para indicar que o manipulador de erros de Automação OLE é usado, você também deve implementar a interface ISupportErrorInfo.

Em primeiro lugar, adicione código à sua definição de classe de automação para mostrar que ela dá suporte a ISupportErrorInfo.

Em segundo, adicione código ao mapa de interface da classe de automação para associar a classe de implementação ISupportErrorInfo ao mecanismo QueryInterface do MFC. A instrução INTERFACE_PART corresponde à classe definida para ISupportErrorInfo.

Por fim, implemente a classe definida para dar suporte a ISupportErrorInfo.

(O exemplo ACDUAL contém três macros para ajudar a executar essas três etapas, DECLARE_DUAL_ERRORINFO, DUAL_ERRORINFO_PART e IMPLEMENT_DUAL_ERRORINFO, todas contidas em MFCDUAL.H.)

O exemplo a seguir implementa uma classe definida para dar suporte a ISupportErrorInfo. CAutoClickDoc é o nome da classe de automação e IID_IDualAClick é o IID da interface que é a fonte de erros relatados por meio do objeto de erro de Automação 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;
}

Confira também

Observações técnicas por número
Observações técnicas por categoria