如何在C++中轻松实现仅IDispatch接口?
如何在C++中轻松实现仅IDispatch接口?
嘿,我懂你现在的需求——你在C#里定义了一个仅IDispatch的接口,现在想在C++里轻松实现它对吧?别发愁,我给你分享两种靠谱的方法,保证能搞定。
首先先确认下你的C#接口定义:
我在C#项目中定义了如下仅IDispatch接口:
[ComVisible(true)] [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)] [Guid("3532C4E8-D320-487C-8BD4-F448B94B9E83")] public interface ICppRuntimeApi2 { [DispId(1)] bool TestCallback(string pasteType); }
你已经导入了生成的类型库,那接下来就看你想用哪种方式实现了:
方法一:用ATL实现(最省心,推荐!)
ATL是微软专门为COM开发设计的模板库,能帮你省掉一大堆手动写IDispatch底层逻辑的麻烦,步骤超简单:
- 要是你还没ATL项目,就新建一个ATL项目;要是已有项目,直接加个ATL简单对象就行
- 让你的实现类直接继承自
ICppRuntimeApi2——这个接口是类型库导入后自动生成的,记得先包含对应的头文件 - 然后只需要实现接口里的
TestCallback方法,剩下的IDispatch相关逻辑ATL全帮你处理了
给你个现成的代码示例:
// 先包含类型库生成的头文件,名字一般是接口名加_h.h #include "ICppRuntimeApi2_h.h" // 定义实现类,用ATL的模板来管理COM对象的生命周期 class ATL_NO_VTABLE CCppRuntimeImpl : public CComObjectRootEx<CComSingleThreadModel>, public CComCoClass<CCppRuntimeImpl, &CLSID_CCppRuntimeImpl>, public ICppRuntimeApi2 // 直接继承我们要实现的IDispatch接口 { public: // 注册表相关的宏,ATL需要这个来注册组件 DECLARE_REGISTRY_RESOURCEID(IDR_CPPRUNTIMEIMPL) // COM接口映射,告诉COM我们支持哪些接口 BEGIN_COM_MAP(CCppRuntimeImpl) COM_INTERFACE_ENTRY(ICppRuntimeApi2) COM_INTERFACE_ENTRY(IDispatch) // 必须加上这个,不然找不到IDispatch入口 END_COM_MAP() // 终于到了我们要写的业务逻辑——实现TestCallback方法 STDMETHODIMP TestCallback(BSTR pasteType, VARIANT_BOOL* pRetVal) { // 把BSTR转成CString,处理起来更顺手 CString strPaste(pasteType); // 这里写你的实际业务逻辑,比如判断粘贴类型 if (strPaste == L"text") { *pRetVal = VARIANT_TRUE; } else { *pRetVal = VARIANT_FALSE; } // COM方法标准返回值,成功就返回S_OK return S_OK; } }; // 这个宏用来注册类和CLSID的关联 OBJECT_ENTRY_AUTO(__uuidof(CppRuntimeImpl), CCppRuntimeImpl)
方法二:手动实现IDispatch(不想用ATL的话)
要是你不想依赖ATL,那就得自己手写IDispatch的四个核心方法:GetIDsOfNames、GetTypeInfo、GetTypeInfoCount和Invoke。这种方式麻烦点,但也能搞定,核心就是在Invoke里处理我们定义的DispId。
给你关键的Invoke方法实现:
STDMETHODIMP Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS* pDispParams, VARIANT* pVarResult, EXCEPINFO* pExcepInfo, UINT* puArgErr) { // 先判断是不是我们要处理的DispId,就是C#里定义的DispId(1) if (dispIdMember != 1) return DISP_E_MEMBERNOTFOUND; // 检查是不是方法调用,我们这个接口里只有方法 if (wFlags & DISPATCH_METHOD) { // 注意:DISPPARAMS里的参数是逆序的,第一个参数在rgvarg[0]里 BSTR pasteType = pDispParams->rgvarg[0].bstrVal; bool result = false; // 这里写你的业务逻辑,比如判断粘贴类型 if (wcscmp(pasteType, L"text") == 0) { result = true; } // 设置返回值,要确保pVarResult不为空 if (pVarResult != nullptr) { V_VT(pVarResult) = VT_BOOL; V_BOOL(pVarResult) = result ? VARIANT_TRUE : VARIANT_FALSE; } return S_OK; } // 要是到这一步,说明不是我们能处理的调用 return DISP_E_MEMBERNOTFOUND; }
一些要注意的小细节
- 不管用哪种方式,都要确保类型库导入正确,比如用
#import "你的C#程序集.tlb" no_namespace named_guids指令,会自动生成头文件和类型信息 - C#里的
string对应C++的BSTR,别自己随便释放别人传入的BSTR,除非是你自己创建的 - 调试的时候要是调用失败,先检查DispId是不是匹配,参数类型有没有搞错,引用计数是不是正常
内容来源于stack exchange




