注释
自联机文档中首次包含此说明以来,尚未更新以下技术说明。 因此,某些过程和主题可能过期或不正确。 有关最新信息,建议在在线文档索引中搜索您感兴趣的主题。
此说明讨论如何将双接口支持添加到基于 MFC 的 OLE 自动化服务器应用程序。 ACDUAL 示例演示了双接口支持,此说明中的示例代码取自 ACDUAL。 此说明中所述的宏(如 DECLARE_DUAL_ERRORINFO、DUAL_ERRORINFO_PART 和 IMPLEMENT_DUAL_ERRORINFO)是 ACDUAL 示例的一部分,可在 MFCDUAL.H 中找到。
双接口
尽管 OLE 自动化允许你实现 IDispatch 接口、VTBL 接口或双接口(包含两者),Microsoft强烈建议为所有公开的 OLE 自动化对象实现双接口。 双接口在仅限 -only 接口或仅 VTBL 接口方面 IDispatch具有显著优势:
绑定可以通过 VTBL 接口在编译时发生,也可以在运行时通过
IDispatch。可以使用 VTBL 接口的 OLE 自动化控制器可能会受益于性能的提高。
使用
IDispatch接口的现有 OLE 自动化控制器将继续工作。VTBL 接口更易于从C++调用。
需要双接口才能与 Visual Basic 对象支持功能兼容。
将 Dual-Interface 支持添加到 CCmdTarget-Based 类
双接口实际上只是派生自 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]将其返回值作为参数传递。 请记住,对于属性,需要添加具有相同 ID 的读取(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 的接口映射机制为对象类中的双接口定义实现类,并在 MFC 的机制 QueryInterface 中生成相应的条目。 ODL 接口语句中每个条目的块中 INTERFACE_PART 都需要一个条目,外加调度接口的条目。 具有 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 的 QueryInterface 机制,请将条目 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;
}
传递 Dual-Interface 指针
传递双接口指针并不简单,尤其是在需要调用 CCmdTarget::FromIDispatch时。
FromIDispatch 仅适用于 MFC 的 IDispatch 指针。 解决此问题的一种方法是查询 MFC 设置的原始 IDispatch 指针,并将该指针传递给需要它的函数。 例如:
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
修改项目生成设置以适应类型库更改
若要修改项目的生成设置,以便在重新生成类型库时,MkTypLib 生成包含 UUID 定义的头文件:
在“ 生成 ”菜单上,单击“ 设置”,然后从每个配置的文件列表中选择 ODL 文件。
单击 “OLE 类型 ”选项卡,并在“ 输出标头 文件名”字段中指定文件名。 使用项目尚未使用的文件名,因为 MkTypLib 将覆盖任何现有文件。 单击“ 确定 ”关闭“ 生成设置 ”对话框。
若要将 MkTypLib 生成的头文件中的 UUID 定义添加到项目,
在标准中包含 MkTypLib 生成的头文件,包括头文件 stdafx.h。
创建新文件 INITIIDS。CPP,并将其添加到项目。 在此文件中,在包括 OLE2 之后包括 MkTypLib 生成的头文件。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++附带的向导错误地使用实现类名称在服务器的 ODL 文件中为可创建 OLE 类指定 coclass。 虽然这不起作用,但实现类名称可能不是你希望对象用户使用的类名。 若要指定正确的名称,请打开 ODL 文件,找到每个 coclass 语句,并将实现类名替换为正确的外部名称。
请注意,更改 coclass 语句时,MkTypLib 生成的头文件中 CLSID的变量名称将相应地更改。 需要更新代码才能使用新的变量名称。
处理异常和自动化错误接口
自动化对象的方法和属性访问器函数可能会引发异常。 如果是这样,则应在双接口实现中处理它们,并通过 OLE 自动化错误处理接口 IErrorInfo将有关异常的信息传递回控制器。 此接口提供通过这两个 IDispatch 接口和 VTBL 接口提供详细的上下文错误信息。 若要指示错误处理程序可用,应实现 ISupportErrorInfo 接口。
为了说明错误处理机制,假定用于实现标准调度支持的 ClassWizard 生成的函数引发异常。 MFC 的实现 IDispatch::Invoke 通常捕获这些异常,并将其转换为通过调用返回的 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 处理发生异常时返回正确的错误代码。
CATCH_ALL_DUAL 使用 ICreateErrorInfo 接口将 MFC 异常转换为 OLE 自动化错误处理信息。 (文件 MFCDUAL 中提供了一个示例 CATCH_ALL_DUAL 宏。 ACDUAL 示例中的 H。它调用以处理异常的 DualHandleException函数位于文件 MFCDUAL 中。CPP.) CATCH_ALL_DUAL 根据发生的异常类型确定要返回的错误代码:
COleDispatchException - 在本例中,
HRESULT使用以下代码构造:hr = MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, (e->m_wCode + 0x200));这会创建一个
HRESULT特定于导致异常的接口。 错误代码被0x200偏移,以避免与标准 OLE 接口的系统定义HRESULTs 发生任何冲突。CMemoryException - 在本例中返回
E_OUTOFMEMORY。任何其他异常 - 在这种情况下,
E_UNEXPECTED将返回。
若要指示使用 OLE 自动化错误处理程序,还应实现 ISupportErrorInfo 接口。
首先,将代码添加到自动化类定义以显示它支持 ISupportErrorInfo。
其次,将代码添加到自动化类的接口映射,以将 ISupportErrorInfo 实现类与 MFC 的机制 QueryInterface 相关联。 该 INTERFACE_PART 语句与为 ISupportErrorInfo.. 定义的类匹配。
最后,实现定义为支持的 ISupportErrorInfo类。
(ACDUAL 示例包含三个宏来帮助执行这三个步骤,DECLARE_DUAL_ERRORINFODUAL_ERRORINFO_PART以及 IMPLEMENT_DUAL_ERRORINFOMFCDUAL.H 中包含的所有宏)
以下示例实现一个定义为支持的 ISupportErrorInfo类。
CAutoClickDoc 是自动化类的名称, IID_IDualAClick 是接口的 IID ,它是通过 OLE 自动化错误对象报告的错误源:
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;
}