com接口必须用idl定义并经midl生成头文件,实现类须显式实现iunknown三方法;coinitializeex线程模型须与注册表threadingmodel严格匹配;dllgetclassobject需extern "c"导出且参数类型正确;atl仅封装不替代com契约。

COM 接口定义必须用 IDL 编写,不能直接在 C++ 头文件里手写
COM 接口不是普通 C++ 类接口,它的二进制布局、调用约定、内存模型都受 COM 规范硬性约束。你不能用 class IMyInterface 直接声明然后期望它能被其他语言或进程正确调用。
IDL(Interface Definition Language)是唯一被 Windows SDK 工具链(midl.exe)认可的接口定义方式,它生成的 .h 和 .c 文件才包含正确的 IUnknown 继承、__declspec(uuid(...))、STDMETHODCALLTYPE 等关键信息。
- 写
IMyService.idl,用interface关键字定义方法,每个方法返回HRESULT - 运行
midl /cpp_win32 IMyService.idl生成IMyService.h和IMyService_i.c - 在 C++ 实现类中继承生成的
IMyService,**必须显式实现QueryInterface、AddRef、Release** —— 即使你用 ATL,底层仍是靠这些函数做接口分发 - 别把
#import生成的智能指针头文件当成“接口定义”,那只是客户端封装,服务端仍需原始 IDL + 实现
CoInitializeEx 调用时机和线程模型必须匹配组件注册的 ThreadingModel
COM 不是线程安全的黑盒,它依赖你明确告诉系统当前线程的使用模式。如果组件注册表里 ThreadingModel 是 Apartment(常见于 UI 组件),但你在多线程环境里用 CoInitializeEx(NULL, COINIT_MULTITHREADED) 初始化,CoCreateInstance 就会失败或行为异常。
- 查看注册表:检查
HKEY_CLASSES_ROOT\CLSID\{...}\InprocServer32\ThreadingModel的值,常见为Apartment或Free - 初始化时严格对应:
COINIT_APARTMENTTHREADED对应Apartment,COINIT_MULTITHREADED对应Free - 主线程(如 Win32 GUI 线程)几乎总是要
COINIT_APARTMENTTHREADED;后台工作线程若需创建 COM 对象,也得按组件要求初始化,不能复用主线程的 COM 环境 - 忘记调用
CoUninitialize()会导致资源泄漏,且下次CoInitializeEx可能静默失败
导出 DllGetClassObject 必须用 extern "C" 和 __declspec(dllexport),且参数类型不能错
COM DLL 被加载时,宿主(如 ole32.dll)通过固定符号名 DllGetClassObject 查找入口。C++ 名字修饰(name mangling)会让链接器找不到它,参数类型不匹配则导致栈破坏。
- 必须写成:
extern "C" HRESULT __declspec(dllexport) DllGetClassObject(REFCLSID rclsid, REFIID riid, void** ppv) -
REFCLSID是const CLSID&,REFIID是const IID&—— 写成CLSID或IID值传递会编译通过但运行崩溃 - 返回前必须用
IsEqualGUID检查rclsid是否匹配你的组件,否则别人 CoCreate 你的 CLSID 会失败 - 不要在
DllGetClassObject里做耗时操作(如初始化全局资源),它可能被频繁调用;延迟到第一次QueryInterface或对象构造时更稳妥
ATL 简化了样板代码,但绕不开 COM 核心契约
用 ATL 向导生成的 CComObject 或 CAtlCOMModule 确实省去手写 DllGetClassObject、DllRegisterServer 等逻辑,但它只是封装,不是替代。很多问题依然源于对 COM 基础规则的忽视。
- ATL 类必须继承自
CComObjectRootEx<t></t>并用DECLARE_REGISTRY_RESOURCEID等宏,否则注册表项缺失 -
COM_INTERFACE_ENTRY(IMyInterface)必须和 IDL 中定义的接口 UUID 完全一致,否则QueryInterface返回E_NOINTERFACE - ATL 默认用
CComSingleThreadModel,如果你的组件要支持多线程调用,得手动改成CComMultiThreadModel并确保所有成员变量线程安全 - 调试时看
CoCreateInstance返回值:-2147221164 是CLASS_NOT_REGISTERED,-2147221008 是NO_INTERFACE,这些错误码比弹窗更有指向性
COM 的麻烦不在语法,而在每个环节都依赖精确的二进制契约:UUID 匹配、内存生命周期归属、线程上下文一致性、注册表路径拼写。漏掉任意一环,表现都是“接口调用失败”,但原因可能隔了三层抽象。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











