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 всякий раз при перестроении библиотеки типов:
В меню "Сборка" щелкните Параметры, а затем выберите ODL-файл из списка файлов для каждой конфигурации.
Щелкните вкладку OLE Types и укажите имя файла в поле "Имя файла заголовка выходных данных". Используйте имя файла, которое еще не используется проектом, так как MkTypLib перезаписывает существующий файл. Нажмите кнопку "ОК", чтобы закрыть диалоговое окно "Сборка Параметры".
Чтобы добавить определения UUID из созданного файла заголовка MkTypLib в проект:
Включите созданный файл заголовка MkTypLib в стандартный файл заголовка, включая файл заголовка, stdafx.h.
Создайте файл 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"
В меню "Сборка" щелкните Параметры и выберите INITIIDS. CPP из списка файлов для каждой конфигурации.
Щелкните вкладку 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_ERRORINFO
DUAL_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;
}
См. также
Технические примечания по номеру
Технические примечания по категории