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:\WINDOWS\system32\msscript.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();

 

posted @ 2019-10-23 21:58  htj10  阅读(1368)  评论(0编辑  收藏  举报
TOP