TN039:MFC/OLE 自动化实现

注释

自联机文档中首次包含此说明以来,尚未更新以下技术说明。 因此,某些过程和主题可能过期或不正确。 有关最新信息,建议在在线文档索引中搜索您感兴趣的主题。

OLE IDispatch 接口概述

接口 IDispatch 是应用程序公开方法和属性的方法和属性,以便其他应用程序(如 Visual BASIC 或其他语言)可以使用应用程序的功能。 此接口最重要的部分是 IDispatch::Invoke 函数。 MFC 使用“调度映射”来实现 IDispatch::Invoke。 调度映射提供有关派生类的布局或“形状”的 CCmdTargetMFC 实现信息,以便它可以直接作对象的属性,或调用对象中的成员函数来满足 IDispatch::Invoke 请求。

在大多数情况下,ClassWizard 和 MFC 合作将 OLE 自动化的大部分详细信息隐藏在应用程序程序员中。 程序员专注于在应用程序中公开的实际功能,无需担心基础管道。

但是,在某些情况下,有必要了解 MFC 在幕后执行的作。 此说明将说明框架如何将 DISPID分配给成员函数和属性。 仅当需要知道 ID 时(例如,为应用程序的对象创建“类型库”时),才需要了解用于分配 DISPID的算法 MFC。

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 分为两个部分。 DISPIDLOWORD 包含第一个组件,即与调度映射顶部的距离。 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

由于属性不在基类中,DISPIDHIWORD 始终为零(CDispPoint 最派生类的距离为零)。

MFC 将为类 CDisp3DPoint 生成 DISPIDs,如下所示:

property Z    (DISPID)0x00000001
property X    (DISPID)0x00010001
property Y    (DISPID)0x00010002

Z 属性的 DISPID 具有零 HIWORD ,因为它是在公开属性 CDisp3DPoint 的类中定义的。 由于 X 和 Y 属性是在基类中定义的,因此 DISPIDHIWORD 为 1,因为定义这些属性的类与大多数派生类的一个派生相距。

注释

LOWORD 始终由地图中的位置确定,即使地图中存在具有显式 DISPID 的条目(有关_ID版本DISP_PROPERTYDISP_FUNCTION宏的信息,请参阅下一部分)。

高级 MFC 调度映射功能

ClassWizard 在此版本的 Visual C++ 中不支持其他许多功能。 ClassWizard 支持 DISP_FUNCTIONDISP_PROPERTY以及 DISP_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)

对应于获取和设置成员函数:

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 自动分配一个。 这些高级宏具有相同的名称,但 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 析构函数默认执行此作)。

另请参阅

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