COM学习笔记8_IDispatch (调度接口) 自动化

 
分类: COM   2835人阅读  评论(0)  收藏  举报

一般的通讯方式:
客户 <==> COM(vbtl)接口 <==> COM组件

自动化通讯方式:
客户(自动化控制器) <==> IDispatch::Invoke <==> 调度接口(或vbtl接口) <==> 实现IDispatch接口的COM组件 (自动化服务器)

自动化服务器 : COM组件
自动化控制器 :COM客户

相关知识:IDispatch, 调度接口,双重接口,类型库,IDL, VARIANT, BSTR
调度接口(dispinterface) :IDispatch::Invoke的一个实现所能调用的函数集合,客户只能通过IDispatch::Invoke使用组件
COM(vbtl)接口(custome) : 一个指针,指向一个函数指针数组,数组前三个元素是 QueryInterface,AddRef和Release
双重接口(dual) :客户既可以通过调度接口(IDispatch::Invoke),也可以直接通过COM接口(vbtl调用)使用组件

一般C++程序直接使用抽象接口调用COM组件,而编译器会进行地址映射。例如:
pIX->Fx (msg) ;
实际会被编译成这样: 
(*(pIX->vbtl [IndexOfFx]))(pIX, msg) ;
具体如下:
1. 获取Fx在虚函数表中的索引 IndexOfFx = 4
2. 获取Fx的函数地址 pAddressOfFx = pIX->vbtl [IndexOfFx]
3. 解引用,调用函数 (注意需要传入this指针) (*pAddressOfFx)(pIX, msg) 
上面三步合成就是 (*(pIX->vbtl [IndexOfFx]))(pIX, msg) ;了

但问题在于像VB, Javascript等没有指针的概念,如何做到上面几步,获取vbtl中的函数指针呢?
可以编写一个C++分析器处理 (相当于加入了一个中间层)
中间层关键要处理三种信息 : 组件的ProgID, 函数名称,参数
这个中间层通过IDispatch接口实现,其原型:

[cpp]  view plain copy
  1. IDispatch : public IUnknown  
  2. {  
  3. public:  
  4.     virtual HRESULT STDMETHODCALLTYPE GetTypeInfoCount(   
  5.         /* [out] */ UINT *pctinfo) = 0;  
  6.       
  7.     virtual HRESULT STDMETHODCALLTYPE GetTypeInfo(   
  8.         /* [in] */ UINT iTInfo,  
  9.         /* [in] */ LCID lcid,  
  10.         /* [out] */ ITypeInfo **ppTInfo) = 0;  
  11.       
  12.     virtual HRESULT STDMETHODCALLTYPE GetIDsOfNames(   
  13.         /* [in] */ REFIID riid,  
  14.         /* [size_is][in] */ LPOLESTR *rgszNames,  
  15.         /* [in] */ UINT cNames,  
  16.         /* [in] */ LCID lcid,  
  17.         /* [size_is][out] */ DISPID *rgDispId) = 0;  
  18.       
  19.     virtual /* [local] */ HRESULT STDMETHODCALLTYPE Invoke(   
  20.         /* [in] */ DISPID dispIdMember,  
  21.         /* [in] */ REFIID riid,  
  22.         /* [in] */ LCID lcid,  
  23.         /* [in] */ WORD wFlags,  
  24.         /* [out][in] */ DISPPARAMS *pDispParams,  
  25.         /* [out] */ VARIANT *pVarResult,  
  26.         /* [out] */ EXCEPINFO *pExcepInfo,  
  27.         /* [out] */ UINT *puArgErr) = 0;      
  28. };  

 

其中比较重要的有GetIDsOfNames 和 Invoke。

Invoke参数说明:
1. DISPID dispIdMember : 标志客户待调用的函数名,可由GetIDsOfNames获得
2. REFIID riid : 必须为 IID_NULL
3. LCID lcid : 用户本地化信息,可用 GetUserDefaultLCID() 获取
4. WORD wFlags : 一个函数名称其实可以和四个函数关联 (常规函数,设置属性函数,通过引用设置属性函数,获取属性函数),
                 它的值可以是DISPATCH_METHOD, DISPATCH_PROPERTYPUT, DISPATCH_PROPERTYPUTREF, DISPATCH_PROPERTYGET.
5. DISPPARAMS *pDispParams : 参数列表,其定义如下:

[cpp]  view plain copy
  1. typedef struct tagDISPPARAMS  
  2.     {  
  3.     /* [size_is] */ VARIANTARG *rgvarg; //与VARIANT相同. 所以自动控制程序能支持的类型有限  
  4.     /* [size_is] */ DISPID *rgdispidNamedArgs; //命名参数,C++中不用,VB支持  
  5.     UINT cArgs; //参数个数  
  6.     UINT cNamedArgs;  
  7.     }   DISPPARAMS;  

6. VARIANT *pVarResult :保存函数或propget的结果,没有返回值时为NULL
7. EXCEPINFO *pExcepInfo :保存例外情况的信息,可参考C++异常处理。当Invoke返回DISP_E_EXCEPTION,DISP_E_PARAMNOTFOUND等
                           时,可查询pExcepInfo中相关信息。


VARIANT 其实是一个标准类型的大枚举,定义大概如下:

[cpp]  view plain copy
  1. struct tagVARIANT  
  2.     {  
  3.     union   
  4.         {  
  5.         struct __tagVARIANT  
  6.             {  
  7.             VARTYPE vt;  
  8.             WORD wReserved1;  
  9.             WORD wReserved2;  
  10.             WORD wReserved3;  
  11.             union   
  12.                 {  
  13.                 LONGLONG llVal;  
  14.                 LONG lVal;  
  15.                 BYTE bVal;  
  16.                 SHORT iVal;  
  17.                 //......  
  18.     }  

 

VARIANT 通过VariantInit初始化,VariantInit将vt设为VT_EMPTY。
可通过VariantChangeType转化VARIANT的类型
对调度接口中的可选参数,可设vt为VT_ERROR,scode为DISP_E_PARAMNOTFOUND。

VARIANT中比较特殊的BSTR和SAFEARRAY类型。
BSTR :它是(Basic String)或(binary string)的缩写。定义如下:

[cpp]  view plain copy
  1. typedef wchar_t WCHAR;  
  2. typedef WCHAR OLECHAR;  
  3. typedef /* [wire_marshal] */ OLECHAR *BSTR;  

但BSTR带有字符计数值,这个值保存在字符数组开头,所以BSTR字串中可以有多个'/0'。
所以下面方法错误:

[c-sharp]  view plain copy
  1. BSTR bStr = L"hello" ;  

应该使用SysAllocString给BSTR赋值,使用SysFreeString释放:
[c-sharp]  view plain copy
  1. BSTR bStr = SysAllocString (L"hello") ;  


SAFEARRAY :包含边界信息的数组

[cpp]  view plain copy
  1. typedef struct tagSAFEARRAY  
  2.     {  
  3.     USHORT cDims;  
  4.     USHORT fFeatures;  
  5.     ULONG cbElements;  
  6.     ULONG cLocks;  
  7.     PVOID pvData;  
  8.     SAFEARRAYBOUND rgsabound[ 1 ];  
  9.     }   SAFEARRAY;  
  10. typedef struct tagSAFEARRAYBOUND  
  11.     {  
  12.     ULONG cElements;  
  13.     LONG lLbound;  
  14.     }   SAFEARRAYBOUND;  

fFeatures 表示SAFEARRAY中数据的类型
自动化库OLEAUT32.Dll中有一系列操作SAFEARRAY的函数,以SafeArray为前缀

 

一个调度接口的可能实现:

一个调度接口的可能实现

 

一个双重接口的可能实现

一个双重接口的可能实现

 

调度接口最好用双重接口实现,这样C++程序员可直接通过vbtl调用函数。
IDispatch::Invoke的两个主要缺点:
1. 效率低,(进程内组件可能差几个数量级,进程外甚至远程组件就不明显了)
2. 参数只能用标准参数

类型库:
有了Invoke, VB或C++程序可以在不知道接口的任何类型信息下控制组件(当然程序员还是需要阅读文档知道接口的参数细节),
但这样做需要运行时类型检查和转换,这样开销很大,并且可能隐藏错误。
所以COM提供类型库,只是一种语言无关,适合解释性语言的C++头文件等价物。
类型库提供组件,接口,方法,属性,参数,接口等类型信息。
它是一个二进制文件,是IDL文件的一个编译版本。
有了类型库,VB也可以通过组件双重接口的Vtbl部分访问组件。

类型库可由CreateTypeLib创建,他返回ICreatetypeLib接口,这种方式很少用。
类型库可在IDL中声明,通过MIDL编译 (TLB后缀,也可包含在exe或dll中)
它包括一个GUID, 一个版本号和一个帮助字符串
coclass 定义一个组件

[cpp]  view plain copy
  1. library ServerLib  
  2. {  
  3.     importlib("stdole32.tlb") ;  
  4.     // Component 1  
  5.     [  
  6.         uuid(0c092c29-882c-11cf-a6bb-0080c7b2d682),  
  7.         helpstring("Component 1 Class")  
  8.     ]  
  9.     coclass Component1  
  10.     {  
  11.         [default] interface IX ;  
  12.         interface IY ;  
  13.         interface IZ ;  
  14.     };  
  15. }  

 

类型库的使用
1. 装载
LoadRegTypeLib,从注册表中装载
LoadTypeLib, 从硬盘上装载(装载库时会自动注册,但若提供完整路径名则不会注册,需要调用RegisterTypeLib注册)
LoadLibFromResource 从Exe/Dll中装载

示例:

[cpp]  view plain copy
  1. HRESULT hr ;  
  2. ITypeLib* pITypeLib = NULL ;  
  3. hr = ::LoadRegTypeLib(LIBID_ServerLib, 1, 0, 0x00, &pITypeLib) ;  
  4. if (FAILED(hr))   
  5. {  
  6.     trace("LoadRegTypeLib Failed, now trying LoadTypeLib.", hr) ;  
  7.     // Get the fullname of the server's executable.  
  8.     char szModule[512] ;  
  9.     DWORD dwResult = ::GetModuleFileName(CFactory::s_hModule, szModule, 512) ;   
  10.     // Split the fullname to get the pathname.  
  11.     char szDrive[_MAX_DRIVE];  
  12.     char szDir[_MAX_DIR];  
  13.     _splitpath(szModule, szDrive, szDir, NULL, NULL) ;  
  14.     // Append name of registry.  
  15.     char szTypeLibName[] = "Server.tlb" ;  
  16.     char szTypeLibFullName[_MAX_PATH];  
  17.     sprintf(szTypeLibFullName, "%s%s%s", szDrive, szDir, szTypeLibName) ;  
  18.     // convert to wide char  
  19.     wchar_t wszTypeLibFullName[_MAX_PATH] ;  
  20.     mbstowcs(wszTypeLibFullName, szTypeLibFullName, _MAX_PATH) ;  
  21.     // if LoadTypeLib succeeds, it will have registered  
  22.     // the type library for us.  
  23.     // for the next time.    
  24.     hr = ::LoadTypeLib(wszTypeLibFullName, &pITypeLib) ;  
  25.     if(FAILED(hr))          
  26.     {  
  27.         trace("LoadTypeLib Failed.", hr) ;  
  28.         return hr;     
  29.     }  
  30.     // Ensure that the type library is registered.  
  31.     hr = RegisterTypeLib(pITypeLib, wszTypeLibFullName, NULL) ;  
  32.     if(FAILED(hr))          
  33.     {  
  34.         trace("RegisterTypeLib Failed.", hr) ;  
  35.         return hr ;     
  36.     }  
  37. }  

 

装载完成后,得到一个ITypeLib接口指针,可以调用ITypeLib::GetTypeInfoOfGuid再获取某组件或接口的信息,他返回一个ITypeInfo指针
ITypeInfo指针可以获取组件,接口,方法,属性,结构和其他类似的任何信息
不过一般C++组件程序员只将它用于实现IDispatch接口,实现IDispatch接口可以简单的将GetIDsOfNames和Invoke转发给对应的ITypeInfo指针

[c-sharp]  view plain copy
  1. // Get type information for the interface of the object.  
  2. ITypeInfo *pITypeInfo = NULL;  
  3. hr = pITypeLib->GetTypeInfoOfGuid(IID_IX, &pITypeInfo) ;  
  4. pITypeLib->Release() ;  
  5. if (FAILED(hr))    
  6. {   
  7.     trace("GetTypeInfoOfGuid failed.", hr) ;  
  8.     return hr ;  
  9. }     
  10. HRESULT __stdcall CA::GetIDsOfNames( const IID& iid, OLECHAR** arrayNames, UINT countNames, LCID, DISPID* arrayDispIDs)  
  11. {  
  12.     if (iid != IID_NULL)  
  13.     {  
  14.         return DISP_E_UNKNOWNINTERFACE ;  
  15.     }  
  16.     HRESULT hr = m_pITypeInfo->GetIDsOfNames(arrayNames, countNames, arrayDispIDs) ;  
  17.     return hr ;  
  18. }  
  19. HRESULT __stdcall CA::Invoke( DISPID dispidMember, const IID& iid, LCID, WORD wFlags,  
  20.       DISPPARAMS* pDispParams, VARIANT* pvarResult, EXCEPINFO* pExcepInfo, UINT* pArgErr)  
  21. {          
  22.     if (iid != IID_NULL)  
  23.     {  
  24.         return DISP_E_UNKNOWNINTERFACE ;  
  25.     }  
  26.     ::SetErrorInfo(0, NULL) ;  
  27.     HRESULT hr = m_pITypeInfo->Invoke( static_cast<IDispatch*>(this), dispidMember, wFlags,   
  28.         pDispParams, pvarResult, pExcepInfo, pArgErr) ;   
  29.     return hr ;  
  30. }  

 

类型库注册
在注册表的HKEY_CLASSED_ROOT/TypeLib下

异常的引发
给Invoke的EXCEPINFO结构参数填充信息
1. 组件实现ISupportErrorInfo接口

[cpp]  view plain copy
  1. class CA : public CUnknown,  
  2.            public IX,  
  3.            public ISupportErrorInfo  
  4. {//......  
  5.     // ISupportErrorInfo  
  6.     virtual HRESULT __stdcall InterfaceSupportsErrorInfo(const IID& riid)  
  7.     {  
  8.         return (riid == IID_IX) ? S_OK : S_FALSE ;  
  9.     }  
  10. }  

 

2. IDispatch::Invoke实现中,调用ITypeInfo::Invoke前先调用

[cpp]  view plain copy
  1. SetErrorInfo (0, NULL);  

 

3. 发生异常时,调用CreateErrorInfo获取ICreateErrorInfo接口指针
   使用ICreateErrorInfo接口指针填充错误信息
   调用SetErrorInfo填充

[cpp]  view plain copy
  1. // Create the error info object.  
  2. ICreateErrorInfo* pICreateErr ;  
  3. HRESULT hr = ::CreateErrorInfo(&pICreateErr) ;  
  4. if (FAILED(hr))  
  5. {  
  6.     return E_FAIL ;  
  7. }  
  8. // pICreateErr->SetHelpFile(...) ;  
  9. // pICreateErr->SetHelpContext(...) ;  
  10. pICreateErr->SetSource(L"InsideCOM.Chap11") ;  
  11. pICreateErr->SetDescription (L"This is a fake error generated by the component.") ;  
  12. IErrorInfo* pIErrorInfo = NULL ;  
  13. hr = pICreateErr->QueryInterface(IID_IErrorInfo, (void**)&pIErrorInfo) ;  
  14. if (SUCCEEDED(hr))  
  15. {  
  16.     ::SetErrorInfo(0L, pIErrorInfo) ;  
  17.     pIErrorInfo->Release() ;  
  18. }  
  19. pICreateErr->Release() ;  
  20. return E_FAIL ;  

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值