在 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 参数的类型为 pointer-to-pointer-to-IDrawable
。 如果以前没有见过此模式,双重间接可能令人费解。
请考虑函数的要求 CreateShape
。 该函数必须将指针送 IDrawable
回调用方。 但函数的返回值已用于错误/成功代码。 因此,必须通过函数的参数返回指针。 调用方会将 类型的 IDrawable*
变量传递给函数,该函数将使用新 IDrawable
指针覆盖此变量。 在 C++ 中,函数只有两种方法可以覆盖参数值:按引用传递或按地址传递。 COM 使用后一种传递地址。 指针的地址是指向指针的指针,因此参数类型必须为 IDrawable**
。
下图有助于直观显示所发生的情况。
函数 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 文档中的主题聚合。) 第三个参数采用一组标志,其main目的是指定对象的执行上下文。 执行上下文指定对象是否在与应用程序相同的进程中运行;在同一台计算机上的不同进程中;或远程计算机上的 。 下表显示了此参数的最常见值。
标志 | 描述 |
---|---|
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 示例:显示“打开”对话框,供用户选择文件。
下一步