IDispatch接口是COM自动化的核心。其实,IDispatch这个接口本身也很简单,只有4个方法:

     IDispatch : public IUnknown
{
public:
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] */ __RPC__in_range(,) UINT cNames,
/* [in] */ LCID lcid,
/* [size_is][out] */ __RPC__out_ecount_full(cNames) DISPID *rgDispId) = ; virtual /* [local] */ HRESULT STDMETHODCALLTYPE Invoke(
/* [annotation][in] */
_In_ DISPID dispIdMember,
/* [annotation][in] */
_In_ REFIID riid,
/* [annotation][in] */
_In_ LCID lcid,
/* [annotation][in] */
_In_ WORD wFlags,
/* [annotation][out][in] */
_In_ DISPPARAMS *pDispParams,
/* [annotation][out] */
_Out_opt_ VARIANT *pVarResult,
/* [annotation][out] */
_Out_opt_ EXCEPINFO *pExcepInfo,
/* [annotation][out] */
_Out_opt_ UINT *puArgErr) = ; };

GetTypeInfoCount和GetTypeInfo以后再说。

先来看看比较熟悉的GetIDsOfNames和Invoke。

GetIDsOfNames

这个函数的主要功能就是:把COM接口的方法名字和参数(可选)映射成一组DISPID。DISPID就是一个LONG型:

 typedef LONG DISPID;

GetIDsOfNames()可以获取方法和属性。先来看一个例子,COM接口IMyCar

 [
object,
uuid(21B794E2---8FC2-CDAB2A486600),
dual,
nonextensible,
pointer_default(unique)
]
interface IMyCar : IDispatch{
[id()] HRESULT Run();
[id()] HRESULT AddGas([in] LONG add, [out] LONG* total);
[propget, id()] HRESULT Gas([out, retval] LONG* pVal);
};

这个接口里面有一个方法AddGas,它有两个参数,一个输入,一个输出。它的实现基本如下:

 STDMETHODIMP CMyCar::AddGas(LONG add, LONG* total)
{
// TODO: Add your implementation code here
m_Gas += add;
*total = m_Gas; return S_OK;
}

试试如何获取AddGas函数的id和参数index,看下面的代码。数组里面的第一个元素是方法名字,第二个/第三个是参数名字。

     CComPtr<IMyCar> spCar;
spCar.CoCreateInstance(CLSID_MyCar);
     DISPID PropertyID[] = {};
BSTR PropName[]; PropName[] = SysAllocString(L"AddGas");
PropName[] = SysAllocString(L"add");
PropName[] = SysAllocString(L"total");
HRESULT hr = spCar->GetIDsOfNames(IID_NULL, PropName, , LOCALE_SYSTEM_DEFAULT, PropertyID); SysFreeString(PropName[]);
SysFreeString(PropName[]);
SysFreeString(PropName[]);

运行一下,可以得到如下结果:

PropertyID数组里面可以得到3个值:2, 0, 1.

2代表的是AddGas的id,跟idl文件里面的一样。

0表示"add"是第一个参数,1表示"total"是第二个参数。

Invoke

Invoke是IDispatch里面非常重要的一样函数,方法调用就靠这个函数了。函数原型:

 HRESULT Invoke(
[in] DISPID dispIdMember,
[in] REFIID riid,
[in] LCID lcid,
[in] WORD wFlags,
[in, out] DISPPARAMS *pDispParams,
[out] VARIANT *pVarResult,
[out] EXCEPINFO *pExcepInfo,
[out] UINT *puArgErr
);

每一个参数的说明,看下面,从MSDN截来的。

先来看一个调用例子:

     CComVariant avarParams[];
avarParams[].vt = VT_I4;
avarParams[] = ; LONG vTotal = ;
avarParams[].vt = VT_I4 | VT_BYREF;
avarParams[] = &vTotal; DISPPARAMS params = { avarParams,
NULL, // Dispatch identifiers of named arguments.
, // Number of arguments.
}; // Number of named arguments. hr = spCar->Invoke(PropertyID[], IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &params, NULL, NULL, NULL);

avarParams是一个具有2个元素的数组,它表示要调用的方法的参数,注意这里的参数是逆序的。比如avarParam[1]存放的是第一个参数,是一个输入参数;avarParams[0]存放的是第二个参数,是一个输出参数。

运行一下:

spCar里面的m_Gas初始值是0,我们调用的时候给它加了4,那么输出就应该是4.看上面的运行结果,vTotal确实是4.

这样我们就通过Invoke成功调用了COM组件的方法(而不是通过spCar->AddGas)。注意Invoke里面的第一个参数是一个DISPID,这个是从GetIDsOfNames来的。

使用Invoke也可以调用COM对象的属性。

比如上面的idl里面的Gas。

调用属性跟方法差不多,如果我们想读取一个属性,那么可以这么调:

     DISPID PropertyID2[] = {  };
BSTR PropName2[]; PropName2[] = SysAllocString(L"Gas"); hr = spCar->GetIDsOfNames(IID_NULL, PropName2, , LOCALE_SYSTEM_DEFAULT, PropertyID2); SysFreeString(PropName2[]); DISPPARAMS params2 = { NULL,
NULL,
, }; CComVariant Result;
hr = spCar->Invoke(PropertyID2[], IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYGET, &params2, &Result, NULL, NULL);

运行可以得到结果:

注意,Invoke的第五个参数啥都没,属性值是从第六个参数返回出来的。如果是方法调用的话,那么COM方法参数需要从第五个参数传进入,第六个参数是函数调用的返回值HRESULT.

我没有试过属性的put,不知道是从哪里传入,当有需要的时候查一下MSDN就行了。

以上就是GetIDsOfNames和Invoke的简要说明以及例子。之后再来分析更多的细节。

完整客户端代码:

 // ConsoleApplication4.cpp : Defines the entry point for the console application.
// #include "stdafx.h" #include <thread>
#include <atlbase.h>
#include <atlcom.h>
#include <algorithm>
#include <vector>
#include <memory> #include "../MyCom/MyCom_i.h"
#include "../MyCom/MyCom_i.c" int _tmain(int argc, _TCHAR* argv[])
{
CoInitializeEx(NULL, COINIT_MULTITHREADED); CComPtr<IMyCar> spCar;
spCar.CoCreateInstance(CLSID_MyCar); // use IDispatch
DISPID PropertyID[] = {};
BSTR PropName[]; PropName[] = SysAllocString(L"AddGas");
PropName[] = SysAllocString(L"add");
PropName[] = SysAllocString(L"total");
HRESULT hr = spCar->GetIDsOfNames(IID_NULL, PropName, , LOCALE_SYSTEM_DEFAULT, PropertyID); SysFreeString(PropName[]);
SysFreeString(PropName[]);
SysFreeString(PropName[]); CComVariant avarParams[];
avarParams[].vt = VT_I4;
avarParams[] = ; LONG vTotal = ;
avarParams[].vt = VT_I4 | VT_BYREF;
avarParams[] = &vTotal; DISPPARAMS params = { avarParams,
NULL, // Dispatch identifiers of named arguments.
, // Number of arguments.
}; // Number of named arguments. hr = spCar->Invoke(PropertyID[], IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, ¶ms, NULL, NULL, NULL); DISPID PropertyID2[] = { };
BSTR PropName2[]; PropName2[] = SysAllocString(L"Gas"); hr = spCar->GetIDsOfNames(IID_NULL, PropName2, , LOCALE_SYSTEM_DEFAULT, PropertyID2); SysFreeString(PropName2[]); DISPPARAMS params2 = { NULL,
NULL,
, }; CComVariant Result;
hr = spCar->Invoke(PropertyID2[], IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYGET, ¶ms2, &Result, NULL, NULL); spCar.Release(); CoUninitialize(); return ;
}

相关的COM组件的代码:

 STDMETHODIMP CMyCar::AddGas(LONG add, LONG* total)
{
// TODO: Add your implementation code here
m_Gas += add;
*total = m_Gas; return S_OK;
} STDMETHODIMP CMyCar::get_Gas(LONG* pVal)
{
// TODO: Add your implementation code here
*pVal = m_Gas; return S_OK;
}

上述摘自:http://blog.csdn.net/zj510/article/details/39494873

引申(原创):

1、CHtmlDialog调用js ,无参数有返回值情况:

js函数如下:

 function GetFileChange()
{
return bFileChange;
}

MFC调用js函数如下:

 BOOL CHTMLDialogDlg::CallJSScript(const CString strFunc, _variant_t* pVarResult)
{
CComPtr<IDispatch> spScript;
if(m_spHtmlDoc==NULL)
return FALSE;
HRESULT hr = m_spHtmlDoc->get_Script(&spScript);
if(!SUCCEEDED(hr))
{
return FALSE;
}
CComBSTR bstrFunc(strFunc);
DISPID dispid = NULL;
HRESULT hrr = spScript->GetIDsOfNames(IID_NULL, &bstrFunc, , LOCALE_SYSTEM_DEFAULT, &dispid);
if(FAILED(hrr))
{
return FALSE;
} DISPPARAMS dispparams;
memset(&dispparams, , sizeof(dispparams)); EXCEPINFO excepInfo;
memset(&excepInfo, , sizeof(excepInfo));
_variant_t vaResult;
UINT nArgErr = (UINT)-;
hrr = spScript->Invoke(dispid, IID_NULL, , DISPATCH_METHOD, &dispparams, &vaResult, &excepInfo, &nArgErr);
delete[] dispparams.rgvarg; if(FAILED(hrr))
{
return FALSE;
}
if(pVarResult)
{
*pVarResult = vaResult;
}
return TRUE;
}

调用如下

 if(CallJSScript("GetFileChange", &retParam)){
if(retParam.vt == VT_BOOL){
bFileChange = retParam.boolVal;
}
}

2、CHtmlDialog调用js ,有参数有返回值情况:

 function OpenArgvFile(strArgvFile){
    .....
return bRet;
}

MFC调用js函数如下:

 BOOL CHtmlDialogDlg::CallJSScript(const CString strFunc, const CStringArray &paramArray, _variant_t* pVarResult)
{
CComPtr<IDispatch> spScript;
if(m_spHtmlDoc==NULL)
return FALSE;
HRESULT hr = m_spHtmlDoc->get_Script(&spScript);
if(!SUCCEEDED(hr))
{
return FALSE;
}
CComBSTR bstrFunc(strFunc);
DISPID dispid = NULL;
HRESULT hrr = spScript->GetIDsOfNames(IID_NULL, &bstrFunc, , LOCALE_SYSTEM_DEFAULT, &dispid);
if(FAILED(hrr))
{
return FALSE;
}
//
const int arraySize = paramArray.GetSize(); DISPPARAMS dispparams;
memset(&dispparams, , sizeof dispparams);
dispparams.cArgs = arraySize;
dispparams.rgvarg = new VARIANT[dispparams.cArgs]; for( int i = ; i < arraySize; i++)
{
CComBSTR bstr = paramArray.GetAt(arraySize - - i); // back reading
bstr.CopyTo(&dispparams.rgvarg[i].bstrVal);
dispparams.rgvarg[i].vt = VT_BSTR;
}
dispparams.cNamedArgs = ;
//
//DISPPARAMS dispparams;
//memset(&dispparams, 0, sizeof(dispparams)); EXCEPINFO excepInfo;
memset(&excepInfo, , sizeof(excepInfo));
_variant_t vaResult;
UINT nArgErr = (UINT)-;
hrr = spScript->Invoke(dispid, IID_NULL, , DISPATCH_METHOD, &dispparams, &vaResult, &excepInfo, &nArgErr);
delete[] dispparams.rgvarg; if(FAILED(hrr))
{
return FALSE;
}
if(pVarResult)
{
*pVarResult = vaResult;
}
return TRUE;
}

调用如下

 CStringArray paramArray;
_variant_t retParam;
paramArray.Add(m_strArgvFile);
if(!CallJSScript("OpenArgvFile", paramArray, &retParam)){...}

附:

CComPtr用法

COM接口指针很危险,因为使用过程中需要每一个使用者都要严格并且正确的AddRef和Release,一旦出现问题,就会造成对象不能被正常释放,或者对象被重复删除,造成程序崩溃。所以使用COM接口,必须小心翼翼才行。
但是,即使所有的代码中,都正确的AddRef和Release,也不一定能保证万无一失,例如:
void SomeApp( IHello * pHello )
{
IHello* pCopy = pHello;
pCopy->AddRef(); 
OtherApp();
pCopy->Hello();
pCopy->Release();
}
看起来好像无懈可击,但是假设OtherApp中抛出了异常,那么pCopy->Release不就被跳过去了吗?
幸好,所有的问题都从简单到复杂,再从复杂到简单的,因为我们有CComPtr!

CComPtr被称为智能指针,是ATL提供的一个模版类,能够从语法上自动完成AddRef和Release。(源代码在atlbase.h中)
CComPtr的用法很简单,以IHello*为例,将程序中所有接口指针类型(除了参数),都使用CComPtr<IHello> 代替即可。即程序中除了参数之外,再也不要使用IHello*,全部以CComPtr<IHello>代替。
CComPtr的用法和普通COM指针几乎一样,另外使用中有以下几点需要注意。
1. CComPtr已经保证了AddRef和Release的正确调用,所以不需要,也不能够再调用AddRef和Release。
2. 如果要释放一个智能指针,直接给它赋NULL值即可。(这一点要牢记曾因为没有设置为null而出错)
3. CComPtr本身析构的时候会释放COM指针。
4. 当对CComPtr使用&运算符(取指针地址)的时候,要确保CComPtr为NUL。(因为通过CComPtr的地址对CComPtr赋值时,不会自动调用AddRef,若不为NULL,则前面的指针不能释放,CComPtr会使用assert报警)
以刚才的程序为例:
void SomeApp( IHello * pHello )
{
CComPtr<IHello> pCopy = pHello;
OtherApp();
pCopy->Hello();
}
由于pCopy是一个局部的对象,所以即使OtherApp()抛出异常,pCopy也会被析构,指针能够被释放。
如果不想在程序临近发布前,还因为COM指针的引用计数造成崩溃的话,就牢记这一点吧:程序中除了参数之外,不要直接使用COM指针类型,一定要全部以CComPtr<IXXX>代替。

IDispatch接口 - GetIDsOfNames和Invoke(转)的更多相关文章

  1. IDispatch接口介绍

    1.         C程序调用时,调用者必须预先知道接口规范(如,参数类型.参数字节长度.参数顺序等).由于不同语言这些规范有所不同,COM未解决不同语言之间调用,提供了IDispatch接口. 2 ...

  2. 【转载】COM 组件设计与应用(十)——IDispatch 接口 for VC.NET

    原文:http://vckbase.com/index.php/wv/1225.html 一.前言 终于写到了第十回,我也一直期盼着写这回的内容耶,为啥呢?因为自动化(automation)是非常常用 ...

  3. 【转载】COM 组件设计与应用(九)——IDispatch 接口 for VC6.0

    原文: http://vckbase.com/index.php/wv/1224.html 一.前言 终于写到了第九回,我也一直期盼着写这回的内容耶,为啥呢?因为自动化(automation)是非常常 ...

  4. TComponent明明实现了IDispatch接口,但是却不加上声明,难道是因为FVCLComObject实体对象不存在?

    TComponent明明实现了IDispatch接口,可是它的声明却是: TComponent = class(TPersistent, IInterface, IInterfaceComponent ...

  5. wpf 错误 执行了 QueryInterface 调用,请求提供 COM 可见的托管类“BoilerMonitoringV1._0.MapControl”的默认 IDispatch 接口。

    在做wpf嵌入地图时,在自定义的WebBrowser 里面使用JavaScript调用外部方法的时报的错误 在原来的WinForm里 我们只要在窗体类设置的头部设置个 [System.Runtime. ...

  6. vs2019 Com组件初探-通过IDispatch接口调用Com

    vs2019 Com组件初探-简单的COM编写以及实现跨语言调用 上一篇实现了如何编写基于IDipatch接口的COM以及vbs如何调用编写的COM 本次主要是实现VBS的CreateObject函数 ...

  7. CEF3开发者系列之外篇——IE中JS与C++交互

    使用IE内核开发客户端产品,系统和前端页面之间的交互,通常给开发和维护带来很大的便利性.但操作系统和前端之间的交互却是比较复杂的.具体来说就是脚本语言和编译语言的交互.在IE内核中html和css虽然 ...

  8. 利用Delphi编写IE扩展

    就是如何使IE扩展组件可以响应事件.    在自己的程序中使用过WebBrowser控件的朋友都知道,WebBrowser控件定义了诸如BeforeNavigate.DownloadComplete ...

  9. COM组件 IDispatch 及双接口的调用

    转自:http://blog.csdn.net/cnhk1225/article/details/50555647 一.前言 前段时间,由于工作比较忙,没有能及时地写作.其间收到了很多网友的来信询问和 ...

随机推荐

  1. FastDFS总结

    前言 FastDFS主要解决互联网中小文件存储存储问题,例如图片,短视频,提供上传和下载功能,轻量级的设计,结构非常简单,主要包含三个角色客户端,Tracer服务,Storage服务.Tracer服务 ...

  2. codeforces 691F Couple Cover 暴力

    分析:开一个300w的数组,统计,然后nlogn统计每个值在在序对第一个出现有多少种情况 时间复杂度:O(nlogn) n在3e6数量级 #include<cstdio> #include ...

  3. codeforces 691D Swaps in Permutation DFS

    这个题刚开始我以为是每个交换只能用一次,然后一共m次操作 结果这个题的意思是操作数目不限,每个交换也可以无限次 所以可以交换的两个位置连边,只要两个位置连通,就可以呼唤 然后连通块内排序就好了 #in ...

  4. SDUT 3568 Rock Paper Scissors 状压统计

    就是改成把一个字符串改成三进制状压,然后分成前5位,后5位统计, 然后直接统计 f[i][j][k]代表,后5局状压为k的,前5局比和j状态比输了5局的有多少个人 复杂度是O(T*30000*25*m ...

  5. 学习Python编程的11个资源

    用 Python 写代码并不难,事实上,它一直以来都是被声称为最容易学习的编程语言.如果你正打算学习 web 开发,Python 是一个不错的选择,甚至你想学游戏开发也可 以从 Python 开始,因 ...

  6. Python 代码性能优化技巧

    选择了脚本语言就要忍受其速度,这句话在某种程度上说明了 python 作为脚本的一个不足之处,那就是执行效率和性能不够理想,特别是在 performance 较差的机器上,因此有必要进行一定的代码优化 ...

  7. python学习之self,cls,staticmethod,classmethod

    一.总体说明 python类里会出现这三个单词,self和cls都可以用别的单词代替,类的方法有三种, 一是通过def定义的 普通的一般的,需要至少传递一个参数,一般用self,这样的方法必须通过一个 ...

  8. Min Edit Distance

    Min Edit Distance ----两字符串之间的最小距离 PPT原稿参见Stanford:http://www.stanford.edu/class/cs124/lec/med.pdf Ti ...

  9. HDU-4720 Naive and Silly Muggles 圆的外心

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4720 先两两点之间枚举,如果不能找的最小的圆,那么求外心即可.. //STATUS:C++_AC_0M ...

  10. Spark SQL应用

    Spark Shell启动后,就可以用Spark SQL API执行数据分析查询. 在第一个示例中,我们将从文本文件中加载用户数据并从数据集中创建一个DataFrame对象.然后运行DataFrame ...