com组件必须继承iunknown并手动管理引用计数:所有接口须直接或间接继承iunknown,addref/release需线程安全实现,queryinterface须精确匹配iid并支持iunknown及所有注册接口,构造函数不增引用,release返回0时delete this,且类声明须用__declspec(uuid)绑定guid并多重继承。

COM组件必须继承 IUnknown 并手动管理引用计数
Windows COM 要求每个接口必须从 IUnknown 派生,且所有引用计数操作(AddRef、Release)必须由组件自身严格控制。C++里没有自动内存管理,不能依赖智能指针替代 COM 的生命周期语义——std::shared_ptr 会破坏 COM 客户端的调用约定,导致崩溃或资源泄漏。
-
AddRef和Release必须是线程安全的(通常用InterlockedIncrement/InterlockedDecrement) - 构造函数不增加引用计数;首次返回接口指针时,调用方隐式持有 1 引用
-
Release返回 0 时才真正 delete this,且必须确保此时不再有其他线程访问该对象 - 不要在
Release中调用delete this后继续执行任何成员访问(包括虚函数表读取)
QueryInterface 实现必须支持 IUnknown 及所有已注册接口
QueryInterface 是 COM 多态的核心,它不负责引用计数增减(除返回的接口外),但必须精确匹配 IID。常见错误是漏掉 IID_IUnknown 或使用 memcmp 比较 GUID——必须用 IsEqualGUID 或直接比较 ==(Windows SDK 中已重载)。
- 先检查
riid == __uuidof(IUnknown)或riid == __uuidof(IMyInterface) - 匹配成功后,对目标接口调用
AddRef(),再通过*ppvObject = static_cast<imyinterface>(this)</imyinterface>赋值 - 不匹配时置
*ppvObject = nullptr并返回E_NOINTERFACE - 所有接口指针必须能回溯到同一对象实例(即
static_cast<iunknown></iunknown>结果一致)
类声明需用 __declspec(uuid) 和多重继承显式导出接口
编译器需要知道每个接口的二进制布局和 GUID,否则 QueryInterface 匹配失败。不能只靠头文件声明,必须用 __declspec(uuid) 绑定 GUID,并让实现类同时继承多个接口(含 IUnknown)。
struct __declspec(uuid("00000000-0000-0000-C000-000000000046")) IUnknown;
struct __declspec(uuid("A1234567-B89C-DE01-F234-567890123456")) IMyInterface : IUnknown {
virtual HRESULT STDMETHODCALLTYPE DoWork() = 0;
};
<p>class MyComponent final : public IUnknown, public IMyInterface {
public:
// IUnknown methods
STDMETHOD(QueryInterface)(REFIID riid, void** ppvObject) override;
STDMETHOD<em>(ULONG, AddRef)() override;
STDMETHOD</em>(ULONG, Release)() override;
// IMyInterface method
STDMETHOD(DoWork)() override;
private:
LONG m_cRef = 0;
};</p>
注册与使用时必须注意模块级 COM 初始化和线程模型
即使组件逻辑正确,若宿主未调用 CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED),或组件 DLL 未正确导出 DllGetClassObject,客户端调用 CoCreateInstance 仍会失败,报错 REGDB_E_CLASSNOTREG 或 CO_E_NOTINITIALIZED。
- 服务端 DLL 必须实现
DllGetClassObject,返回对应 CLSID 的类厂(IClassFactory) - 类厂的
CreateInstance方法中 new 出组件实例,再调用其QueryInterface获取请求接口 - 客户端必须在同一线程模型下使用接口(例如 STA 线程不能直接跨线程传递原始接口指针)
- 调试时可用
oleview.exe查看注册表中 CLSID 是否存在、InprocServer32 路径是否正确
引用计数本身逻辑简单,但 COM 的约束嵌套在 ABI、线程模型、注册机制里——漏掉任一环,都会表现为“对象析构过早”或“Release 不触发销毁”,而不是编译错误。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











