TN038: implementação de IUnknown 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.

No coração do OLE 2 está o "Modelo de Objeto do Componente OLE" ou COM. O COM define um padrão de como os objetos de cooperação se comunicam entre si. Isso inclui os detalhes da aparência de um "objeto", incluindo como os métodos são enviados em um objeto. O COM também define uma classe base, da qual todas as classes compatíveis com COM são derivadas. Esta classe base é IUnknown. Embora a interface IUnknown seja conhecida como uma classe C++, COM não é específico de nenhuma linguagem, ele pode ser implementado em C, PASCAL ou qualquer outra linguagem compatível com o layout binário de um objeto COM.

O OLE se refere a todas as classes derivadas de IUnknown como "interfaces". Essa é uma distinção importante, uma vez que uma "interface" como IUnknown não carrega com ela nenhuma implementação. Ela simplesmente define o protocolo pelo qual os objetos se comunicam, não as especificidades do que essas implementações fazem. Isso é razoável para um sistema que permite a máxima flexibilidade. É trabalho do MFC implementar um comportamento padrão para programas MFC/C++.

Para entender a implementação do IUnknown do MFC, é preciso entender o que é essa interface. Uma versão simplificada do IUnknown é definida abaixo:

class IUnknown
{
public:
    virtual HRESULT QueryInterface(REFIID iid, void** ppvObj) = 0;
    virtual ULONG AddRef() = 0;
    virtual ULONG Release() = 0;
};

Observação

Determinados detalhes necessários da convenção de chamada, como __stdcall, são deixados de fora para esta ilustração.

As funções membro AddRef e Release controlam o gerenciamento de memória do objeto. O COM usa um esquema de contagem de referência para controlar objetos. Um objeto nunca é referenciado diretamente como você faria no C++. Em vez disso, os objetos COM são sempre referenciados por meio de um ponteiro. Para liberar o objeto quando o proprietário terminar de usá-lo, o membro Release do objeto é chamado (em vez de usar a exclusão do operador, como seria feito para um objeto C++ tradicional). O mecanismo de contagem de referência permite que várias referências a um só objeto sejam gerenciadas. Uma implementação de AddRef e Release mantém uma contagem de referência no objeto – o objeto não é excluído até que sua contagem de referência atinja zero.

AddRef e Release são bastante simples do ponto de vista da implementação. Aqui está uma implementação trivial:

ULONG CMyObj::AddRef()
{
    return ++m_dwRef;
}

ULONG CMyObj::Release()
{
    if (--m_dwRef == 0)
    {
        delete this;
        return 0;
    }
    return m_dwRef;
}

A função de membro QueryInterface é um pouco mais interessante. Não é muito interessante ter um objeto cujas únicas funções de membro são AddRef e Release. Seria bom dizer ao objeto para fazer mais coisas do que o IUnknown fornece. É aqui que a QueryInterface é útil. Ela permite que você obtenha uma "interface" diferente no mesmo objeto. Essas interfaces geralmente são derivadas do IUnknown e incluem funcionalidades extras adicionando novas funções de membro. As interfaces COM nunca têm variáveis de membro declaradas na interface e todas as funções de membro são declaradas como puras-virtuais. Por exemplo,

class IPrintInterface : public IUnknown
{
public:
    virtual void PrintObject() = 0;
};

Para obter um IPrintInterface se você tiver apenas um IUnknown, chame QueryInterface usando o IID de IPrintInterface. Um IID é um número de 128 bits que identifica exclusivamente a interface. Há uma interface IID para cada interface que você ou OLE definem. Se pUnk for um ponteiro para um objeto IUnknown, o código para recuperar um IPrintInterface dele poderá ser:

IPrintInterface* pPrint = NULL;
if (pUnk->QueryInterface(IID_IPrintInterface, (void**)&pPrint) == NOERROR)
{
    pPrint->PrintObject();
    pPrint->Release();
    // release pointer obtained via QueryInterface
}

Isso parece bastante fácil, mas como você implementaria um objeto que dá suporte à interface IPrintInterface e IUnknown? Nesse caso, é simples, pois o IPrintInterface deriva diretamente de IUnknown: ao implementar IPrintInterface, automaticamente há suporte para IUnknown. Por exemplo:

class CPrintObj : public CPrintInterface
{
    virtual HRESULT QueryInterface(REFIID iid, void** ppvObj);
    virtual ULONG AddRef();
    virtual ULONG Release();
    virtual void PrintObject();
};

As implementações de AddRef e Release seriam exatamente as mesmas implementadas acima. CPrintObj::QueryInterface teria uma aparência como esta:

HRESULT CPrintObj::QueryInterface(REFIID iid, void FAR* FAR* ppvObj)
{
    if (iid == IID_IUnknown || iid == IID_IPrintInterface)
    {
        *ppvObj = this;
        AddRef();
        return NOERROR;
    }
    return E_NOINTERFACE;
}

Como você pode ver, se o IID (identificador de interface) for reconhecido, um ponteiro será retornado ao seu objeto; caso contrário, ocorrerá um erro. Observe também que uma QueryInterface bem-sucedida resulta em uma AddRef implícita. Claro, você também teria que implementar CEditObj::Print. Isso é simples porque a IPrintInterface foi derivada diretamente da interface IUnknown. No entanto, se você quiser dar suporte a duas interfaces diferentes, ambas derivadas de IUnknown, considere o seguinte:

class IEditInterface : public IUnkown
{
public:
    virtual void EditObject() = 0;
};

Embora haja várias maneiras de implementar uma classe que dá suporte a IEditInterface e IPrintInterface, incluindo o uso de herança múltipla C++, essa anotação se concentrará no uso de classes aninhadas para implementar essa funcionalidade.

class CEditPrintObj
{
public:
    CEditPrintObj();

    HRESULT QueryInterface(REFIID iid, void**);
    ULONG AddRef();
    ULONG Release();
    DWORD m_dwRef;

    class CPrintObj : public IPrintInterface
    {
    public:
        CEditPrintObj* m_pParent;
        virtual HRESULT QueryInterface(REFIID iid, void** ppvObj);
        virtual ULONG AddRef();
        virtual ULONG Release();
    } m_printObj;

    class CEditObj : public IEditInterface
    {
    public:
        CEditPrintObj* m_pParent;
        virtual ULONG QueryInterface(REFIID iid, void** ppvObj);
        virtual ULONG AddRef();
        virtual ULONG Release();
    } m_editObj;
};

Toda a implementação está incluída abaixo:

CEditPrintObj::CEditPrintObj()
{
    m_editObj.m_pParent = this;
    m_printObj.m_pParent = this;
}

ULONG CEditPrintObj::AddRef()
{
    return ++m_dwRef;
}

CEditPrintObj::Release()
{
    if (--m_dwRef == 0)
    {
        delete this;
        return 0;
    }
    return m_dwRef;
}

HRESULT CEditPrintObj::QueryInterface(REFIID iid, void** ppvObj)
{
    if (iid == IID_IUnknown || iid == IID_IPrintInterface)
    {
        *ppvObj = &m_printObj;
        AddRef();
        return NOERROR;
    }
    else if (iid == IID_IEditInterface)
    {
        *ppvObj = &m_editObj;
        AddRef();
        return NOERROR;
    }
    return E_NOINTERFACE;
}

ULONG CEditPrintObj::CEditObj::AddRef()
{
    return m_pParent->AddRef();
}

ULONG CEditPrintObj::CEditObj::Release()
{
    return m_pParent->Release();
}

HRESULT CEditPrintObj::CEditObj::QueryInterface(REFIID iid, void** ppvObj)
{
    return m_pParent->QueryInterface(iid, ppvObj);
}

ULONG CEditPrintObj::CPrintObj::AddRef()
{
    return m_pParent->AddRef();
}

ULONG CEditPrintObj::CPrintObj::Release()
{
    return m_pParent->Release();
}

HRESULT CEditPrintObj::CPrintObj::QueryInterface(REFIID iid, void** ppvObj)
{
    return m_pParent->QueryInterface(iid, ppvObj);
}

Observe que a maior parte da implementação IUnknown é colocada na classe CEditPrintObj em vez de duplicar o código em CEditPrintObj::CEditObj e CEditPrintObj::CPrintObj. Isso reduz a quantidade de código e evita bugs. O ponto crucial aqui é que, por meio da interface IUnknown, é possível chamar QueryInterface para recuperar qualquer interface que o objeto possa dar suporte, e de cada uma dessas interfaces pode fazer o mesmo. Isso significa que todas as funções QueryInterface disponíveis de cada interface devem se comportar exatamente da mesma maneira. Para que esses objetos inseridos chamem a implementação no "objeto externo", um ponteiro para trás é usado (m_pParent). O ponteiro m_pParent é inicializado durante o construtor CEditPrintObj. Em seguida, você implementaria também CEditPrintObj::CPrintObj::PrintObject e CEditPrintObj::CEditObj::EditObject. Um pouco de código foi incluído para adicionar um recurso: a capacidade de editar o objeto. Felizmente, é bastante incomum que as interfaces tenham apenas uma função de membro (embora isso aconteça) e, nesse caso, EditObject e PrintObject normalmente seriam combinados em uma só interface.

Isso é muita explicação e muito código para um cenário tão simples. As classes MFC/OLE fornecem uma alternativa mais simples. A implementação do MFC usa uma técnica semelhante à maneira como as mensagens do Windows são encapsuladas com Mapas de Mensagens. Isso se chama Mapas de Interface, que são discutidos na próxima seção.

Mapas de interface MFC

O MFC/OLE inclui uma implementação de "Mapas de Interface" semelhante aos "Mapas de Mensagens" e "Mapas de Expedição" do MFC em conceito e execução. Os principais recursos dos Mapas de Interface do MFC são:

  • Uma implementação padrão de IUnknown, incorporada à classe CCmdTarget.

  • Manutenção da contagem de referência, modificada por AddRef e Release

  • Implementação controlada por dados de QueryInterface

Além disso, os mapas de interface dão suporte aos seguintes recursos avançados:

  • Suporte para a criação de objetos COM agregáveis

  • Suporte para o uso de objetos agregados na implementação de um objeto COM

  • A implementação é engatável e extensível

Para mais informações sobre agregação, confira o tópico Agregação.

O suporte ao mapa de interface do MFC está enraizado na classe CCmdTarget. Contagem de referência CCmdTarget "has-a", bem como todas as funções de membro associadas à implementação IUnknown (a contagem de referência, por exemplo, está em CCmdTarget). Para criar uma classe que dê suporte ao OLE COM, você deriva uma classe CCmdTarget e usa várias macros, bem como funções de membro de CCmdTarget para implementar as interfaces desejadas. A implementação do MFC usa classes aninhadas para definir cada implementação de interface, de modo muito semelhante ao exemplo acima. Isso é facilitado com uma implementação padrão de IUnknown, bem como uma série de macros que eliminam parte do código repetitivo.

Noções básicas do mapa de interface

Para implementar uma classe usando os mapas de interface do MFC

  1. Derive uma classe direta ou indiretamente de CCmdTarget.

  2. Use a função DECLARE_INTERFACE_MAP na definição de classe derivada.

  3. Para cada interface que você deseja dar suporte, use as macros BEGIN_INTERFACE_PART e END_INTERFACE_PART na definição de classe.

  4. No arquivo de implementação, use as macros BEGIN_INTERFACE_MAP e END_INTERFACE_MAP para definir o mapa da interface da classe.

  5. Para cada IID com suporte, use a macro INTERFACE_PART entre as macros BEGIN_INTERFACE_MAP e END_INTERFACE_MAP para mapear essa IID para uma "parte" específica da classe.

  6. Implemente cada uma das classes aninhadas que representam as interfaces compatíveis.

  7. Use a macro METHOD_PROLOGUE para acessar o objeto pai derivado CCmdTarget.

  8. AddRef, Release e QueryInterface podem delegar à implementação de CCmdTarget destas funções (ExternalAddRef, ExternalRelease e ExternalQueryInterface).

O exemplo CPrintEditObj acima pode ser implementado da seguinte maneira:

class CPrintEditObj : public CCmdTarget
{
public:
    // member data and member functions for CPrintEditObj go here

// Interface Maps
protected:
    DECLARE_INTERFACE_MAP()

    BEGIN_INTERFACE_PART(EditObj, IEditInterface)
        STDMETHOD_(void, EditObject)();
    END_INTERFACE_PART(EditObj)

    BEGIN_INTERFACE_PART(PrintObj, IPrintInterface)
        STDMETHOD_(void, PrintObject)();
    END_INTERFACE_PART(PrintObj)
};

A declaração acima cria uma classe derivada de CCmdTarget. A macro DECLARE_INTERFACE_MAP informa à estrutura que essa classe terá um mapa de interface personalizado. Além disso, as macros BEGIN_INTERFACE_PART e END_INTERFACE_PART definem classes aninhadas, nesse caso com nomes CEditObj e CPrintObj (o X é usado apenas para diferenciar as classes aninhadas de classes globais que começam com "C" e classes de interface que começam com "I"). Dois membros aninhados dessas classes são criados: m_CEditObj e m_CPrintObj, respectivamente. As macros declaram automaticamente as funções AddRef, Release e QueryInterface. Portanto, você declara apenas as funções específicas para essa interface: EditObject e PrintObject (a macro OLE STDMETHOD é usada de modo que _stdcall e palavras-chave virtuais sejam fornecidas conforme apropriado para a plataforma de destino).

Para implementar o mapa da interface para esta classe:

BEGIN_INTERFACE_MAP(CPrintEditObj, CCmdTarget)
    INTERFACE_PART(CPrintEditObj, IID_IPrintInterface, PrintObj)
    INTERFACE_PART(CPrintEditObj, IID_IEditInterface, EditObj)
END_INTERFACE_MAP()

Isso conecta a IID IID_IPrintInterface com a m_CPrintObj e a IID_IEditInterface com a m_CEditObj, respectivamente. A implementação CCmdTarget de QueryInterface (CCmdTarget::ExternalQueryInterface) usa esse mapa para retornar ponteiros para m_CPrintObj e m_CEditObj quando solicitado. Não é necessário incluir uma entrada para IID_IUnknown; a estrutura usará a primeira interface no mapa (nesse caso, m_CPrintObj) quando IID_IUnknown for solicitado.

Embora a macro BEGIN_INTERFACE_PART tenha declarado automaticamente as funções AddRef, Release e QueryInterface para você, você ainda precisa implementá-las:

ULONG FAR EXPORT CEditPrintObj::XEditObj::AddRef()
{
    METHOD_PROLOGUE(CEditPrintObj, EditObj)
    return pThis->ExternalAddRef();
}

ULONG FAR EXPORT CEditPrintObj::XEditObj::Release()
{
    METHOD_PROLOGUE(CEditPrintObj, EditObj)
    return pThis->ExternalRelease();
}

HRESULT FAR EXPORT CEditPrintObj::XEditObj::QueryInterface(
    REFIID iid,
    void FAR* FAR* ppvObj)
{
    METHOD_PROLOGUE(CEditPrintObj, EditObj)
    return (HRESULT)pThis->ExternalQueryInterface(&iid, ppvObj);
}

void FAR EXPORT CEditPrintObj::XEditObj::EditObject()
{
    METHOD_PROLOGUE(CEditPrintObj, EditObj)
    // code to "Edit" the object, whatever that means...
}

A implementação de CEditPrintObj::CPrintObj seria semelhante às definições acima para CEditPrintObj::CEditObj. Embora seja possível criar uma macro que possa ser usada para gerar automaticamente essas funções (mas antes, no desenvolvimento MFC/OLE, esse era o caso), torna-se difícil definir pontos de interrupção quando uma macro gera mais de uma linha de código. Por esse motivo, esse código é expandido manualmente.

Usando a implementação da estrutura de mapas de mensagens, não era necessário fazer várias coisas:

  • Implementar QueryInterface

  • Implementar AddRef e Release

  • Declarar qualquer um desses métodos internos em ambas as interfaces

Além disso, a estrutura usa mapas de mensagens internamente. Isso permite derivar de uma classe de estrutura, digamos COleServerDoc, que já dá suporte a determinadas interfaces e fornece substituições ou adições às interfaces fornecidas pela estrutura. Você pode fazer isso porque a estrutura é totalmente compatível com a herança de um mapa de interface de uma classe base. Essa é a razão pela qual BEGIN_INTERFACE_MAP usa como segundo parâmetro o nome da classe base.

Observação

Em geral, não é possível reutilizar a implementação das implementações internas do MFC das interfaces OLE apenas herdando a especialização inserida dessa interface da versão MFC. Isso não é possível porque usar a macro METHOD_PROLOGUE para obter acesso ao objeto derivado de CCmdTarget contentor implica um deslocamento fixo do objeto inserido do objeto derivado de CCmdTarget. Isso significa, por exemplo, que você não pode derivar um XMyAdviseSink inserido da implementação do MFC em COleClientItem::XAdviseSink, pois o XAdviseSink depende de estar em um deslocamento específico da parte superior do objeto COleClientItem.

Observação

No entanto, você pode delegar à implementação do MFC para todas as funções em que deseja o comportamento padrão do MFC. Isso é feito na implementação MFC de IOleInPlaceFrame (XOleInPlaceFrame) na classe COleFrameHook (delega a m_xOleInPlaceUIWindow para muitas funções). Esse design foi escolhido para reduzir o tamanho do runtime de objetos que implementam muitas interfaces; elimina a necessidade de um ponteiro traseiro (como a maneira como m_pParent foi usado na seção anterior).

Mapas de agregação e interface

Além de dar suporte a objetos COM autônomos, o MFC também dá suporte à agregação. A agregação em si é um tópico muito complexo para discutir aqui. Confira o tópico Agregação para mais informações sobre agregação. Esta observação simplesmente descreverá o suporte à agregação incorporada aos mapas de estrutura e interface.

Há duas maneiras de usar a agregação: (1) usar um objeto COM que dá suporte à agregação; e (2) implementar um objeto que pode ser agregado por outro. Esses recursos podem ser chamados de "usando um objeto agregado" e "tornando um objeto agregável". O MFC dá suporte a ambos.

Como usar um objeto de agregação

Para usar um objeto de agregação, é necessário haver algum modo de vincular a agregação ao mecanismo QueryInterface. Em outras palavras, o objeto agregado deve se comportar como se fosse uma parte nativa do objeto. Como isso se relaciona ao mecanismo de mapa de interface do MFC? Além da macro INTERFACE_PART, em que um objeto aninhado é mapeado para uma IID, você também pode declarar um objeto agregado como parte de sua classe derivada de CCmdTarget. Para fazer isso, a macro INTERFACE_AGGREGATE é usada. Isso permite que você especifique uma variável de membro (que deve ser um ponteiro para um IUnknown ou uma classe derivada), que deve ser integrada ao mecanismo de mapa de interface. Se o ponteiro não for NULL quando CCmdTarget::ExternalQueryInterface for chamado, a estrutura chamará automaticamente a função de membro QueryInterface do objeto agregado, se o IID solicitado não for um dos IID nativos com suporte do objeto CCmdTarget em si.

Para usar a macro INTERFACE_AGGREGATE

  1. Declare uma variável de membro (um IUnknown*) que conterá um ponteiro para o objeto agregado.

  2. Inclua uma macro INTERFACE_AGGREGATE no mapa da interface, que se refere à variável de membro pelo nome.

  3. Em algum momento (geralmente durante CCmdTarget::OnCreateAggregates), inicialize a variável de membro para algo diferente de NULL.

Por exemplo:

class CAggrExample : public CCmdTarget
{
public:
    CAggrExample();

protected:
    LPUNKNOWN m_lpAggrInner;
    virtual BOOL OnCreateAggregates();

    DECLARE_INTERFACE_MAP()
    // "native" interface part macros may be used here
};

CAggrExample::CAggrExample()
{
    m_lpAggrInner = NULL;
}

BOOL CAggrExample::OnCreateAggregates()
{
    // wire up aggregate with correct controlling unknown
    m_lpAggrInner = CoCreateInstance(CLSID_Example,
        GetControllingUnknown(), CLSCTX_INPROC_SERVER,
        IID_IUnknown, (LPVOID*)&m_lpAggrInner);

    if (m_lpAggrInner == NULL)
        return FALSE;
    // optionally, create other aggregate objects here
    return TRUE;
}

BEGIN_INTERFACE_MAP(CAggrExample, CCmdTarget)
    // native "INTERFACE_PART" entries go here
    INTERFACE_AGGREGATE(CAggrExample, m_lpAggrInner)
END_INTERFACE_MAP()

A variável m_lpAggrInner é inicializada no construtor para NULL. A estrutura ignora uma variável de membro NULL na implementação padrão de QueryInterface. OnCreateAggregates é um bom lugar para realmente criar seus objetos agregados. Você precisará chamá-lo explicitamente se estiver criando o objeto fora da implementação do MFC de COleObjectFactory. O motivo para criar agregações em CCmdTarget::OnCreateAggregates, bem como o uso de CCmdTarget::GetControllingUnknown, se tornará aparente quando a criação de objetos agregáveis for discutida.

Essa técnica fornecerá ao objeto todas as interfaces compatíveis com o objeto agregado e suas interfaces nativas. Se você quiser apenas um subconjunto das interfaces compatíveis com a agregação, poderá substituir CCmdTarget::GetInterfaceHook. Isso permite que você tenha uma conexão de nível muito baixo, semelhante a QueryInterface. Normalmente, você deseja todas as interfaces compatíveis com a agregação.

Como tornar uma implementação de objeto agregável

Para que um objeto seja agregável, a implementação de AddRef, Release e QueryInterface deve delegar a um "controle desconhecido". Em outras palavras, para que ele faça parte do objeto, ele deve delegar AddRef, Release e QueryInterface a um objeto diferente, também derivado de IUnknown. Esse "controle desconhecido" é fornecido ao objeto quando ele é criado, ou seja, ele é fornecido para a implementação de COleObjectFactory. Implementar isso acarreta uma pequena quantidade de sobrecarga e, em alguns casos, não é desejável, portanto, o MFC torna isso opcional. Para permitir que um objeto seja agregável, você chama CCmdTarget::EnableAggregation do construtor do objeto.

Se o objeto também usa agregações, passe o "controle desconhecido" correto para os objetos de agregação. Em geral, esse ponteiro IUnknown é passado para o objeto quando a agregação é criada. Por exemplo, o parâmetro pUnkOuter é o "controlador desconhecido" para objetos criados com CoCreateInstance. O ponteiro "controlador desconhecido" correto pode ser recuperado chamando CCmdTarget::GetControllingUnknown. O valor retornado dessa função, no entanto, não é válido durante o construtor. Por esse motivo, sugere-se que você crie suas agregações somente em uma substituição de CCmdTarget::OnCreateAggregates, em que o valor retornado GetControllingUnknown é confiável, mesmo que criado com base na implementação de COleObjectFactory.

Também é importante que o objeto manipule a contagem de referência correta ao adicionar ou liberar contagens de referência artificial. Para garantir que esse seja o caso, sempre chame ExternalAddRef e ExternalRelease em vez de InternalRelease e InternalAddRef. É raro chamar InternalRelease ou InternalAddRef em uma classe que dá suporte à agregação.

Material de referência

O uso avançado do OLE, como definir interfaces próprias ou substituir a implementação da estrutura das interfaces OLE, requer o uso do mecanismo de mapa de interface subjacente.

Esta seção discute cada macro e as APIs usadas para implementar esses recursos avançados.

CCmdTarget::EnableAggregation, descrição da função

void EnableAggregation();

Comentários

Chame essa função no construtor da classe derivada para dar suporte à agregação OLE para objetos desse tipo. Isso prepara uma implementação especial do IUnknown necessária para objetos agregáveis.

CCmdTarget::ExternalQueryInterface, descrição da função

DWORD ExternalQueryInterface(
    const void FAR* lpIID,
    LPVOIDFAR* ppvObj
);

Parâmetros

lpIID
Um ponteiro distante para um IID (o primeiro argumento para QueryInterface)

ppvObj
Um ponteiro para um IUnknown* (segundo argumento para QueryInterface)

Comentários

Chame essa função na implementação do IUnknown para cada interface que sua classe implementa. Essa função fornece a implementação padrão controlada por dados de QueryInterface com base no mapa de interface do objeto. É necessário converter o valor retornado em um HRESULT. Se o objeto for agregado, essa função chamará o "controle IUnknown" em vez de usar o mapa da interface local.

CCmdTarget::ExternalAddRef, descrição da função

DWORD ExternalAddRef();

Comentários

Chame essa função em sua implementação de IUnknown::AddRef para cada interface que sua classe implementa. O valor retornado é a nova contagem de referência no objeto CCmdTarget. Se o objeto for agregado, essa função chamará o "controlling IUnknown" em vez de manipular a contagem de referência local.

CCmdTarget::ExternalRelease, descrição da função

DWORD ExternalRelease();

Comentários

Chame essa função na implementação de IUnknown::Release para cada interface que sua classe implementa. O valor retornado indica a nova contagem de referência no objeto. Se o objeto for agregado, essa função chamará o "controlling IUnknown" em vez de manipular a contagem de referência local.

DECLARE_INTERFACE_MAP, descrição da macro

DECLARE_INTERFACE_MAP

Comentários

Use essa macro em qualquer classe derivada de CCmdTarget que terá um mapa de interface. Usado da mesma forma que DECLARE_MESSAGE_MAP. Essa invocação de macro deve ser colocada na definição de classe, geralmente em um arquivo de cabeçalho (.H). Uma classe com DECLARE_INTERFACE_MAP deve definir o mapa da interface no arquivo de implementação (. CPP) com as macros BEGIN_INTERFACE_MAP e END_INTERFACE_MAP.

BEGIN_INTERFACE_PART e END_INTERFACE_PART, descrições de macro

BEGIN_INTERFACE_PART(localClass, iface);
END_INTERFACE_PART(localClass)

Parâmetros

localClass
O nome da classe que implementa a interface

iface
O nome da interface que essa classe implementa

Comentários

Para cada interface que sua classe implementará, você precisa ter um par BEGIN_INTERFACE_PART e END_INTERFACE_PART. Essas macros definem uma classe local derivada da interface OLE que você define, bem como uma variável de membro inserido dessa classe. Os membros AddRef, Release e QueryInterface são declarados automaticamente. Você deve incluir as declarações para as outras funções membro que fazem parte da interface que está sendo implementada (essas declarações são colocadas entre as macros BEGIN_INTERFACE_PART e END_INTERFACE_PART).

O argumento iface é a interface OLE que você deseja implementar, como IAdviseSink ou IPersistStorage (ou sua interface personalizada).

O argumento localClass é o nome da classe local que será definida. Um "X" será anexado automaticamente ao nome. Essa convenção de nomenclatura é usada para evitar colisões com classes globais de mesmo nome. Além disso, o nome do membro inserido, o mesmo que o nome localClass, exceto que ele é prefixado por 'm_x'.

Por exemplo:

BEGIN_INTERFACE_PART(MyAdviseSink, IAdviseSink)
    STDMETHOD_(void, OnDataChange)(LPFORMATETC, LPSTGMEDIUM);
    STDMETHOD_(void, OnViewChange)(DWORD, LONG);
    STDMETHOD_(void, OnRename)(LPMONIKER);
    STDMETHOD_(void, OnSave)();
    STDMETHOD_(void, OnClose)();
END_INTERFACE_PART(MyAdviseSink)

definiria uma classe local chamada XMyAdviseSink derivada de IAdviseSink e um membro da classe na qual ela é declarada chamada m_xMyAdviseSink.Note:

Observação

As linhas que começam com STDMETHOD_ são essencialmente copiadas do OLE2. H e modificadas ligeiramente. Copiá-las de OLE2. H pode reduzir erros difíceis de resolver.

BEGIN_INTERFACE_MAP e END_INTERFACE_MAP, descrições de macro

BEGIN_INTERFACE_MAP(theClass, baseClass)
END_INTERFACE_MAP

Parâmetros

theClass
A classe na qual o mapa da interface deve ser definido

baseClass
A classe da qual theClass deriva.

Comentários

As macros BEGIN_INTERFACE_MAP e END_INTERFACE_MAP são usadas no arquivo de implementação para definir o mapa da interface. Para cada interface implementada, há uma ou mais invocações de macro INTERFACE_PART. Para cada agregação que a classe usa, há uma invocação de macro INTERFACE_AGGREGATE.

INTERFACE_PART, descrição da macro

INTERFACE_PART(theClass, iid, localClass)

Parâmetros

theClass
O nome da classe que contém o mapa da interface.

iid
O IID que deve ser mapeado para a classe inserida.

localClass
O nome da classe local (menos o 'X').

Comentários

Essa macro é usada entre a macro BEGIN_INTERFACE_MAP e a macro END_INTERFACE_MAP para cada interface a que seu objeto dará suporte. Ela permite mapear uma IID para um membro da classe indicada por theClass e localClass. O "m_x" será adicionado automaticamente à localClass. Observe que mais de um IID pode estar associado a um só membro. Isso é muito útil quando você está implementando apenas uma interface "mais derivada" e deseja fornecer todas as interfaces intermediárias também. Um bom exemplo disso é a interface IOleInPlaceFrameWindow. Sua hierarquia é semelhante a esta:

IUnknown
    IOleWindow
        IOleUIWindow
            IOleInPlaceFrameWindow

Se um objeto for implementado IOleInPlaceFrameWindow, um cliente poderá QueryInterface em qualquer uma destas interfaces: IOleUIWindow, IOleWindow ou IUnknown, além da interface IOleInPlaceFrameWindow "mais derivada" (aquela que você está realmente implementando). Para lidar com isso, você pode usar mais de uma macro INTERFACE_PART para mapear cada interface base para a interface IOleInPlaceFrameWindow:

no arquivo de definição de classe:

BEGIN_INTERFACE_PART(CMyFrameWindow, IOleInPlaceFrameWindow)

no arquivo de implementação de classe:

BEGIN_INTERFACE_MAP(CMyWnd, CFrameWnd)
    INTERFACE_PART(CMyWnd, IID_IOleWindow, MyFrameWindow)
    INTERFACE_PART(CMyWnd, IID_IOleUIWindow, MyFrameWindow)
    INTERFACE_PART(CMyWnd, IID_IOleInPlaceFrameWindow, MyFrameWindow)
END_INTERFACE_MAP

A estrutura cuida do IUnknown porque ele sempre é necessário.

INTERFACE_PART, descrição da macro

INTERFACE_AGGREGATE(theClass, theAggr)

Parâmetros

theClass
O nome da classe que contém o mapa da interface,

theAggr
O nome da variável membro a ser agregada.

Comentários

Essa macro é usada para informar à estrutura que a classe está usando um objeto agregado. Ela deve aparecer entre as macros BEGIN_INTERFACE_PART e END_INTERFACE_PART. Um objeto agregado é um objeto separado, derivado de IUnknown. Usando uma macro de agregação e INTERFACE_AGGREGATE, você pode fazer com que todas as interfaces compatíveis com a agregação pareçam ter suporte direto do objeto. O argumento theAggr é simplesmente o nome de uma variável de membro da classe derivada de IUnknown (direta ou indiretamente). Todas as macros INTERFACE_AGGREGATE devem seguir as macros INTERFACE_PART quando colocadas em um mapa de interface.

Confira também

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