TN065. Поддержка сдвоенных интерфейсов для серверов автоматизации OLE

Примечание.

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

В этой заметке описывается, как добавить поддержку двух интерфейсов в серверное приложение OLE Automation на основе MFC. Пример ACDUAL иллюстрирует поддержку двух интерфейсов, а пример кода в этой заметке взят из ACDUAL . Макросы, описанные в этой заметке, такие как DECLARE_DUAL_ERRORINFO, DUAL_ERRORINFO_PART и IMPLEMENT_DUAL_ERRORINFO, являются частью примера ACDUAL и можно найти в MFCDUAL.H.

Двойные интерфейсы

Хотя служба автоматизации OLE позволяет реализовать IDispatch интерфейс, интерфейс V ТБ L или двойной интерфейс (который охватывает оба), корпорация Майкрософт настоятельно рекомендует реализовать двойные интерфейсы для всех предоставляемых объектов OLE Automation. Двойные интерфейсы имеют значительные преимущества по сравнению IDispatchс интерфейсами только -only или V ТБ L:

  • Привязка может выполняться во время компиляции через интерфейс V ТБ L или во время IDispatchвыполнения.

  • Контроллеры OLE Automation, которые могут использовать интерфейс V ТБ L, могут воспользоваться улучшенной производительностью.

  • Существующие контроллеры OLE Automation, использующие IDispatch интерфейс, будут продолжать работать.

  • Интерфейс V ТБ L проще вызывать из C++.

  • Для совместимости с функциями поддержки объектов Visual Basic требуются двойные интерфейсы.

Добавление поддержки двойного интерфейса в класс на основе CCmdTarget

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

Сначала измените ODL-файл для сервера, чтобы определить двойные интерфейсы для объектов. Чтобы определить двойной интерфейс, необходимо использовать инструкцию интерфейса вместо инструкции, DISPINTERFACE которую создают мастеры Visual C++. Вместо удаления существующей DISPINTERFACE инструкции добавьте новую инструкцию интерфейса. Сохраняя DISPINTERFACE форму, вы можете продолжать использовать ClassWizard для добавления свойств и методов в объект, но необходимо добавить эквивалентные свойства и методы в инструкцию интерфейса.

Оператор интерфейса для двойного интерфейса должен иметь атрибуты OLEAUTOMATION и DUAL , а интерфейс должен быть производным от IDispatch. Пример GUIDGEN можно использовать для создания iiD для двух интерфейсов:

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

После создания инструкции интерфейса начните добавлять записи для методов и свойств. Для двух интерфейсов необходимо изменить порядок списков параметров, чтобы методы и функции доступа к свойствам в двойном интерфейсе возвращали HRESULT и передают их возвращаемые значения в качестве параметров с атрибутами [retval,out]. Помните, что для свойств необходимо добавить функцию доступа чтения (propget) и записи (propput) с одинаковым идентификатором. Например:

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

После определения методов и свойств необходимо добавить ссылку на инструкцию интерфейса в операторе coclass. Например:

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

После обновления файла ODL используйте механизм карты интерфейса MFC, чтобы определить класс реализации для двойного интерфейса в классе объектов и сделать соответствующие записи в механизме QueryInterface MFC. Для каждой записи в INTERFACE_PART инструкции интерфейса ODL требуется одна запись в блоке, а также записи для интерфейса диспетчера. Для каждой записи ODL с атрибутом propput требуется функция с именем put_propertyname. Для каждой записи с атрибутом propget требуется функция с именем get_propertyname.

Чтобы определить класс реализации для двойного интерфейса, добавьте DUAL_INTERFACE_PART блок в определение класса объекта. Например:

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)

Чтобы подключить двойной интерфейс к механизму QueryInterface MFC, добавьте INTERFACE_PART запись в карту интерфейса:

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

Затем необходимо заполнить реализацию интерфейса. В большинстве случаев вы сможете делегировать существующую реализацию MFC IDispatch . Например:

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

Для методов и функций доступа к свойствам объекта необходимо заполнить реализацию. Функции метода и свойства обычно могут делегировать методы, созданные с помощью ClassWizard. Однако если вы настроили свойства для доступа к переменным напрямую, необходимо написать код, чтобы получить или поместить значение в переменную. Например:

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

Передача указателей с двумя интерфейсами

Передача указателя с двумя интерфейсами не является простой, особенно если вам нужно вызвать CCmdTarget::FromIDispatch. FromIDispatch работает только на указателях IDispatch MFC. Один из способов обойти эту проблему — запросить исходный IDispatch указатель, настроенный MFC, и передать этот указатель на функции, которые нуждаются в нем. Например:

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

Перед передачей указателя обратно через метод двойного интерфейса может потребоваться преобразовать его из указателя MFC IDispatch в указатель с двумя интерфейсами. Например:

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

Регистрация библиотеки типов приложения

AppWizard не создает код для регистрации библиотеки типов сервера OLE Automation в системе. Хотя существуют другие способы регистрации библиотеки типов, удобно регистрировать библиотеку типов при обновлении сведений о типе OLE, то есть при каждом запуске приложения.

Чтобы зарегистрировать библиотеку типов приложения всякий раз, когда приложение выполняется отдельно:

  • Включите AFXCTL. H в стандартном формате включает файл заголовка STDAFX. H, чтобы получить доступ к определению AfxOleRegisterTypeLib функции.

  • В функции приложения InitInstance найдите вызов COleObjectFactory::UpdateRegistryAll. После этого вызова добавьте вызов AfxOleRegisterTypeLib, указав LIBID , соответствующий библиотеке типов, а также имя библиотеки типов:

    // 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
    

Изменение Параметры сборки проекта для изменения библиотеки типов

Чтобы изменить параметры сборки проекта, чтобы файл заголовка, содержащий определения UUID , создается MkTypLib всякий раз при перестроении библиотеки типов:

  1. В меню "Сборка" щелкните Параметры, а затем выберите ODL-файл из списка файлов для каждой конфигурации.

  2. Щелкните вкладку OLE Types и укажите имя файла в поле "Имя файла заголовка выходных данных". Используйте имя файла, которое еще не используется проектом, так как MkTypLib перезаписывает существующий файл. Нажмите кнопку "ОК", чтобы закрыть диалоговое окно "СборкаПараметры".

Чтобы добавить определения UUID из созданного файла заголовка MkTypLib в проект:

  1. Включите созданный файл заголовка MkTypLib в стандартный файл заголовка, включая файл заголовка, stdafx.h.

  2. Создайте файл INITIIDS. CPP и добавьте его в проект. В этом файле добавьте созданный файл заголовка MkTypLib после включения OLE2. H и 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. В меню "Сборка" щелкните Параметры и выберите INITIIDS. CPP из списка файлов для каждой конфигурации.

  4. Щелкните вкладку C++ и нажмите кнопку "Предварительно скомпилированные заголовки" и нажмите кнопку "Не использовать предварительно скомпилированные заголовки". Нажмите кнопку "ОК", чтобы закрыть диалоговое окно "Сборка Параметры".

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

Мастера, отправленные в Visual C++, неправильно используют имя класса реализации, чтобы указать coclass в ODL-файле сервера для классов OLE-creatable. Хотя это будет работать, имя класса реализации, вероятно, не является именем класса, которое требуется использовать пользователям объекта. Чтобы указать правильное имя, откройте ODL-файл, найдите каждую инструкцию coclass и замените имя класса реализации правильным внешним именем.

Обратите внимание, что при изменении инструкции сокласса имена переменных CLSIDв файле заголовков, созданном в MkTypLib, будут изменяться соответствующим образом. Вам потребуется обновить код, чтобы использовать новые имена переменных.

Обработка исключений и интерфейсов ошибок автоматизации

Методы объекта автоматизации и функции доступа к свойствам могут вызывать исключения. Если это так, их следует обрабатывать в реализации двух интерфейсов и передавать сведения об исключении обратно контроллеру через интерфейс обработки ошибок OLE Automation. IErrorInfo Этот интерфейс предоставляет подробные, контекстные сведения об ошибках с помощью IDispatch интерфейсов V ТБ L. Чтобы указать, что обработчик ошибок доступен, необходимо реализовать ISupportErrorInfo интерфейс.

Чтобы проиллюстрировать механизм обработки ошибок, предположим, что созданные классом ClassWizard функции, используемые для реализации стандартной поддержки диспетчеризации, вызывают исключения. Реализация IDispatch::Invoke MFC обычно перехватывает эти исключения и преобразует их в структуру EXCEPTINFO, возвращаемую через Invoke вызов. Однако при использовании интерфейса V ТБ L вы несете ответственность за перехват исключений самостоятельно. В качестве примера защиты методов двойного интерфейса:

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 возвращает правильный код ошибки при возникновении исключения. CATCH_ALL_DUAL Преобразует исключение MFC в сведения об обработке ошибок OLE Automation с помощью ICreateErrorInfo интерфейса. (Пример CATCH_ALL_DUAL макроса находится в файле MFCDUAL. H в примере ACDUAL . Функция, вызываемая для обработки исключений, DualHandleExceptionнаходится в файле MFCDUAL. CPP.) CATCH_ALL_DUAL определяет код ошибки, возвращаемый на основе типа исключения, которое произошло:

  • COleDispatchException — в данном случае HRESULT создается с помощью следующего кода:

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

    При этом создается определенный HRESULT интерфейс, вызвавшего исключение. Код ошибки смещается 0x200, чтобы избежать конфликтов с системными HRESULTинтерфейсами OLE.

  • CMemoryException — в данном случае E_OUTOFMEMORY возвращается.

  • Возвращается любое другое исключение . В этом случае E_UNEXPECTED возвращается.

Чтобы указать, что используется обработчик ошибок OLE Automation, необходимо также реализовать ISupportErrorInfo интерфейс.

Сначала добавьте код в определение класса автоматизации, чтобы показать, что он поддерживает ISupportErrorInfo.

Во-вторых, добавьте код в карту интерфейса класса автоматизации, чтобы связать ISupportErrorInfo класс реализации с механизмом MFC QueryInterface . Оператор INTERFACE_PART соответствует классу, определенному для ISupportErrorInfo.

Наконец, реализуйте класс, определенный для поддержки ISupportErrorInfo.

(The Пример ACDUAL содержит три макроса для выполнения этих трех шагов, DECLARE_DUAL_ERRORINFODUAL_ERRORINFO_PARTа также IMPLEMENT_DUAL_ERRORINFOвсех, содержащихся в MFCDUAL.H.)

В следующем примере реализуется класс, определенный для поддержки ISupportErrorInfo. CAutoClickDoc — это имя класса автоматизации и IID_IDualAClick является iiD для интерфейса, который является источником ошибок, сообщаемых с помощью объекта ошибки OLE Automation:

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

См. также

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