参考: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; };
其中:
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()
建立接口映射表如下
//接口映射表 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; }
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
系统提供了一个控件:C:WINDOWSsystem32msscript.ocx,它提供了一个叫做IScriptControl的接口,通过它,我们就可以执行脚本语句了。
1.用VC++自动创建包装类
用VC++建立一个支持MFC的工程,添加一个类,选择“类型库中的MFC类”,再选择msscript.ocx文件,并将IScriptControl添加到右栏,如下图:
点击完成后即可生成CScriptControl包装类。
2.使用生成的类