创建 COM 组件对象
- W_Z_C
- 共 1607 字,阅读约 4 分钟
当在线程内初始化 COM 组件 库之后就可以安全的调用 COM 接口了。如果想要使用 COM 接口,你的程序首先创建一个实现该接口的对象。
一般情况下,有两种方式创建 COM 组件对象:
- 使用模块本身专门提供用来创建对象的函数。
- 使用 COM 组件自身提供的函数 CoCreateInstance。
1. COM 组件专门的创建接口
依然用 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 是一个指针的指针。如果你没有见过这种方式,理解起来可能需要一些时间。
考虑一下 CreateShape 函数,这个函数必须返回一个 IDrawable 类型的接口指针对象,但是这个函数需要用返回值来获取错误代码,所以 IDrawable 的指针只能通过参数返回。
函数的使用者将指针变量通过参数传递给 CreateShape 函数内部,函数会生成一个新的 IDrawable 指针,并将指针保存到传入的参数中。在 C++ 中,有两种方式可以将结果传出,一种是引用,一种是指针,COM 使用了后者,这也是一般 C语言中使用的方式。
在 C/C++ 语言中,如果用参数返回结果,就会涉及到形参实参的问题,如果想要正确传出,必须要使用对应类型的指针,所以 IDrawable 指针类型的指针,就是指针的指针,所以参数必须是 IDrawable**
。
下面是该过程的图示:
2. 常规的创建方法 CoCreateInstance
CoCreateInstance 提供一种常规的创建对象的方法。如果想要理解 CoCreateInstance,首先需要记住不同的 COM 组件对象可以拥有相同的接口,而一个 COM 组件对象又可以继承实现多个接口。所以,一个通用的创建对象的函数需要两方面的信息:
- 要创建什么对象。
- 要从对象中获取什么接口。
当我在调用函数的时候,如何才能提供这些信息?在 COM 组件 中,对象或者接口都会用一个128位的编号标识,这种标识被称为全球唯一标识(globally unique identifier),也就是常说的 GUID。
GUID 的生成算法能保证它的唯一性。在没有一个中央控制统筹的情况下,GUID 是保证标识唯一性的可靠方式之一。有时候 GUID 也会被称为(universally unique identifiers),即 UUID。在 COM 之前,它们被用在 DCE/RPC(Distributed Computing Environment/Remote Procedure Call) 中。GUID 生成算法并不唯一,不是所有的算法都会确保唯一性,但是重复的几率非常小,可以说基本为零。
对于前面案例中的 Shapes 库来说,可以声明两个GUID 常量:
extern const GUID CLSID_Shape;
extern const GUID IID_IDrawable;
常量 CLSID_Shape 用来标识 Shape 对象,IID_IDrawable 用来标识 IDrawable 接口,它们都是128位的字符串。CLSID 前缀的常量代表类的标识,而 IID 前缀的常量代表接口标识,这个是 COM 组件的标准约定。
通过这些值,你可以创建一个新的 Shape 接口:
IDrawable *pShape;
hr = CoCreateInstance(CLSID_Shape, NULL, CLSCTX_INPROC_SERVER, IID_Drawable,
reinterpret_cast<void**>(&pShape));
if (SUCCEEDED(hr))
{
// Use the Shape object.
}
else
{
// An error occurred.
}
CoCreateInstance 函数包含五个参数。第一个和第四个参数分别代表类和接口的标识。实际上这几个参数的含义是,创建一个 Shape 对象,给我返回一个指向 IDrawable 接口的指针。
第二个参数设置为 NULL。第三个参数接收一组标记,用来确定对象的执行上下文(execution context)。这个执行上下文标识着对象是否和应用程序在同一个进程中运行。可以指定在相同的机器不同的进程中或者在远程计算机中。下面该参数常用的标记:
标记 | 含义 |
---|---|
CLSCTX_INPROC_SERVER | 同一个进程 |
CLSCTX_LOCAL_SERVER | 相同计算机,不同进程 |
CLSCTX_REMOTE_SERVER | 不同计算机 |
CLSCTX_ALL | 使用对象支持的最有效选项,(从最高效到最低效的排名是:进程内、进程外和跨计算机。) |
在特定组件的文档中可能会告诉你如何指定执行上下文,如果没有说明,可以使用 CLSCTX_ALL。如果你请求的执行上下文 COM 组件不支持,则CoCreateInstance 函数会返回错误代码 REGDB_E_CLASSNOTREG。如果组件没有注册对应的 CLSID 标识,函数也会返回这个错误码。
第五个参数接收一个接口指针变量,因为 CoCreateInstance 函数适用于所有组件,所以无法确认接口的具体类型,因此这个参数的类型是 void**。如果你想得到自己想要的接口指针,你需要使用 reinterpret_cast 强制转换。
检测 CoCreateInstance 函数的返回值至关重要,如果函数返回一个错误码,这个 COM 接口指针是无效的,尝试使用它可能会导致你程序的崩溃。
在 CoCreateInstance 函数的内部会使用多种技术创建一个对象。在简单的案例中,它会在注册表中查找类标识。注册表的入口指向实现了该类的 DLL 或者 EXE 程序。除了注册表,CoCreateInstance 也可以使用 COM+ 中的目录或者 SxS 清单列表。不过这些对于调用者来说都是不可见的。
上面例子中的 Shapes 类并不是很自然,现实中 COM 组件和它还是有很大差别的,下面我们展示一个调用真实 COM 组件 的例子以供学习。