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();
常记溪亭日暮,沉醉不知归路。兴尽晚回舟,误入藕花深处。争渡,争渡,惊起一滩鸥鹭。