如何用纯C正确实现多个COM接口?附官方示例分析
纯C环境下实现多COM接口的方案
一、C++多继承COM类对应的C内存布局
你给出的C++类CDialogEventHandler通过多继承实现了IFileDialogEvents和IFileDialogControlEvents,其内存布局为:
[IFileDialogEvents子对象(含vtable指针)][IFileDialogControlEvents子对象(含vtable指针)][类自定义成员]
在纯C中需要完全模拟该布局,对应的结构体定义如下:
#include <shobjidl.h> typedef struct CDialogEventHandler { // 对应C++第一个基类:IFileDialogEvents接口实例 IFileDialogEvents fileDialogEvents; // 对应C++第二个基类:IFileDialogControlEvents接口实例 IFileDialogControlEvents fileDialogControlEvents; // 自定义成员:引用计数 ULONG refCount; // 其他业务所需自定义数据... } CDialogEventHandler;
每个COM接口结构体(如IFileDialogEvents)的第一个成员都是指向其vtable的指针,上述C结构体完全匹配C++多继承的内存布局。
二、双接口的vTable实现
每个COM接口需要独立的vtable,包含该接口所有方法的函数指针,以下是具体实现:
1. 实现接口核心方法
先编写两个接口的所有方法(包括IUnknown的QueryInterface/AddRef/Release,以及各自的事件方法):
// 通用QueryInterface逻辑,处理所有IID请求 static HRESULT WINAPI DialogEventHandler_QueryInterface(IUnknown* pUnk, REFIID riid, void** ppv) { CDialogEventHandler* pThis = CONTAINING_RECORD(pUnk, CDialogEventHandler, fileDialogEvents); *ppv = NULL; // 处理IUnknown请求:返回第一个接口的IUnknown,满足COM同一性要求 if (IsEqualIID(riid, &IID_IUnknown)) { *ppv = &pThis->fileDialogEvents; } // 处理IFileDialogEvents请求 else if (IsEqualIID(riid, &IID_IFileDialogEvents)) { *ppv = &pThis->fileDialogEvents; } // 处理IFileDialogControlEvents请求 else if (IsEqualIID(riid, &IID_IFileDialogControlEvents)) { *ppv = &pThis->fileDialogControlEvents; } else { return E_NOINTERFACE; } // 成功返回接口时增加引用计数 ((IUnknown*)*ppv)->AddRef(); return S_OK; } // IFileDialogEvents的QueryInterface包装(适配接口this指针) static HRESULT WINAPI FDEvents_QueryInterface(IFileDialogEvents* pThis, REFIID riid, void** ppv) { return DialogEventHandler_QueryInterface((IUnknown*)pThis, riid, ppv); } // IFileDialogControlEvents的QueryInterface包装(调整指针到实例起始地址) static HRESULT WINAPI FDControlEvents_QueryInterface(IFileDialogControlEvents* pThis, REFIID riid, void** ppv) { CDialogEventHandler* pInst = CONTAINING_RECORD(pThis, CDialogEventHandler, fileDialogControlEvents); return DialogEventHandler_QueryInterface((IUnknown*)&pInst->fileDialogEvents, riid, ppv); } // 通用AddRef:线程安全的引用计数增加 static ULONG WINAPI DialogEventHandler_AddRef(IUnknown* pUnk) { CDialogEventHandler* pThis = CONTAINING_RECORD(pUnk, CDialogEventHandler, fileDialogEvents); return InterlockedIncrement(&pThis->refCount); } // IFileDialogControlEvents的AddRef包装 static ULONG WINAPI FDControlEvents_AddRef(IFileDialogControlEvents* pThis) { CDialogEventHandler* pInst = CONTAINING_RECORD(pThis, CDialogEventHandler, fileDialogControlEvents); return DialogEventHandler_AddRef((IUnknown*)&pInst->fileDialogEvents); } // 通用Release:线程安全的引用计数减少,计数为0时释放实例 static ULONG WINAPI DialogEventHandler_Release(IUnknown* pUnk) { CDialogEventHandler* pThis = CONTAINING_RECORD(pUnk, CDialogEventHandler, fileDialogEvents); ULONG newCount = InterlockedDecrement(&pThis->refCount); if (newCount == 0) { HeapFree(GetProcessHeap(), 0, pThis); } return newCount; } // IFileDialogControlEvents的Release包装 static ULONG WINAPI FDControlEvents_Release(IFileDialogControlEvents* pThis) { CDialogEventHandler* pInst = CONTAINING_RECORD(pThis, CDialogEventHandler, fileDialogControlEvents); return DialogEventHandler_Release((IUnknown*)&pInst->fileDialogEvents); } // IFileDialogEvents的OnFileOk方法示例 static HRESULT WINAPI FDEvents_OnFileOk(IFileDialogEvents* pThis, IFileDialog* pfd) { CDialogEventHandler* pInst = CONTAINING_RECORD(pThis, CDialogEventHandler, fileDialogEvents); // 自定义业务逻辑 return S_OK; } // IFileDialogControlEvents的OnItemSelected方法示例 static HRESULT WINAPI FDControlEvents_OnItemSelected(IFileDialogControlEvents* pThis, IFileDialogCustomize* pfdc, DWORD dwIDCtl, DWORD dwIDItem) { CDialogEventHandler* pInst = CONTAINING_RECORD(pThis, CDialogEventHandler, fileDialogControlEvents); // 自定义业务逻辑 return S_OK; } // 根据shobjidl.h定义,补充两个接口的剩余方法实现
2. 组装vTable结构体
将上述方法指针组装为两个接口的vtable:
// IFileDialogEvents的vTable static const IFileDialogEventsVtbl g_FDEventsVtbl = { FDEvents_QueryInterface, DialogEventHandler_AddRef, DialogEventHandler_Release, FDEvents_OnFileOk, // 补充IFileDialogEvents的其他方法指针 }; // IFileDialogControlEvents的vTable static const IFileDialogControlEventsVtbl g_FDControlEventsVtbl = { FDControlEvents_QueryInterface, FDControlEvents_AddRef, FDControlEvents_Release, FDControlEvents_OnItemSelected, // 补充IFileDialogControlEvents的其他方法指针 };
三、实例创建与初始化
编写实例创建函数,初始化接口vtable指针和引用计数:
CDialogEventHandler* CreateDialogEventHandler() { CDialogEventHandler* pInst = (CDialogEventHandler*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(CDialogEventHandler)); if (!pInst) { return NULL; } // 绑定两个接口的vTable pInst->fileDialogEvents.lpVtbl = &g_FDEventsVtbl; pInst->fileDialogControlEvents.lpVtbl = &g_FDControlEventsVtbl; // 初始引用计数设为1 pInst->refCount = 1; return pInst; }
关键注意事项
- 指针调整:通过
CONTAINING_RECORD宏将接口指针转换为实例指针,这是C中处理多接口布局的核心,确保无论通过哪个接口调用方法,都能正确访问实例的自定义成员。 - IUnknown同一性:COM要求对象所有接口返回的
IUnknown必须是同一个,因此统一返回第一个接口的IUnknown实现。 - 线程安全:引用计数操作必须使用
InterlockedIncrement和InterlockedDecrement保证多线程环境下的正确性。
内容的提问来源于stack exchange,提问作者Ivan Venkov
相关产品推荐
相关产品推荐

