You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何在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的四个核心方法:GetIDsOfNamesGetTypeInfoGetTypeInfoCountInvoke。这种方式麻烦点,但也能搞定,核心就是在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

火山引擎 最新活动