在 COM 中创建对象

线程初始化 COM 库后,线程可以使用 COM 接口是安全的。 若要使用 COM 接口,程序首先创建实现该接口的对象实例。

一般情况下,有两种方法可以创建 COM 对象:

  • 实现对象的模块可以提供专门设计用于创建该对象的实例的函数。
  • 或者,COM 提供名为 CoCreateInstance 的泛型创建函数。

例如,从主题“什么是 COM 接口”中获取假设Shape对象? 在该示例中,该 Shape 对象实现名为 的 IDrawable接口。 实现对象的 Shape 图形库可能会导出具有以下签名的函数。

// Not an actual Windows function. 

HRESULT CreateShape(IDrawable** ppShape);

鉴于此函数,可以创建如下所示的新 Shape 对象。

IDrawable *pShape;

HRESULT hr = CreateShape(&pShape);
if (SUCCEEDED(hr))
{
    // Use the Shape object.
}
else
{
    // An error occurred.
}

ppShape 参数的类型为指针到指针到指针。IDrawable 如果以前没有看到这种模式,双重间接可能令人费解。

考虑函数的要求 CreateShape 。 该函数必须返回 IDrawable 指向调用方的指针。 但函数的返回值已用于错误/成功代码。 因此,必须通过指向函数的参数返回指针。 调用方将向函数传递类型 IDrawable* 变量,该函数将使用新 IDrawable 指针覆盖此变量。 在 C++ 中,函数仅可通过两种方式覆盖参数值:按引用传递或按地址传递。 COM 使用后一个传递地址。 指针的地址是指向指针的指针,因此参数类型必须是 IDrawable**

下面是一个图表,用于帮助直观显示正在发生的事情。

diagram that shows double pointer indirection

CreateShape 函数使用 pShape 的地址 (&pShape) 将新的指针值写入 pShape

CoCreateInstance:创建对象的通用方法

CoCreateInstance 函数提供用于创建对象的泛型机制。 若要了解 CoCreateInstance,请记住,两个 COM 对象可以实现相同的接口,一个对象可以实现两个或多个接口。 因此,创建对象的泛型函数需要两段信息。

  • 要创建的对象。
  • 要从对象中获取的接口。

但是,当我们调用函数时,如何指示此信息? 在 COM 中,通过为其分配 128 位数字来标识对象或接口,该数字称为 全局唯一标识符 (GUID) 。 GUID 以有效唯一的方式生成。 GUID 是解决如何在没有中央注册机构的情况下创建唯一标识符的问题。 GUID 有时称为 通用唯一标识符 (UUID) 。 在 COM 之前,它们用于 DCE/RPC (分布式计算环境/远程过程调用) 。 存在多个用于创建新 GUID 的算法。 并非所有算法都严格保证唯一性,但意外创建同一 GUID 值的概率极小,实际上为零。 GUID 可用于标识任何类型的实体,而不仅仅是对象和接口。 但是,这只是在本模块中关注我们的唯一用途。

例如, Shapes 库可能声明两个 GUID 常量:

extern const GUID CLSID_Shape;
extern const GUID IID_IDrawable; 

(可以假定这些常量的实际 128 位数值在别处定义。) 常量 CLSID_Shape 标识 Shape 对象,而常量 IID_IDrawable 标识 IDrawable 接口。 前缀“CLSID”表示 类标识符,前缀 IID 表示 接口标识符。 它们是 COM 中的标准命名约定。

鉴于这些值,将创建一个新 Shape 实例,如下所示:

IDrawable *pShape;
hr = CoCreateInstance(CLSID_Shape, NULL, CLSCTX_INPROC_SERVER, IID_IDrawable,
     reinterpret_cast<void**>(&pShape));

if (SUCCEEDED(hr))
{
    // Use the Shape object.
}
else
{
    // An error occurred.
}

CoCreateInstance 函数具有五个参数。 第一个和第四个参数是类标识符和接口标识符。 实际上,这些参数告知函数“创建 Shape 对象,并为我提供指向 IDrawable 接口的指针。

将第二个参数设置为 NULL。 (有关此参数的含义的详细信息,请参阅 COM 文档中的主题 聚合 。) 第三个参数采用一组标志,其主要目的是指定对象的 执行上下文 。 执行上下文指定对象是否在与应用程序相同的进程中运行;在同一台计算机上的不同进程中;或远程计算机上。 下表显示了此参数的最常见值。

标志 描述
CLSCTX_INPROC_SERVER 相同的过程。
CLSCTX_LOCAL_SERVER 不同的进程,同一台计算机。
CLSCTX_REMOTE_SERVER 不同的计算机。
CLSCTX_ALL 使用对象支持的最有效选项。 (排名,从效率最高到效率最低,是:进程内、进程外和跨计算机。)

 

特定组件的文档可能会告知对象支持哪些执行上下文。 如果没有,请使用 CLSCTX_ALL。 如果请求对象不支持的执行上下文, CoCreateInstance 函数将返回错误代码 REGDB_E_CLASSNOTREG。 此错误代码还可以指示 CLSID 与用户计算机上注册的任何组件不对应。

CoCreateInstance 的第五个参数接收指向接口的指针。 由于 CoCreateInstance 是一种泛型机制,因此不能强类型化此参数。 相反,数据类型为 void**,调用方必须强制指向 void 类型的 指针的地址。 这是上一示例中 reinterpret_cast 的目的。

检查 CoCreateInstance 的返回值至关重要。 如果函数返回错误代码,COM 接口指针无效,并且尝试取消引用它可能会导致程序崩溃。

在内部, CoCreateInstance 函数使用各种技术来创建对象。 最简单的情况下,它会在注册表中查找类标识符。 注册表项指向实现对象的 DLL 或 EXE。 CoCreateInstance 还可以使用 COM+ 目录或并行 (SxS) 清单中的信息。 不管怎样,详细信息对调用方都是透明的。 有关 CoCreateInstance 的内部详细信息的详细信息,请参阅 COM 客户端和服务器

Shapes我们一直在使用的示例有点复杂,因此现在让我们转向实际操作的 COM 示例:显示“打开”对话框供用户选择文件。

下一步

示例:“打开”对话框