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_PROPERTYDISP_FUNCTION 宏的 _ID 版本)

高级 MFC 调度映射功能

在此版本的 Visual C++ 中,有许多 ClassWizard 不支持的附加功能。 ClassWizard 支持 DISP_FUNCTIONDISP_PROPERTYDISP_PROPERTY_EX,它们分别定义方法、成员变量属性和 get/set 成员函数属性。 创建大多数自动化服务器通常只需要这些功能。

当 ClassWizard 支持的宏不够时,可以使用以下附加宏: DISP_PROPERTY_NOTIFYDISP_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 析构函数默认执行此操作)。

另请参阅

按编号列出的技术说明
按类别列出的技术说明