【COM API】基础知识与使用方法
本文最后更新于:7 天前
初识 COM API
和 WinAPI 类似,COM API 也提供了一系列函数供应用程序调用来实现功能。两者都属于 Windows API 范畴,但在调用方式上有着很大的不同。
WinAPI | COM API |
---|---|
调用 DLL 中导出的函数 | 通过接口指针调用虚函数 |
指定函数名就可以调用 | 指定 CLSID 和 IID 进行初始化 |
调用完不需要释放函数 | 调用完需手动释放以及销毁接口指针 |
从现在开始,本文将向大家讲述有关 COM API 的基础知识以及使用模板,我们将使用 C++ 将 COM API 封装为 WinAPI 供 C# 或其他程序调用 (C# 本来也可以初始化和使用 COM API,但不是本文重点)。
HRESULT
HRESULT 几乎是所有 COM API 函数的返回类型,用于表示函数是否执行成功。可以把它类比为 BOOL,因为它们本质上都是整型 (long 和 int)。但两者在真假判定上也有区别。
BOOL | HRESULT | 真假 |
---|---|---|
!= 0 |
>= 0 |
true |
== 0 |
< 0 |
false |
那我们怎么在编程中判断两者的真假呢?
BOOL:直接写在 if 表达式里就行。
1 |
|
HRESULT:使用 SUCCEEDED 或 FAILED 宏,两者分别用于判断是否执行成功或失败。
1 |
|
初始化/取消初始化 COM 运行时
在初始化 COM API 前,我们需要初始化 COM 运行时。一旦初始化之后且离开作用域,我们必须取消初始化 COM 运行时。
也就是两个步骤必须成对进行,在哪里初始化的,就必须要在哪里取消初始化,二者缺一不可。
1 |
|
这里仅供演示需要引入 combaseapi.h,实际开发中引入接口所在的头文件就行。
我们调用了 CoInitializeEx 函数来初始化 COM 运行时,COINIT_APARTMENTTHREADED 表示单线程单元模型(STA), 与之相对的还有 COINIT_MULTITHREADED 多线程单元模型 (MTA)。我们不用清楚两者有什么区别,只需要知道几乎所有 COM API 的运行环境都是 STA 就行了。
如果你开发过 WinForms 或者是 WPF,在 Main 方法上,你都能看到被标注了 [STAThread]
,这个属性的背后就是 CLR 运行时在此初始化了 COM 运行环境为 STA。也就是说,对于 WinForms 或 WPF 应用程序,我们在调用 COM API 之前就可以不用初始化 COM 运行时了,因为 CLR 已经完成了初始化,重复初始化可能会发生潜在的问题。
CoUninitialize 表示取消初始化 COM 运行环境。同样的,如果是 WinForms 或 WPF 的话,我们也无需再手动取消初始化,CLR 会帮我们完成。
初始化 COM API 接口指针
如果我们已经初始化了 COM 运行时并获取到了 COM 接口实体,以及它的 CLSID、IID (接口 ID),接下来就可以进行初始化,成功后便可以调用其中的虚函数了。
我们通常使用接口指针来表示要初始化的 COM 对象,因为 COM 本身就是一堆虚函数,我们只有通过指针来调用它们。
接着调用 CoCreateInstance,传入 CLSID、IID、指向接口指针的指针,并使用 SUCCEEDED 宏判断是否成功:
1 |
|
调用 COM API
当运行时和接口指针都初始化成功后,我们就可以调用我们想要的函数了
1 |
|
释放 COM API 接口指针
1 |
|
封装为 WinAPI
很简单,将我们上面提到的函数导出就行,注意保持调用约定一致 (StdCall)。
为了方便导出,我们可以添加一个宏定义:
1 |
|
1 |
|
可能的疑惑
- 为什么导出后,函数类型是 void 而不是 HRESULT?
这个我只能说是个人习惯吧,如果你想在调用端获取执行状态的话,可以导出为 HRESULT 返回类型。但是要知道这些 COM API 同 WinAPI 一样,只要我们符合规范没写错的话,出错率是很小的,很多情况出现异常通常都是我们自己的问题,肯定是哪里漏写之类的,不太可能是系统的问题。就算真不是我们的问题,那到时候再来调试也行,故返回值就没太大必要了。