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