TN038. Реализация MFC/OLE IUnknown

Примечание

Следующее техническое примечание не было обновлено, поскольку сначала оно было включено в электронную документацию. В результате некоторые процедуры и разделы могут быть устаревшими или неверными. Для получения последних сведений рекомендуется выполнить поиск интересующей темы в алфавитном указателе документации в Интернете.

В основе OLE 2 лежит модель COM OLE. Модель COM определяет стандарт взаимодействия совместно действующих объектов. Сюда входят сведения о самом «объекте», включая то, какие методы подготавливаются для отправки в объект. Модель COM также определяет базовый класс, от которого наследуются все COM-совместимые классы. Это базовый класс IUnknown. Хотя интерфейс IUnknown называется классом C++, COM не относится ни к одному языку— его можно реализовать на C, PASCAL или любом другом языке, поддерживающем двоичную компоновку COM-объекта.

OLE называет все классы, производные от IUnknown , "интерфейсами". Это важное различие, так как "интерфейс", такой как IUnknown , не несет с собой реализации. Он просто определяет протокол, по которому взаимодействуют объекты, а не особенности этих реализаций. Это оправдано для системы, которая нацелена на максимальную гибкость. За реализацию поведения по умолчанию для программ MFC/C++ отвечает MFC.

Чтобы понять реализацию IUnknown в MFC, сначала необходимо понять, что собой представляет этот интерфейс. Упрощенная версия IUnknown определена ниже:

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

Примечание

Некоторые необходимые сведения по соглашениям о вызовах, такие как __stdcall, в данной иллюстрации опущены.

Функции-члены AddRef и Release управляют управлением памятью объекта. Модель COM использует схему подсчета ссылок для отслеживания объектов. Ссылка на объект никогда не указывается напрямую, как это происходит в C++. Вместо этого ссылка на COM-объекты всегда осуществляется через указатель. Чтобы освободить объект после его использования владельцем, вызывается элемент Release объекта (в отличие от использования оператора delete, как это делается для традиционного объекта C++). Механизм подсчета ссылок позволяет управлять несколькими ссылками на один объект. Реализация AddRef и Release поддерживает подсчет ссылок на объект — объект не удаляется, пока его число ссылок не достигнет нуля.

AddRef и Release довольно просты с точки зрения реализации. Вот пример простой реализации:

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

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

Функция-член QueryInterface немного интереснее. Не очень интересно иметь объект, единственными функциями-членами которого являются AddRef и Release — было бы неплохо сообщить объекту о том, чтобы он делал больше вещей, чем предоставляет IUnknown . Именно здесь полезно использовать QueryInterface . Он позволяет получать разный «интерфейс» на базе одного и того же объекта. Эти интерфейсы обычно являются производными от IUnknown и добавляют дополнительные функции, добавляя новые функции-члены. COM-интерфейсы никогда не имеют объявляемых в интерфейсе переменных-членов, а все функции-члены объявлены как чисто виртуальные. Например,

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

Чтобы получить IPrintInterface, если у вас есть только IUnknown, вызовите QueryInterface с помощью IID объекта IPrintInterface. IID представляет собой 128-разрядное число, которое однозначно определяет интерфейс. IID присутствует для каждого интерфейса, определенного вами или OLE. Если pUnk является указателем на объект IUnknown , код для извлечения IPrintInterface из него может выглядеть следующим образом:

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

Это довольно просто, но как можно реализовать объект, поддерживающий интерфейсЫ IPrintInterface и IUnknown . В этом случае это просто, так как IPrintInterface является производным непосредственно от IUnknown — при реализации IPrintInterface IUnknown автоматически поддерживается IUnknown . Пример:

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

Реализации AddRef и Release будут точно таким же, как реализованы выше. CPrintObj::QueryInterface будет выглядеть примерно так:

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

Как видите, если распознается идентификатор интерфейса (IID), в объект возвращается указатель, в противном случае возникает ошибка. Также обратите внимание, что успешное выполнение QueryInterface приводит к неявным addRef. Конечно же, необходимо также реализовать CEditObj::Print. Это просто, так как интерфейс IPrintInterface был непосредственно производным от интерфейса IUnknown . Однако если вы хотите поддерживать два разных интерфейса, которые являются производными от IUnknown, учитывайте следующее:

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

Хотя существует несколько разных способов реализации класса, поддерживающего как IEditInterface, так и IPrintInterface, включая использование множественного наследования C++, эта заметка будет посвящена использованию вложенных классов для реализации данной функциональности.

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

Полная реализация приведена ниже.

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

Обратите внимание, что большая часть реализации IUnknown помещается в класс CEditPrintObj, а не дублирует код в CEditPrintObj::CEditObj и CEditPrintObj::CPrintObj. Это уменьшает объем кода и помогает избежать ошибок. Ключевым моментом здесь является то, что из интерфейса IUnknown можно вызвать QueryInterface , чтобы получить любой интерфейс, который может поддерживать объект, и из каждого из этих интерфейсов можно сделать то же самое. Это означает, что все функции QueryInterface , доступные из каждого интерфейса, должны вести себя одинаково. Чтобы эти внедренные объекты вызвали реализацию во «внешнем объекте», используется обратный указатель (m_pParent). Указатель m_pParent инициализируется во время выполнения конструктора CEditPrintObj. Затем следует реализовать CEditPrintObj::CPrintObj::PrintObject, а также CEditPrintObj::CEditObj::EditObject. Потребовалось написать довольно большой объем кода для реализации одной функции — возможности изменения объекта. К счастью, интерфейсы крайне редко имеют только одну функцию-член (хотя такое и случается), и в этом случае EditObject и PrintObject обычно объединяются в единый интерфейс.

Такой простой сценарий требует слишком долгих пояснений и слишком большого объема кода. Классы MFC/OLE предоставляют более простую альтернативу. Реализация MFC использует методику, аналогичную использованию программы-оболочки для сообщений Windows с помощью схем сообщений. Это средство называется Картами интерфейсов и рассматривается в следующем разделе.

Схемы интерфейсов MFC

MFC/OLE включает в себя реализацию «Схемы интерфейсов», аналогичную компонентам «Схемы сообщений» и «Схемы подготовки к отправке» MFC в рамках концепции и выполнения. Схемы интерфейсов MFC имеют следующие основные возможности.

  • Стандартная реализация IUnknown, встроенная в CCmdTarget класс .

  • Обслуживание счетчика ссылок, измененных AddRef и Release

  • Реализация QueryInterface на основе данных

Кроме того, схемы интерфейсов поддерживают следующие дополнительные возможности.

  • Поддержка создания COM-объектов, допускающих статистическую обработку

  • Поддержка использования агрегатных объектов в реализации COM-объекта

  • Реализация является обрабатываемой и расширяемой

Дополнительные сведения об агрегации см. в разделе Агрегирование .

Поддержка схем интерфейсов MFC заключена в класс CCmdTarget. CCmdTarget Число ссылок has-a, а также все функции-члены, связанные с реализацией IUnknown (число ссылок, например, находится в CCmdTarget). Чтобы создать класс, поддерживающий модель COM OLE, создайте класс, производный от CCmdTarget, и используйте различные макросы и функции-члены CCmdTarget для реализации требуемых интерфейсов. Реализация MFC использует вложенные классы для определения каждой реализации интерфейса, что во многом похоже на приведенный выше пример. Эта задача упрощается благодаря стандартной реализации IUnknown, а также набору макросов, позволяющих исключить некоторые из повторяющихся частей кода.

Основы использования схем интерфейсов

Реализация класса с помощью схем интерфейсов MFC

  1. Создайте класс, который явно или косвенно является от CCmdTarget.

  2. Используйте функцию DECLARE_INTERFACE_MAP в определении производного класса.

  3. Для каждого интерфейса, который вы хотите поддерживать, используйте макросы BEGIN_INTERFACE_PART и END_INTERFACE_PART в определении класса.

  4. В файле реализации используйте макросы BEGIN_INTERFACE_MAP и END_INTERFACE_MAP, чтобы определить схему интерфейса класса.

  5. Для каждого поддерживаемого IID используйте макрос INTERFACE_PART между BEGIN_INTERFACE_MAP и END_INTERFACE_MAP макросами, чтобы сопоставить этот IID с определенной "частью" класса.

  6. Реализуйте каждый из вложенных классов, представляющих интерфейсы, которые вы поддерживаете.

  7. Используйте макрос METHOD_PROLOGUE для доступа к родительскому объекту, CCmdTargetпроизводном от него.

  8. AddRef, Release и QueryInterface могут делегировать реализацию CCmdTarget этих функций (ExternalAddRef, ExternalReleaseи ExternalQueryInterface).

Приведенный выше пример CPrintEditObj можно реализовать следующим образом:

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

Указанное выше объявление создает класс, производный от CCmdTarget. Макрос DECLARE_INTERFACE_MAP сообщает платформе, что этот класс будет иметь настраиваемую карту интерфейса. Кроме того, макросы BEGIN_INTERFACE_PART и END_INTERFACE_PART определяют вложенные классы, в данном случае с именами CEditObj и CPrintObj (X используется только для отличия вложенных классов от глобальных классов, которые начинаются с "C", и классов интерфейса, которые начинаются с "I"). Создаются два вложенных элемента этих классов — m_CEditObj и m_CPrintObj соответственно. Макросы автоматически объявляют функции AddRef, Release и QueryInterface ; Поэтому объявляются только функции, относящиеся к этому интерфейсу: EditObject и PrintObject (макрос OLE STDMETHOD используется для предоставления ключевых слов _stdcall и virtual в соответствии с целевой платформой).

Чтобы реализовать схему интерфейсов для этого класса, используйте следующий код:

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

Он соединяет IID_IPrintInterface IID с m_CPrintObj, а IID_IEditInterface — с m_CEditObj. Реализация CCmdTargetQueryInterface (CCmdTarget::ExternalQueryInterface) использует эту карту для возврата указателей на m_CPrintObj и m_CEditObj по запросу. Включать запись для IID_IUnknown не нужно, при запросе IID_IUnknown платформа будет использовать первый интерфейс в схеме (в данном случае это m_CPrintObj).

Несмотря на то, что макрос BEGIN_INTERFACE_PART автоматически объявил функции AddRef, Release и QueryInterface , вам все равно необходимо реализовать их:

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

Реализация CEditPrintObj::CPrintObj будет похожа на приведенные выше определения CEditPrintObj::CEditObj. Несмотря на то что возможно создать макрос, который может использоваться для автоматического создания таких функций (а так было ранее при разработке MFC/OLE), становится трудно задавать точки останова, когда макрос создает более одной строки кода. По этой причине этот код будет расширен вручную.

При использовании реализации платформы для схем сообщений существует несколько вещей, которые не нужно было выполнять.

  • Реализация QueryInterface

  • Реализация AddRef и Release

  • Объявление любого из этих встроенных методов для обоих интерфейсов

Кроме того, платформа использует схемы сообщений для внутренних целей. Это позволяет создавать производные объекты от класса платформы, например COleServerDoc, который уже поддерживает определенные интерфейсы и предоставляет замены или дополнения для интерфейсов, предоставляемых платформой. Это можно сделать, так как платформа полностью поддерживает наследование схемы интерфейсов от базового класса. Именно поэтому BEGIN_INTERFACE_MAP принимает в качестве второго параметра имя базового класса.

Примечание

В общем случае нельзя повторно использовать реализацию встроенных реализаций интерфейсов OLE MFC путем простого наследования внедренных специализаций этого интерфейса от версии MFC. Это невозможно, поскольку использование макроса METHOD_PROLOGUE для получения доступа к содержакому CCmdTargetобъекту подразумевает фиксированное смещение внедренного объекта от объекта, производного от CCmdTargetнего. Это означает, например, что нельзя наследовать внедренный XMyAdviseSink от реализации MFC в COleClientItem::XAdviseSink, так как XAdviseSink ожидает наличия определенного смещения относительно верхнего края объект COleClientItem.

Примечание

Однако можно делегировать реализацию MFC для всех функций, для которых требуется поведение по умолчанию MFC. Это делается в реализации MFC IOleInPlaceFrame (XOleInPlaceFrame) в классе COleFrameHook (он делегирует в m_xOleInPlaceUIWindow для многих функций). Такой подход был выбран для уменьшения размера среды выполнения объектов, реализующих несколько интерфейсов. Он устраняет необходимость в обратном указателе (например, в m_pParent из примера в предыдущем разделе).

Статистическая обработка и схемы интерфейсов

В дополнение к поддержке изолированных COM-объектов MFC также поддерживает статистическую обработку. Агрегирование само по себе является слишком сложной темой, чтобы обсудить здесь; Дополнительные сведения об агрегации см. в разделе Агрегирование . Эта заметка просто описывает поддержку статистической обработки, встроенную в платформу и схемы интерфейсов.

Существует два способа использования статистической обработки: (1) использование COM-объекта, поддерживающего статистическую обработку, и (2) реализация объекта, который может быть статистически вычислен другим объектом. Эти возможности можно называть «использование агрегатного объекта» и «превращение объекта в допускающий статистическую обработку». MFC поддерживает оба этих варианта.

Использование агрегатного объекта

Для использования агрегатного объекта требуется какой-либо способ связать статистическое выражение с механизмом QueryInterface. Другими словами, агрегатный объект должен работать так, как будто это собственная часть объекта. Так как это связано с механизмом сопоставления интерфейса MFC. Помимо макроса INTERFACE_PART, где вложенный объект сопоставляется с IID, можно также объявить агрегатный объект как часть производного CCmdTarget класса. Для этого используется макрос INTERFACE_AGGREGATE. Это позволяет указать переменную-член (которая должна быть указателем на IUnknown или производный класс), которая должна быть интегрирована в механизм карты интерфейса. Если при вызове указатель не равен NULL CCmdTarget::ExternalQueryInterface , платформа автоматически вызывает функцию-член QueryInterface объекта агрегата, если IID запрошенный объект не является одним из собственных IIDобъектов, поддерживаемых CCmdTarget самим объектом.

Использование макроса INTERFACE_AGGREGATE

  1. Объявите переменную-член (IUnknown*) которая будет содержать указатель на агрегатный объект.

  2. Включите макрос INTERFACE_AGGREGATE в карту интерфейса, который ссылается на переменную-член по имени.

  3. В определенный момент (обычно во время CCmdTarget::OnCreateAggregates) инициализируйте переменную-член со значением, отличным от NULL.

Пример:

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()

Переменная m_lpAggrInner инициализируется в конструкторе со значением NULL. Платформа игнорирует переменную-член NULL в реализации queryInterface по умолчанию. OnCreateAggregates удобно использовать для создания агрегатных объектов. Его потребуется вызвать явным образом при создании объекта за пределами реализации COleObjectFactory MFC. Причина создания статистических выражений в CCmdTarget::OnCreateAggregates и использование CCmdTarget::GetControllingUnknown станут очевидными при рассмотрении создания агрегатных объектов.

Эта методика дает объекту все интерфейсы, поддерживаемые агрегатным объектом, а также все его собственные интерфейсы. Если требуется только подмножество интерфейсов, поддерживаемых статистическим выражением, можно переопределить CCmdTarget::GetInterfaceHook. Это обеспечивает очень низкоуровневую возможность подключения, аналогичную QueryInterface. В общем случае требуются все интерфейсы, которые поддерживает статистическое выражение.

Превращение реализации объекта в допускающую статистическую обработку

Чтобы объект был агрегируемым, реализация AddRef, Release и QueryInterface должна делегироваться в "управление неизвестным". Иными словами, чтобы он был частью объекта , он должен делегировать AddRef, Release и QueryInterface другому объекту, также производному от IUnknown. Этот «управляющий unknown» предоставляется для объекта при его создании, то есть предоставляется реализации COleObjectFactory. Такая реализация имеет небольшое количество издержек и в некоторых случаях является нежелательной, поэтому MFC делает ее необязательной. Чтобы сделать объект допускающим статистическую обработку, вызовите CCmdTarget::EnableAggregation из конструктора объекта.

Если этот объект также использует статистические выражения, необходимо обязательно передать в агрегатные объекты правильный «управляющий unknown». Обычно этот указатель IUnknown передается объекту при создании статистического выражения. Например, параметр pUnkOuter является «управляющим unknown» для объектов, созданных с использованием CoCreateInstance. Можно получить правильный указатель «управляющий unknown» путем вызова CCmdTarget::GetControllingUnknown. Однако значение, возвращаемое из этой функции, является недопустимым во время конструктора. Поэтому рекомендуется создавать ваши статистические выражения только в переопределении CCmdTarget::OnCreateAggregates, где возвращаемое значение из GetControllingUnknown является надежным, даже если оно создано на базе реализации COleObjectFactory.

Не менее важно, чтобы объект изменял правильный счетчик ссылок во время добавления или освобождения искусственных счетчиков ссылок. Чтобы обеспечить это, следует всегда вызывать ExternalAddRef и ExternalRelease вместо InternalRelease и InternalAddRef. InternalRelease или InternalAddRef редко вызываются для класса, поддерживающего статистическую обработку.

Справочные материалы

Расширенное использование OLE, например при определении собственных интерфейсов или переопределении реализации интерфейсов OLE платформы, требует использования базового механизма схем интерфейсов.

В этом разделе рассматриваются все макросы и интерфейсы API, которые используются для реализации этих дополнительных функций.

CCmdTarget::EnableAggregation — описание функции

void EnableAggregation();

Комментарии

Вызовите эту функцию в конструкторе производного класса, если хотите обеспечить поддержку статистической обработки OLE для объектов этого типа. При этом подготавливается специальная реализация IUnknown, которая необходима для агрегатных объектов.

CCmdTarget::ExternalQueryInterface — описание функции

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

Параметры

lpIID
Дальний указатель на IID (первый аргумент QueryInterface)

ppvObj
Указатель на IUnknown * (второй аргумент QueryInterface)

Комментарии

Вызовите эту функцию в реализации IUnknown для каждого интерфейса, который реализуется вашим классом. Эта функция предоставляет стандартную управляемую данными реализацию QueryInterface, основанную на схеме интерфейсов объекта. Это необходимо, чтобы привести возвращаемое значение к HRESULT. Если объект является статистическим выражением, вместо использования локальной схемы интерфейсов эта функция вызывает «управляющий IUnknown».

CCmdTarget::ExternalAddRef — описание функции

DWORD ExternalAddRef();

Комментарии

Вызовите эту функцию в реализации IUnknown::AddRef для каждого интерфейса, который реализуется вашим классом. Возвращаемое значение является новым счетчиком ссылок для объекта CCmdTarget. Если объект является статистическим выражением, вместо изменения локального счетчика ссылок эта функция вызывает «управляющий IUnknown».

CCmdTarget::ExternalRelease — описание функции

DWORD ExternalRelease();

Комментарии

Вызовите эту функцию в реализации IUnknown::Release для каждого интерфейса, который реализуется вашим классом. Возвращаемое значение указывает новый счетчик для объекта. Если объект является статистическим выражением, вместо изменения локального счетчика ссылок эта функция вызывает «управляющий IUnknown».

DECLARE_INTERFACE_MAP — описание макроса

DECLARE_INTERFACE_MAP

Комментарии

Используйте этот макрос в любом классе, производном от CCmdTarget, который будет иметь схему интерфейсов. Используется во многом так же, как DECLARE_MESSAGE_MAP. Вызов этого макроса следует поместить в определение класса, обычно для этого используется файл заголовка (. (H). Класс с DECLARE_INTERFACE_MAP должен определять сопоставление интерфейса в файле реализации (. CPP) с макросами BEGIN_INTERFACE_MAP и END_INTERFACE_MAP.

BEGIN_INTERFACE_PART и END_INTERFACE_PART — описания макросов

BEGIN_INTERFACE_PART(localClass, iface);
END_INTERFACE_PART(localClass)

Параметры

localClass
Имя класса, реализующего интерфейс.

iface
Имя интерфейса, реализуемого с помощью данного класса.

Комментарии

Для каждого интерфейса, который будет реализован классом, необходимо иметь пару BEGIN_INTERFACE_PART и END_INTERFACE_PART. Эти макросы определяют локальный класс, производный от определенного вами интерфейса OLE, а также внедренную переменную-член этого класса. Элементы AddRef, Release и QueryInterface объявляются автоматически. Необходимо включить объявления для других функций-членов, которые являются частью реализуемого интерфейса (эти объявления размещаются между макросами BEGIN_INTERFACE_PART и END_INTERFACE_PART).

Аргумент iface — это интерфейс OLE, который вы хотите реализовать, например IAdviseSink, или IPersistStorage (или собственный пользовательский интерфейс).

Аргумент localClass — это имя локального класса, который будет определен. К имени будет автоматически добавляться буква X. Это соглашение об именовании используется для предотвращения конфликтов с глобальными классами, имеющими такое же имя. Кроме того, имя внедренного элемента совпадает с именем localClass , за исключением того, что оно имеет префикс "m_x".

Пример:

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)

Этот код определяет локальный класс с именем XMyAdviseSink, являющийся производным от IAdviseSink, и член класса, в котором он определен, с именем m_xMyAdviseSink.Note:

Примечание

Строки, начинающиеся с STDMETHOD_, по сути, копируются из OLE2. H и незначительно изменены. Их копирование из OLE2.H позволяет уменьшить число трудноразрешимых ошибок.

BEGIN_INTERFACE_MAP и END_INTERFACE_MAP — описания макросов

BEGIN_INTERFACE_MAP(theClass, baseClass)
END_INTERFACE_MAP

Параметры

Класс
Класс, в котором определяется схема интерфейсов.

baseClass
Класс, от которого наследуется класс .

Комментарии

Макросы BEGIN_INTERFACE_MAP и END_INTERFACE_MAP используются в файле реализации для фактического определения карты интерфейса. Для каждого реализованного интерфейса имеется один или несколько вызовов макросов INTERFACE_PART. Для каждого статистического выражения, которое использует класс, существует один INTERFACE_AGGREGATE вызова макроса.

INTERFACE_PART — описание макроса

INTERFACE_PART(theClass, iid, localClass)

Параметры

Класс
Имя класса, содержащего схему интерфейсов.

Iid
IID, который будет сопоставляться с внедренным классом.

localClass
Имя локального класса (без буквы X).

Комментарии

Этот макрос используется между макросом BEGIN_INTERFACE_MAP и макросом END_INTERFACE_MAP для каждого интерфейса, поддерживаемого объектом. Он позволяет сопоставить IID с членом класса, указанного классом иlocalClass. "m_x" будет добавлен в localClass автоматически. Обратите внимание, что с одним членом можно сопоставить больше одного IID. Это очень удобно, если вы реализуете только «самый производный» интерфейс и хотите также предоставить все промежуточные интерфейсы. Хорошим примером этого является интерфейс IOleInPlaceFrameWindow. Иерархия выглядит следующим образом.

IUnknown
    IOleWindow
        IOleUIWindow
            IOleInPlaceFrameWindow

Если объект реализует IOleInPlaceFrameWindow, клиент может QueryInterface использовать любой из этих интерфейсов: IOleUIWindow, IOleWindowили IUnknown, помимо "самого производного" интерфейса IOleInPlaceFrameWindow (который вы фактически реализуете). Для этого можно использовать несколько макросов INTERFACE_PART для сопоставления каждого базового интерфейса с интерфейсом IOleInPlaceFrameWindow :

В файле определения класса:

BEGIN_INTERFACE_PART(CMyFrameWindow, IOleInPlaceFrameWindow)

В файле реализации класса:

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

Платформа самостоятельно обрабатывает IUnknown, так как он всегда является обязательным.

INTERFACE_PART — описание макроса

INTERFACE_AGGREGATE(theClass, theAggr)

Параметры

Класс
Имя класса, содержащего схему интерфейсов.

TheAggr
Имя переменной-члена, подлежащей статистической обработке.

Комментарии

Этот макрос применяется, чтобы сообщить платформе о том, что класс использует агрегатный объект. Он должен отображаться между макросами BEGIN_INTERFACE_PART и END_INTERFACE_PART. Агрегатный объект — это отдельный объект, производный от IUnknown. Используя агрегат и макрос INTERFACE_AGGREGATE, можно сделать так, чтобы все интерфейсы, поддерживаемые агрегатом, напрямую поддерживались объектом . Аргумент TheAggr — это просто имя переменной-члена класса, которое является производным от IUnknown (прямо или косвенно). Все INTERFACE_AGGREGATE макросы должны следовать INTERFACE_PART макросам при размещении в карте интерфейса.

См. также раздел

Технические примечания по номеру
Технические примечания по категориям