TN039: implementação de automação MFC/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.

Visão geral da Interface de IDispatch OLE

A interface IDispatch é o meio pelo qual os aplicativos expõem métodos e propriedades de tal forma que outros aplicativos, como o Visual BASIC ou outras linguagens, possam usar os recursos do aplicativo. A parte mais importante dessa interface é a função IDispatch::Invoke. O MFC usa "mapas de expedição" para implementar IDispatch::Invoke. O mapa de expedição fornece as informações de implementação do MFC sobre o layout ou a "forma" de suas classes derivadas de CCmdTarget, de modo que ele possa manipular diretamente as propriedades do objeto ou chamar funções membro em seu objeto para atender às solicitações de IDispatch::Invoke.

Na maioria das vezes, o ClassWizard e o MFC cooperam para ocultar a maioria dos detalhes da automação OLE do programador de aplicativos. O programador se concentra na funcionalidade real a ser exposta no aplicativo e não precisa se preocupar com a infraestrutura subjacente.

Há casos, no entanto, em que é necessário entender o que o MFC está fazendo nos bastidores. Esta observação abordará como a estrutura atribui DISPIDs a funções membro e propriedades. O conhecimento do algoritmo usado pelo MFC para atribuir DISPIDs só é necessário quando você precisar saber as IDs, como quando você cria uma "biblioteca de tipos" para os objetos do aplicativo.

Atribuição DISPID do MFC

Embora o usuário final da automação (um usuário do Visual Basic, por exemplo), veja os nomes reais das propriedades e métodos habilitados para automação em seu código (como o obj.ShowWindow), a implementação de IDispatch::Invoke não recebe os nomes reais. Por motivos de otimização, ela recebe um DISPID, que é um "cookie mágico" de 32 bits que descreve o método ou a propriedade que deve ser acessado. Esses valores DISPID são retornados da implementação IDispatch por meio de outro método, chamado IDispatch::GetIDsOfNames. Um aplicativo cliente de automação chamará GetIDsOfNames uma vez para cada membro ou propriedade que pretende acessar e os armazenará em cache para chamadas posteriores a IDispatch::Invoke. Dessa forma, a pesquisa cara de cadeia de caracteres só é feita uma vez por uso de objeto, em vez de uma vez por chamada IDispatch::Invoke.

O MFC determina os DISPIDs para cada método e propriedade com base em duas coisas:

  • A distância da parte superior do mapa de expedição (1 relativo)

  • A distância do mapa de expedição da classe mais derivada (0 relativo)

O DISPID é dividido em duas partes. O LOWORD do DISPID contém o primeiro componente, a distância da parte superior do mapa de expedição. O HIWORD contém a distância da classe mais derivada. Por exemplo:

class CDispPoint : public CCmdTarget
{
public:
    short m_x, m_y;
    // ...
    DECLARE_DISPATCH_MAP()
    // ...
};

class CDisp3DPoint : public CDispPoint
{
public:
    short m_z;
    // ...
    DECLARE_DISPATCH_MAP()
    // ...
};

BEGIN_DISPATCH_MAP(CDispPoint, CCmdTarget)
    DISP_PROPERTY(CDispPoint, "x", m_x, VT_I2)
    DISP_PROPERTY(CDispPoint, "y", m_y, VT_I2)
END_DISPATCH_MAP()

BEGIN_DISPATCH_MAP(CDisp3DPoint, CDispPoint)
    DISP_PROPERTY(CDisp3DPoint, "z", m_z, VT_I2)
END_DISPATCH_MAP()

Como você pode ver, há duas classes, e ambas expõem interfaces de automação OLE. Uma dessas classes é derivada da outra e, portanto, aproveita a funcionalidade da classe base, incluindo a parte de automação OLE (propriedades "x" e "y" nesse caso).

O MFC gerará DISPIDs para a classe CDispPoint da seguinte maneira:

property X    (DISPID)0x00000001
property Y    (DISPID)0x00000002

Como as propriedades não estão em uma classe base, o HIWORD do DISPID é sempre zero (a distância da classe mais derivada do CDispPoint é zero).

O MFC gerará DISPIDs para a classe CDisp3DPoint da seguinte maneira:

property Z    (DISPID)0x00000001
property X    (DISPID)0x00010001
property Y    (DISPID)0x00010002

A propriedade Z recebe um DISPID com um HIWORD zero, pois é definida na classe que está expondo as propriedades, CDisp3DPoint. Como as propriedades X e Y são definidas em uma classe base, o HIWORD do DISPID é 1, uma vez que a classe na qual essas propriedades estão definidas está a uma distância de uma derivação da classe mais derivada.

Observação

O LOWORD é sempre determinado pela posição no mapa, mesmo se houver entradas no mapa com DISPID explícito (consulte a próxima seção para obter informações sobre as versões _ID das macros DISP_PROPERTY e DISP_FUNCTION).

Recursos avançados do mapa de expedição do MFC

Há uma série de recursos adicionais que o ClassWizard não dá suporte com essa versão do Visual C++. O ClassWizard dá suporte a DISP_FUNCTION, DISP_PROPERTY e DISP_PROPERTY_EX, que definem um método, uma propriedade de variável de membro e a propriedade de função membro get/set, respectivamente. Normalmente, esses recursos são tudo o que é necessário para criar a maioria dos servidores de automação.

As seguintes macros adicionais podem ser usadas quando as macros com suporte ClassWizard não são adequadas: DISP_PROPERTY_NOTIFY e DISP_PROPERTY_PARAM.

DISP_PROPERTY_NOTIFY — Descrição da macro

DISP_PROPERTY_NOTIFY(
    theClass,
    pszName,
    memberName,
    pfnAfterSet,
    vtPropType)

Parâmetros

theClass
Nome da classe.

pszName
Nome externo da propriedade.

memberName
Nome da variável de membro na qual a propriedade é armazenada.

pfnAfterSet
Nome da função membro a ser chamada quando a propriedade é alterada.

vtPropType
Um valor que especifica o tipo da propriedade.

Comentários

Essa macro é muito parecida com DISP_PROPERTY, exceto por aceitar um argumento adicional. O argumento adicional, pfnAfterSet, deve ser uma função membro que não retorna nada e não usa parâmetros, “void OnPropertyNotify()”. Ele será chamado depois que a variável de membro tiver sido modificada.

DISP_PROPERTY_PARAM — Descrição da macro

DISP_PROPERTY_PARAM(
    theClass,
    pszName,
    pfnGet,
    pfnSet,
    vtPropType,
    vtsParams)

Parâmetros

theClass
Nome da classe.

pszName
Nome externo da propriedade.

memberGet
Nome da função membro usada para obter a propriedade.

memberSet
Nome da função membro usada para definir a propriedade.

vtPropType
Um valor que especifica o tipo da propriedade.

vtsParams
Uma cadeia de caracteres de espaço separada VTS_ para cada parâmetro.

Comentários

Assim como a macro DISP_PROPERTY_EX, essa macro define uma propriedade acessada com funções membro Get e Set separadas. Essa macro, no entanto, permite que você especifique uma lista de parâmetros para a propriedade. Isso é útil para implementar propriedades indexadas ou parametrizadas de alguma outra forma. Os parâmetros sempre serão colocados primeiro, seguidos pelo novo valor da propriedade. Por exemplo:

DISP_PROPERTY_PARAM(CMyObject, "item", GetItem, SetItem, VT_DISPATCH, VTS_I2 VTS_I2)

corresponderia a funções membro get e set:

LPDISPATCH CMyObject::GetItem(short row, short col)
void CMyObject::SetItem(short row, short col, LPDISPATCH newValue)

DISP_XXXX_ID — Descrições de macro

DISP_FUNCTION_ID(
    theClass,
    pszName,
    dispid,
    pfnMember,
    vtRetVal,
    vtsParams)
DISP_PROPERTY_ID(
    theClass,
    pszName,
    dispid,
    memberName,
    vtPropType)
DISP_PROPERTY_NOTIFY_ID(
    theClass,
    pszName,
    dispid,
    memberName,
    pfnAfterSet,
    vtPropType)
DISP_PROPERTY_EX_ID(
    theClass,
    pszName,
    dispid,
    pfnGet,
    pfnSet,
    vtPropType)
DISP_PROPERTY_PARAM_ID(
    theClass,
    pszName,
    dispid,
    pfnGet,
    pfnSet,
    vtPropType,
    vtsParams)

Parâmetros

theClass
Nome da classe.

pszName
Nome externo da propriedade.

dispid
O DISPID fixo para a propriedade ou método.

pfnGet
Nome da função membro usada para obter a propriedade.

pfnSet
Nome da função membro usada para definir a propriedade.

memberName
O nome da variável membro a ser mapeada para a propriedade

vtPropType
Um valor que especifica o tipo da propriedade.

vtsParams
Uma cadeia de caracteres de espaço separada VTS_ para cada parâmetro.

Comentários

Essas macros permitem que você especifique um DISPID em vez de permitir que o MFC atribua um automaticamente. Essas macros avançadas têm os mesmos nomes, exceto que a ID é acrescentada ao nome da macro (por exemplo, DISP_PROPERTY_ID) e a ID é determinada pelo parâmetro especificado logo após o parâmetro pszName. Consulte AFXDISP.H para obter mais informações sobre essas macros. As entradas _ID devem ser colocadas no final do mapa de expedição. Eles afetarão a geração DISPID automática da mesma forma que uma versão não _ID da macro (os DISPIDs são determinados por posição). Por exemplo:

BEGIN_DISPATCH_MAP(CDisp3DPoint, CCmdTarget)
    DISP_PROPERTY(CDisp3DPoint, "y", m_y, VT_I2)
    DISP_PROPERTY(CDisp3DPoint, "z", m_z, VT_I2)
    DISP_PROPERTY_ID(CDisp3DPoint, "x", 0x00020003, m_x, VT_I2)
END_DISPATCH_MAP()

O MFC gerará DISPIDs para a classe CDisp3DPoint da seguinte maneira:

property X    (DISPID)0x00020003
property Y    (DISPID)0x00000002
property Z    (DISPID)0x00000001

Especificar um DISPID fixo é útil para manter a compatibilidade com versões anteriores para uma interface de expedição existente anteriormente ou para implementar determinados métodos ou propriedades definidos pelo sistema (geralmente indicados por um DISPID negativo, como a coleção DISPID_NEWENUM).

Recuperar a interface IDispatch para um COleClientItem

Muitos servidores darão suporte à automação em seus objetos de documento, juntamente com a funcionalidade do servidor OLE. Para obter acesso a essa interface de automação, é necessário acessar diretamente a variável de membro COleClientItem::m_lpObject. O código a seguir recuperará a interface IDispatch de um objeto derivado de COleClientItem. Você pode incluir o código abaixo em seu aplicativo se achar essa funcionalidade necessária:

LPDISPATCH CMyClientItem::GetIDispatch()
{
    ASSERT_VALID(this);
    ASSERT(m_lpObject != NULL);

    LPUNKNOWN lpUnk = m_lpObject;

    Run();      // must be running

    LPOLELINK lpOleLink = NULL;
    if (m_lpObject->QueryInterface(IID_IOleLink,
        (LPVOID FAR*)&lpOleLink) == NOERROR)
    {
        ASSERT(lpOleLink != NULL);
        lpUnk = NULL;
        if (lpOleLink->GetBoundSource(&lpUnk) != NOERROR)
        {
            TRACE0("Warning: Link is not connected!\n");
            lpOleLink->Release();
            return NULL;
        }
        ASSERT(lpUnk != NULL);
    }

    LPDISPATCH lpDispatch = NULL;
    if (lpUnk->QueryInterface(IID_IDispatch, &lpDispatch) != NOERROR)
    {
        TRACE0("Warning: does not support IDispatch!\n");
        return NULL;
    }

    ASSERT(lpDispatch != NULL);
    return lpDispatch;
}

A interface de expedição retornada dessa função pode ser usada diretamente ou anexada a um COleDispatchDriver para acesso fortemente tipado. Se você usá-lo diretamente, certifique-se de chamar seu membro Release quando estiver usando o ponteiro (o destruidor COleDispatchDriver faz isso por padrão).

Confira também

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