TN039:MFC/OLE 自动化实现
注意
以下技术说明在首次包括在联机文档中后未更新。 因此,某些过程和主题可能已过时或不正确。 要获得最新信息,建议你在联机文档索引中搜索热点话题。
OLE IDispatch 接口概述
应用程序采用 IDispatch
接口公开方法和属性,以便其他应用程序(如 Visual BASIC)或其他语言可以使用应用程序的功能。 此接口最重要的部分是 IDispatch::Invoke
函数。 MFC 使用“调度映射”实现 IDispatch::Invoke
。 调度映射提供有关 CCmdTarget
派生类的布局或“形状”的 MFC 实现信息,以便它可以直接操作对象的属性,或调用对象中的成员函数来满足 IDispatch::Invoke
请求。
在大多数情况下,ClassWizard 和 MFC 合作对应用程序程序员隐藏 OLE 自动化的大部分详细信息。 程序员可专注于要在应用程序中公开的实际功能,无需担心基础管道。
但是,在某些情况下,有必要了解 MFC 在后台执行的操作。 本文将说明框架如何将 DISPID 分配给成员函数和属性。 仅当你需要知道 ID(例如,为应用程序的对象创建“类型库”)时,才需要了解 MFC 用于分配 DISPID 的算法。
MFC DISPID 分配
尽管自动化的最终用户(例如 Visual Basic 用户)可在代码(例如 obj.ShowWindow)中看到已启用自动化的属性和方法的实际名称,但 IDispatch::Invoke
的实现不会接收实际名称。 出于优化原因,它会接收一个 DISPID,这是一个 32 位“magic cookie”,用于描述要访问的方法或属性。 这些 DISPID 值是通过名为 IDispatch::GetIDsOfNames
的另一种方法从 IDispatch
实现返回的。 自动化客户端应用程序将为它打算访问的每个成员或属性调用一次 GetIDsOfNames
,并缓存它们以便之后用于调用 IDispatch::Invoke
。 这样一来,只会在每次使用对象而不是每次调用 IDispatch::Invoke
时完成一次成本较高的字符串查找。
MFC 根据以下两项内容确定每种方法和每个属性的 DISPID:
到调度映射顶部的距离(相对值 1)
调度映射到最派生类的距离(相对值 0)
DISPID 分为两部分。 DISPID 的 LOWORD 包含第一个组件,即到调度映射顶部的距离。 HIWORD 包含到最派生类的距离。 例如:
class CDispPoint : public CCmdTarget
{
public:
short m_x, m_y;
// ...
DECLARE_DISPATCH_MAP()
// ...
};
class CDisp3DPoint : public CDispPoint
{
public:
short m_z;
// ...
DECLARE_DISPATCH_MAP()
// ...
};
BEGIN_DISPATCH_MAP(CDispPoint, CCmdTarget)
DISP_PROPERTY(CDispPoint, "x", m_x, VT_I2)
DISP_PROPERTY(CDispPoint, "y", m_y, VT_I2)
END_DISPATCH_MAP()
BEGIN_DISPATCH_MAP(CDisp3DPoint, CDispPoint)
DISP_PROPERTY(CDisp3DPoint, "z", m_z, VT_I2)
END_DISPATCH_MAP()
如你所见,有两个类,它们都公开了 OLE 自动化接口。 其中一个类派生自另一个类,因此可利用基类的功能,包括 OLE 自动化部分(在本例中为“x”和“y”属性)。
MFC 将为类 CDispPoint 生成 DISPID,如下所示:
property X (DISPID)0x00000001
property Y (DISPID)0x00000002
由于属性不在基类中,因此 DISPID 的 HIWORD 始终为零(到 CDispPoint 的最派生类的距离为零)。
MFC 将为类 CDisp3DPoint 生成 DISPID,如下所示:
property Z (DISPID)0x00000001
property X (DISPID)0x00010001
property Y (DISPID)0x00010002
为 Z 属性提供了 HIWORD 为零的 DISPID,因为它是在公开属性的类 CDisp3DPoint 中定义的。 由于 X 和 Y 属性是在基类中定义的,因此 DISPID 的 HIWORD 为 1,因为在其中定义这些属性的类与最派生类相距一个派生类。
注意
LOWORD 始终由映射中的位置确定,即使映射中存在具有显式 DISPID 的条目(请参阅下一部分,了解有关 DISP_PROPERTY
和 DISP_FUNCTION
宏的 _ID 版本)。
高级 MFC 调度映射功能
在此版本的 Visual C++ 中,有许多 ClassWizard 不支持的附加功能。 ClassWizard 支持 DISP_FUNCTION
、DISP_PROPERTY
和 DISP_PROPERTY_EX
,它们分别定义方法、成员变量属性和 get/set 成员函数属性。 创建大多数自动化服务器通常只需要这些功能。
当 ClassWizard 支持的宏不够时,可以使用以下附加宏: DISP_PROPERTY_NOTIFY
和 DISP_PROPERTY_PARAM
。
DISP_PROPERTY_NOTIFY - 宏说明
DISP_PROPERTY_NOTIFY(
theClass,
pszName,
memberName,
pfnAfterSet,
vtPropType)
参数
theClass
类的名称。
pszName
属性的外部名称。
memberName
在其中存储属性的成员变量的名称。
pfnAfterSet
更改属性时要调用的成员函数的名称。
vtPropType
一个指定属性类型的值。
备注
此宏与 DISP_PROPERTY 非常类似,只不过它接受一个附加参数。 此附加参数 pfnAfterSet 应该是一个不返回任何内容且不采用任何参数的成员函数“void OnPropertyNotify()”。 在修改成员变量后将调用此参数。
DISP_PROPERTY_PARAM - 宏说明
DISP_PROPERTY_PARAM(
theClass,
pszName,
pfnGet,
pfnSet,
vtPropType,
vtsParams)
参数
theClass
类的名称。
pszName
属性的外部名称。
memberGet
用于获取属性的成员函数的名称。
memberSet
用于设置属性的成员函数的名称。
vtPropType
一个指定属性类型的值。
vtsParams
分隔每个参数的 VTS_ 的空格字符串。
注解
与 DISP_PROPERTY_EX 宏非常类似,此宏定义使用单独的 Get 和 Set 成员函数访问的属性。 但是,此宏使你能够指定属性的参数列表。 这对于实现以某种方式对属性编制索引或进行参数化非常有用。 参数将始终放在前面,后跟属性的新值。 例如:
DISP_PROPERTY_PARAM(CMyObject, "item", GetItem, SetItem, VT_DISPATCH, VTS_I2 VTS_I2)
将对应于 get 和 set 成员函数:
LPDISPATCH CMyObject::GetItem(short row, short col)
void CMyObject::SetItem(short row, short col, LPDISPATCH newValue)
DISP_XXXX_ID - 宏说明
DISP_FUNCTION_ID(
theClass,
pszName,
dispid,
pfnMember,
vtRetVal,
vtsParams)
DISP_PROPERTY_ID(
theClass,
pszName,
dispid,
memberName,
vtPropType)
DISP_PROPERTY_NOTIFY_ID(
theClass,
pszName,
dispid,
memberName,
pfnAfterSet,
vtPropType)
DISP_PROPERTY_EX_ID(
theClass,
pszName,
dispid,
pfnGet,
pfnSet,
vtPropType)
DISP_PROPERTY_PARAM_ID(
theClass,
pszName,
dispid,
pfnGet,
pfnSet,
vtPropType,
vtsParams)
参数
theClass
类的名称。
pszName
属性的外部名称。
dispid
属性或方法的固定 DISPID。
pfnGet
用于获取属性的成员函数的名称。
pfnSet
用于设置属性的成员函数的名称。
memberName
要映射到属性的成员变量的名称
vtPropType
一个指定属性类型的值。
vtsParams
分隔每个参数的 VTS_ 的空格字符串。
注解
通过这些宏,你可以指定 DISPID,而不是让 MFC 自动分配 DISPID。 这些高级宏的名称相同,只是 ID 会附加到宏名称后面(例如 DISP_PROPERTY_ID),并且 ID 由紧随 pszName 参数之后指定的参数确定。 请参阅 AFXDISP.H,了解有关这些宏的详细信息。 必须将 _ID 条目放置在调度映射的末尾。 这些条目将以与宏的非 _ID 版本相同的方式影响自动 DISPID 生成(DISPID 由位置确定)。 例如:
BEGIN_DISPATCH_MAP(CDisp3DPoint, CCmdTarget)
DISP_PROPERTY(CDisp3DPoint, "y", m_y, VT_I2)
DISP_PROPERTY(CDisp3DPoint, "z", m_z, VT_I2)
DISP_PROPERTY_ID(CDisp3DPoint, "x", 0x00020003, m_x, VT_I2)
END_DISPATCH_MAP()
MFC 将为类 CDisp3DPoint 生成 DISPID,如下所示:
property X (DISPID)0x00020003
property Y (DISPID)0x00000002
property Z (DISPID)0x00000001
指定固定 DISPID 有助于保持与先前存在的调度接口的向后兼容性,或实现某些系统定义的方法或属性(通常由负的 DISPID 表示,如 DISPID_NEWENUM 集合)。
检索 COleClientItem 的 IDispatch 接口
许多服务器将支持其文档对象中的自动化以及 OLE 服务器功能。 若要访问此自动化接口,需要直接访问 COleClientItem::m_lpObject
成员变量。 下面的代码将检索 IDispatch
接口以获取派生自 COleClientItem
的对象。 如果你发现此功能是必需的,可以在应用程序中包括以下代码:
LPDISPATCH CMyClientItem::GetIDispatch()
{
ASSERT_VALID(this);
ASSERT(m_lpObject != NULL);
LPUNKNOWN lpUnk = m_lpObject;
Run(); // must be running
LPOLELINK lpOleLink = NULL;
if (m_lpObject->QueryInterface(IID_IOleLink,
(LPVOID FAR*)&lpOleLink) == NOERROR)
{
ASSERT(lpOleLink != NULL);
lpUnk = NULL;
if (lpOleLink->GetBoundSource(&lpUnk) != NOERROR)
{
TRACE0("Warning: Link is not connected!\n");
lpOleLink->Release();
return NULL;
}
ASSERT(lpUnk != NULL);
}
LPDISPATCH lpDispatch = NULL;
if (lpUnk->QueryInterface(IID_IDispatch, &lpDispatch) != NOERROR)
{
TRACE0("Warning: does not support IDispatch!\n");
return NULL;
}
ASSERT(lpDispatch != NULL);
return lpDispatch;
}
然后,可以直接使用从此函数返回的调度接口或将其附加到 COleDispatchDriver
以进行类型安全访问。 如果直接使用它,请确保在使用指针时调用其 Release
成员(COleDispatchDriver
析构函数默认执行此操作)。