• C++代码调用vbs或js——IDispatch接口


    参考:https://blog.csdn.net/wenzhou1219/article/details/52039731

    IDispatch接口,称为自动化接口、调度接口、分派接口。

    COM提供接口IDispatch,中文一般译作自动化接口,其实感觉直译为分派接口更好理解。自动化,顾名思义一开始诞生就是为了实现自动化的功能,支持各种脚本语言来调用接口工作。前面说了C++的接口都是指针,基于虚表的,而脚本语言没有指针也没法向COM传递参数调起指定函数。那么如何才能让脚本语言如Js来调用COM完成指定功能呢,他们间的参数如何传递,如何调起对应的程序,这都是COM 自动化的功能。


    1. IDispatch接口原理

    在介绍IDispatch接口前,先思考一个问题,脚本语言如何才能调起C++的接口工作呢?

    其实根本上和普通接口一致,我们提供了IDispatch接口,JS语言本身不支持指针,但是JS引擎是可以自由实现的——在JS中调用具体的方法或属性,对应的名字传给JS引擎,JS引擎使用名字作为参数,调用IDispatch Invoke函数即可完成对应的调用操作。IDispatch接口相当于在脚本引擎和自实现的COM组件间规定了一个标准的调用接口。

    除此之外,使用IDispatch接口的最大好处在于最大化程度的解耦实现和调用,程序的灵活性大大增强。比如之前我们需要根据输入调用COM组件中指定函数时,必须各种分支判断,获得对应的接口,再调用接口中对应函数才行;而使用IDispatch接口,只需要传入的方法名传给Invoke函数即可,相当于实现了脚本语言中的eval函数,大大增强了编译类语言的灵活性。

    调用IDispatch接口时,既可以直接调用接口成员函数,也可通过Invoke传入方法名称和参数,所以这个接口也称作双接口,对应IDL关键字为dual。

    IDispatch定义如下:

    IDispatch : public IUnknown
    {
    public:
        virtual HRESULT STDMETHODCALLTYPE GetTypeInfoCount( 
            /* [out] */ UINT *pctinfo) = 0;
        
        virtual HRESULT STDMETHODCALLTYPE GetTypeInfo( 
            /* [in] */ UINT iTInfo,
            /* [in] */ LCID lcid,
            /* [out] */ ITypeInfo **ppTInfo) = 0;
        
        virtual HRESULT STDMETHODCALLTYPE GetIDsOfNames( 
            /* [in] */ REFIID riid,
            /* [size_is][in] */ LPOLESTR *rgszNames,
            /* [range][in] */ UINT cNames,
            /* [in] */ LCID lcid,
            /* [size_is][out] */ DISPID *rgDispId) = 0;
        
        virtual HRESULT STDMETHODCALLTYPE Invoke( 
            /* [in] */ DISPID dispIdMember,
            /* [in] */ REFIID riid,
            /* [in] */ LCID lcid,
            /* [in] */ WORD wFlags,
            /* [out][in] */ DISPPARAMS *pDispParams,
            /* [out] */ VARIANT *pVarResult,
            /* [out] */ EXCEPINFO *pExcepInfo,
            /* [out] */ UINT *puArgErr) = 0;
        
    };
    View Code

    其中:

    1.GetTypeInfoCount中指明pctinfo=1为有返回类型库,0为没有
    2.GetTypeInfo 获得当前的类型库中指定接口的类型信息接口指针
    3.GetIDsofNames 获得指定名称的对应分派ID
    4.Invoke传入指定的函数名称或分派ID,调用指定功能


    2. IDispatch接口实现

    IDispatch接口实现很灵活:


    1.不使用类型库

    GetTypeInfoCount和GetTypeInfo不需要实现,GetTypeInfoCount中指明pctinfo=1;GetIDsofNames和Invoke一般要求实现,必须要时连GetIDsofNames也可不用实现。不使用类型库的时候,实现GetIDsofNames和Invoke主要是靠查表。

    GetIDsofNames传入Name值即可返回对应的ID

    Invoke传入指定的ID 查表调用对应的函数实现逻辑

    MFC中IDisaptch的实现,主要依靠自己查表的方法,后文将演示。


    2.使用类型库

    四个函数均需要实现。

    使用类型库,实际上就是将表内容用类型库表示且提供更加丰富的信息。由类型库查询得到信息再调用是相当复杂的过程,所以COM库提供了标准的实现。
    我们只需要使用LoadRegTypeLib和LoadTypeLib加载对应的类型库得到ITypeLib接口,再查询得到对应的接口的ITypeInfo接口,借助ITypeInfo接口我们即可完成对应IDispatch函数的实现。
    ATL中IDisaptch的实现,主要依靠类型库的方法,后文将演示

    前文叙述了IDispatch接口的原理,本文先讲MFC的实现细节,下文讲ATL的实现细节。


    1.通用方法

    MFC不使用类型库,这里先讲不用类型库实现IDispatch,此时一般实现GetIDsOfNames和Invoke函数。这里使用MFC实现,实际上在ATL中也可以使用。

    按照之前讲的通用接口的编写方法,定义嵌入类和工厂类声明如下,嵌入类实现了IDispatch接口。

        //接口映射表
        BEGIN_INTERFACE_PART(Cat, IDispatch)
            INIT_INTERFACE_PART(CAnimalObject, Cat)
     
            virtual HRESULT STDMETHODCALLTYPE GetTypeInfoCount( 
            /* [out] */ __RPC__out UINT *pctinfo);
     
            virtual HRESULT STDMETHODCALLTYPE GetTypeInfo( 
                /* [in] */ UINT iTInfo,
                /* [in] */ LCID lcid,
                /* [out] */ __RPC__deref_out_opt ITypeInfo **ppTInfo);
     
            virtual HRESULT STDMETHODCALLTYPE GetIDsOfNames( 
                /* [in] */ __RPC__in REFIID riid,
                /* [size_is][in] */ __RPC__in_ecount_full(cNames) LPOLESTR *rgszNames,
                /* [range][in] */ UINT cNames,
                /* [in] */ LCID lcid,
                /* [size_is][out] */ __RPC__out_ecount_full(cNames) DISPID *rgDispId);
     
            virtual /* [local] */ HRESULT STDMETHODCALLTYPE Invoke( 
                /* [in] */ DISPID dispIdMember,
                /* [in] */ REFIID riid,
                /* [in] */ LCID lcid,
                /* [in] */ WORD wFlags,
                /* [out][in] */ DISPPARAMS *pDispParams,
                /* [out] */ VARIANT *pVarResult,
                /* [out] */ EXCEPINFO *pExcepInfo,
                /* [out] */ UINT *puArgErr);
        END_INTERFACE_PART_STATIC(Cat)
     
        DECLARE_INTERFACE_MAP()
    View Code

    建立接口映射表如下

    //接口映射表
    BEGIN_INTERFACE_MAP(CAnimalObject, CCmdTarget)
        INTERFACE_PART(CAnimalObject, IID_IDispatch, Cat)
    END_INTERFACE_MAP()

    为了不依赖类型库实现IDispatch接口,建立名字和DispID如下

    //建立Dispatch表
    map<CString, UINT> g_DispMap;
     
    CAnimalObject::CAnimalObject(void)
    {
        g_DispMap[L"SayHello1"] = DISP_ID_SAYHELLO1;
        g_DispMap[L"SayHello2"] = DISP_ID_SAYHELLO2;
    }

    由于没有类型库,则GetTypeInfoCount和GetTypeInfo不用实现,具体如下:

    HRESULT STDMETHODCALLTYPE CAnimalObject::XCat::GetTypeInfoCount( 
        /* [out] */ __RPC__out UINT *pctinfo)
    {
        *pctinfo = 0;    //没有类型库
        return S_OK;
    }
     
    HRESULT STDMETHODCALLTYPE CAnimalObject::XCat::GetTypeInfo( 
        /* [in] */ UINT iTInfo,
        /* [in] */ LCID lcid,
        /* [out] */ __RPC__deref_out_opt ITypeInfo **ppTInfo)
    {
        *ppTInfo = NULL;
        return E_NOTIMPL;
    }

    GetIDsOfNames的实现只需要查表即可,如下:

    HRESULT STDMETHODCALLTYPE CAnimalObject::XCat::GetIDsOfNames( 
        /* [in] */ __RPC__in REFIID riid,
        /* [size_is][in] */ __RPC__in_ecount_full(cNames) LPOLESTR *rgszNames,
        /* [range][in] */ UINT cNames,
        /* [in] */ LCID lcid,
        /* [size_is][out] */ __RPC__out_ecount_full(cNames) DISPID *rgDispId)
    {
        
        for (UINT i=0; i<cNames; i++)
        {
            map<CString, UINT>::iterator iter = g_DispMap.find(rgszNames[i]);
            if ( g_DispMap.end() != iter )
            {
                rgDispId[i] = iter->second;
            }
            else
            {
                rgDispId[i] = DISPID_UNKNOWN;
            }
        }
     
        return S_OK;
    }

    这里可能存在一次性传入多个Name的情况,此时cNames标示传入的name个数,rgszNames和rgDispID均为数组。

    Invoke根据传入的分发ID,调用不同的逻辑:

    HRESULT STDMETHODCALLTYPE CAnimalObject::XCat::Invoke( 
        /* [in] */ DISPID dispIdMember,
        /* [in] */ REFIID riid,
        /* [in] */ LCID lcid,
        /* [in] */ WORD wFlags,
        /* [out][in] */ DISPPARAMS *pDispParams,
        /* [out] */ VARIANT *pVarResult,
        /* [out] */ EXCEPINFO *pExcepInfo,
        /* [out] */ UINT *puArgErr)
    {
        if (0==dispIdMember ||
            (dispIdMember!=DISP_ID_SAYHELLO1 && dispIdMember!=DISP_ID_SAYHELLO2) ||
            0==(DISPATCH_METHOD&wFlags))
        {
            return E_NOTIMPL;
        }
     
        if (pVarResult)
        {
            CComVariant var(true);
            *pVarResult = var;
        }
     
        USES_CONVERSION;
     
        switch (dispIdMember)
        {
        case DISP_ID_SAYHELLO1:
            if (pDispParams &&                            //参数数组有效
                pDispParams->cArgs==1 &&                //参数个数为1
                pDispParams->rgvarg[0].vt==VT_BSTR &&    //参数类型满足
                pDispParams->rgvarg[0].bstrVal)            //参数值有效
            {
                 CString strVal(OLE2T(pDispParams->rgvarg[0].bstrVal));
                 wcout << L"猫猫说我的名字叫:" << strVal.GetBuffer(0) << endl;
            }
            break;
     
        case DISP_ID_SAYHELLO2:
            if (pDispParams &&                            //参数数组有效
                pDispParams->cArgs==1 &&                //参数个数为1
                pDispParams->rgvarg[0].vt==VT_I4 &&        //参数类型满足
                pDispParams->rgvarg[0].intVal)            //参数值有效
            {
                wcout << L"猫猫说我的年龄是:" << pDispParams->rgvarg[0].intVal << endl;
            }
            break;
        }
     
        return S_OK;
    }
    View Code

    2.标准MFC的实现方法

    MFC中我们已经见到了各种查表,如消息映射表MESSAGE_MAP,接口映射表INTERFACE|_MAP等。同样为了支持IDISPATC接口,MFC做了一套分发映射表DISPATCH_MAP,和之前的使用方法一样。

    另外,MFC中的CCmdTarget默认实现了IDispatch接口,只要我们在子类构造函数调用EnableAutomation开启自动化支持即可。此时不用再单独添加接口映射表,MFC已默认将IDispatch接口加到接口查询表中。


    MFC这套机制非常简单,如下:

    声明分发映射表:

        //分派映射表
        DECLARE_DISPATCH_MAP()

    实现分发映射表:

    //分配映射表
    BEGIN_DISPATCH_MAP(CAnimalObject, CCmdTarget)
        DISP_FUNCTION_ID(CAnimalObject, "SayHello1", DISP_ID_SAYHELLO1, SayHello1, VT_I4, VTS_BSTR)// "SayHello1"不要加L前缀
        DISP_FUNCTION_ID(CAnimalObject, "SayHello2", DISP_ID_SAYHELLO2, SayHello2, VT_I4, VTS_I4)
    END_DISPATCH_MAP()

    DISP_FUNCTION_ID宏参数分别为当前类名,函数名,分发ID,函数指针,函数返回值,函数参数


    对应的调用函数逻辑实现如下:

    BOOL CAnimalObject::SayHello1( BSTR szWord )
    {
        USES_CONVERSION;
        CString strWord(OLE2CW(szWord));
     
        wcout << L"猫猫2的名字:" << strWord.GetBuffer(0) << endl;
        return TRUE;
    }
     
    BOOL CAnimalObject::SayHello2( int nAge )
    {
        wcout << L"猫猫2的年龄:" << nAge << endl;
        return TRUE;
    }

    3.调用IDispatch接口

    默认的IDispatch接口调用Invoke函数时参数太繁琐,MFC提供COleDispatchDriver类来辅助操作,如下:

            //初始化COM库
            if (CoInitialize(NULL) != S_OK)
            {
                wcout << L"Fail to Initialize COM" << endl;
                return -1;
            }
     
            //自动化调用
            COleDispatchDriver d;
            if (d.CreateDispatch(CLSID_AnimalObject))
            {
                BYTE params1[] = {VTS_BSTR};
                BYTE params2[] = {VTS_I4};
                BOOL bRet;
     
                d.InvokeHelper(DISP_ID_SAYHELLO1, DISPATCH_METHOD, VT_I4, (LPVOID)&bRet, params1, L"maomao");
                d.InvokeHelper(DISP_ID_SAYHELLO2, DISPATCH_METHOD, VT_I4, (LPVOID)&bRet, params2, 20);
     
                d.ReleaseDispatch();
            }    
     
            ::CoUninitialize();

    InvokeHelper参数依次为分发ID,方法Flag,返回类型,返回值,函数参数类型数组,函数参数。


    ————————————————
    原文链接:https://blog.csdn.net/wenzhou1219/article/details/52074099
    原文链接:https://blog.csdn.net/wenzhou1219/article/details/52039731


     
    在VC++中执行脚本语句,比如VBScript语句。

    系统提供了一个控件:C:WINDOWSsystem32msscript.ocx,它提供了一个叫做IScriptControl的接口,通过它,我们就可以执行脚本语句了。

    1.用VC++自动创建包装类

    用VC++建立一个支持MFC的工程,添加一个类,选择“类型库中的MFC类”,再选择msscript.ocx文件,并将IScriptControl添加到右栏,如下图:
    vs2013 : 新建类-》
    点击完成后即可生成CScriptControl包装类。


    2.使用生成的类

        //初始化COM
        CoInitialize(NULL);

        //创建MSScriptControl.ScriptControl实例
        //这个名称(ProgId)可以通过VC++目录下的小工具oleview得到。
        CScriptControl js;
        if (js.CreateDispatch("MSScriptControl.ScriptControl"))
        {
            //设置当前使用的脚本语言
            js.put_Language("JScript");
            //执行语句,执行完成后var中即包含了表达式的结果
            VARIANT var=js.Eval("1.234+5.31");
            //释放接口
            js.ReleaseDispatch();
        }
          
        //关闭COM
        CoUninitialize();

    常记溪亭日暮,沉醉不知归路。兴尽晚回舟,误入藕花深处。争渡,争渡,惊起一滩鸥鹭。

    昨夜雨疏风骤,浓睡不消残酒。试问卷帘人,却道海棠依旧。知否?知否?应是绿肥红瘦。
  • 相关阅读:
    Echarts 源码解读 一:zrender源码分析1var zr = zrender.init(document.getElementById(‘main‘));
    Vue3 script setup 语法糖详解
    TCP的状态 (SYN, FIN, ACK, PSH, RST, URG)
    vuerouter路由懒加载
    ECharts 源码解读 二
    前端必备10种设计模式
    VUE路由懒加载的3种方式
    RewriterConfig 配置
    JQ弹出层插件(tipsWindow 2.8)
    一个jQuery弹出层(tipsWindown)
  • 原文地址:https://www.cnblogs.com/htj10/p/11729274.html
Copyright © 2020-2023  润新知