TN065:双重接口的OLE自动化服务器支持
备注
以下技术声明,则它在联机文档,首先包括了不更新。因此,某些过程和主题可能已过时或不正确。有关最新信息,建议您搜索议题在联机文档的索引。
此说明讨论如何添加双重接口支持。基于 MFC 的 OLE 自动化服务器应用程序。 ACDUAL 示例阐释双重接口支持,因此,此说明的代码示例从 ACDUAL 中采用。 此说明描述的宏,如 DECLARE_DUAL_ERRORINFO, DUAL_ERRORINFO_PART和 IMPLEMENT_DUAL_ERRORINFO,是 ACDUAL 示例的一部分,并且可以在 MFCDUAL.H. 找到。
双重接口
虽然 OLE 自动化使您可以实现 IDispatch 接口、 VTBL 接口或包含两个) 的双重接口 (Microsoft,强烈建议您实现所有公开的 OLE 自动化对象的双重接口。 双重接口具有很高的优势 IDispatch一个或 VTBL 接口:
绑定可以在编译时通过 VTBL 接口,或在运行时通过 IDispatch。
可以使用 VTBL 接口的 OLE 自动化控制器受益提高性能。
使用 IDispatch 接口的现有的 OLE 自动化控制器将继续工作。
VTBL 接口更易于从 C++ 调用。
双重接口对于兼容性所需的与 Visual Basic 对象支持功能。
添加双重接口支持。基于 CCmdTarget 的类
双重接口实际上是从 IDispatch派生的自定义接口。 最简单的方法实现双重接口在 CCmdTarget支持 - 使用 MFC 和类向导,基类是先实现在类中正常调度接口,然后后添加自定义接口。 大多数情况下,您的自定义接口实现将委托回 MFC IDispatch 实现。
首先,修改您的服务器的 ODL 文件定义对象的双重接口。 若要定义双重接口,必须使用接口语句,而不是 Visual C++ 向导生成的 DISPINTERFACE 语句。 而不是移除现有 DISPINTERFACE 语句,添加一个新的接口语句。 通过保留 DISPINTERFACE 窗体,则可以继续使用类向导将属性和方法添加到您的对象,但是,您必须添加等价的属性和方法添加到您的接口语句。
双重接口的一个接口语句必须具有 OLEAUTOMATION 和 双 属性,并且,必须从 IDispatch派生接口。 可以使用 GUIDGEN 示例创建双重接口的 IID :
[ uuid(0BDD0E81-0DD7-11cf-BBA8-444553540000), // IID_IDualAClick
oleautomation,
dual
]
interface IDualAClick : IDispatch
{
};
一旦具有适当接口语句,以添加方法和属性的项。 对于双绑定接口,需要重新排列参数,以便您的方法和属性访问器函数在双重接口返回 HRESULT 并将其返回值作为参数使用特性 [retval,out]。 确保为属性,则需要添加读取 (propget) 和写入 (propput) 具有相同 ID. 的访问功能 例如:
[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 的接口映射结构定义双重接口的实现类在对象类和在此 MFC 的 QueryInterface 结构的相应项。 在 INTERFACE_PART 需要一项为 ODL 的接口语句的每个块,以及调度接口的项。 与 propput 特性的每个 ODL 项需要名为 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)
若要连接双重接口到 MFC 的 QI 结构,添加 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);
}
对于您的对象的方法和属性访问器函数,则需要填写实现。 您的方法和属性函数通常可以委托回使用类向导生成的方法。 但是,因此,如果您直接设置属性访问变量,需要编写代码来获取/将该值赋给变量。 例如:
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 在 MFC 中 IDispatch 指针只能。 回避此问题的一种方法将原始 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 自动化服务器应用程序的类型库。 当还可以通过其他方式向注册类型库时,有应用程序注册类型库很方便,即时,中,当更新其 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 选项并指定文件名。 Output header 文件名字段。 使用您的项目尚未使用的文件名,,因为 MkTypLib 将复盖任何现有文件。 单击关闭 生成设置 对话框的 确定 。
从 MkTypLib 生成的头文件添加 UUID 定义到项目中:
包括 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++ 选项,单击类别 预编译头,然后选择 Not using precompiled headers 单选按钮。 单击 " 确定 生成设置 对话框。
指定正确的对象类名称在类型库
向导随 Visual C++ 为 OLEcreatable 类不正确使用实现类名指定 coclass 服务器上的 ODL 文件。 因为这将会起作用,实现类名可能不是您想要对使用的对象用户的类名。 若要指定正确的名称,请打开 ODL 文件,找到每个 coclass 语句,并用正确的外部名称替换实现类名。
请注意,当更改 coclass 语句, CLSID中的变量名。 MkTypLib 生成的头文件中将相应地更改。 您将需要更新代码以使用新的变量名。
处理异常和自动化错误接口
您的自动化对象的方法和属性访问器函数可能会引发异常。 如果是这样,您应处理它们在您的有关异常的双重接口实现并将信息传递回该管理员通过 OLE 自动化错误处理的接口, IErrorInfo。 此接口提供详细,上下文错误信息。 IDispatch 和 VTBL 接口。 若要指示错误处理程序可用,则应实现 ISupportErrorInfo 接口。
若要声明该错误处理机制,假定,用于类向导生成的功能实现标准计划支持引发异常。 IDispatch::Invoke 的 MFC 的实现通常捕捉这些异常将其转换成通过 Invoke 调用返回的 EXCEPTINFO 结构。 但是,在使用时, VTBL 接口,您负责捕获异常。 例如保护您的双重接口方法:
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 负责返回正确的错误代码。 使用 ICreateErrorInfo 接口,CATCH_ALL_DUAL 转换 MFC 异常为 OLE 自动化错误处理的信息。 (示例 CATCH_ALL_DUAL 宏在 ACDUAL 示例中的文件 MFCDUAL.H。 它调用处理异常的函数, DualHandleException,在文件 MFCDUAL.CPP。) CATCH_ALL_DUAL 确定错误代码返回基于发生异常的类型:
COleDispatchException –使用以下代码,在这种情况下, HRESULT 构造:
hr = MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, (e->m_wCode + 0x200));
这将创建 HRESULT 特定于导致异常的接口。 错误代码可由避免与系统定义的 HRESULT中的所有冲突的 0x200 标准 OLE 接口的。
CMemoryException (在这种情况下, E_OUTOFMEMORY 返回。
其他异常 (在这种情况下, E_UNEXPECTED 返回。
若要指示使用 OLE 自动化错误处理程序,还应实现 ISupportErrorInfo 接口。
首先,请将代码添加到您的自动化类定义显示它支持 ISupportErrorInfo。
接下来,请将代码添加到您的自动化类的接口映射关联 ISupportErrorInfo 实现类和 MFC 的 QueryInterface 结构。 INTERFACE_PART 语句与为 ISupportErrorInfo定义的类。
最后,实现中定义的类支持 ISupportErrorInfo。
( ACDUAL 示例包含三宏帮助执行这三个步骤, DECLARE_DUAL_ERRORINFO、 DUAL_ERRORINFO_PART和 IMPLEMENT_DUAL_ERRORINFO,所有包含在 MFCDUAL.H.)
下面的示例实现中定义的类支持 ISupportErrorInfo。 CAutoClickDoc 是您的自动化类的名称,然后 IID_IDualAClick 是通过 OLE 自动化错误对象报告错误源接口的 IID :
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;
}