自动化服务器:实现了IDispatch接口的COM组件。
自动化控制程序:通过IDispatch接口同自动化服务器进行通信的COM客户。
通过COM接口提供的任何服务都可以通过IDispatch接口提供。有了IDispatch,COM可通过一个标准的接口提供它所支持的服务,而无需提供多个特定于服务的接口。
IDispatch将接受一个函数名,并执行它。
IDispatch定义 可以在编译器的/Include目录下找到IDispatch定义
interface IDispatch : IUnknown{ typedef [unique] IDispatch * LPDISPATCH; HRESULT GetTypeInfoCount( [out] UINT * pctinfo ); HRESULT GetTypeInfo( [in] UINT iTInfo, [in] LCID lcid, [out] ITypeInfo ** PPTInfo ); //读取一个函数名称并返回其调度ID,即DISPID HRESULT GetIDsOfNames( [in] REFIID riid, [in, size_is(cNames)] LPOLESTR * rgszNames, [in] UINT cNames, [in] LCID lcid, [out, size_is(cNames)] DISPID * rgDispId ); //为执行某个函数,自动化控制程序把DISPID传给Invoke //Invoke的行为由受到的DISPID决定 [local] HRESULT Invoke( //控制程序待调用函数的DISPID [in] DISPID dispIdMember, //保留的,必须为IID_NULL [in] REFIID riid, //保存位置信息 [in] LCID lcid, //调用的函数是哪种类型 //DISPATCH_METHOD 常规函数 //DISPATCH_PROPERTYGET 获取属性的含 //DISPATCH_PROPERTYPUT 设置属性的函数 //DISPATCH_PROPERTYPUTREF 通过引用设置属性的函数 [in] Word wFlags, /* 传给被调用函数的参数 typedef struct tagDISPPARAMS { 参数数组,只有那些能放入VARIANTARG结构中的类型才可以通过调度接口进行传递 VARIANTARG *rgvarg; DISPID *rgdispidNamedArgs; 命名参数的DISPID,对于C++永远为NULL UINT cArgs; 数组中元素个数 UINT cNamedArgs; 命名参数个数,对于C++永远为0 } DISPPARAMS; */ [in, out] DISPPARAMS * pDispParams, //用于保存Invoke所执行的函数或propget的结果 //对于没有返回值的成员函数或propputs及propputrefs,为NULL //VARIANT是一个union结构,方便用相同的方式保存不同类型变量的值 [out] VARIANT * pVarResult, /* 若Invoke执行的函数或属性遇到一个例外情况,此结构被填入关于例外的信息。 typedef struct FARSTRUCT tagEXCEPINFO { unsigned short wCode; // Error code. Unsigned short wReserved; // Reserved. BSTR bstrSource; // Exception source. BSTR bstrDescription; //Exception description. BSTR bstrHelpFile; // Help file path. Unsigned long dwHelpContext; // Help context ID. Void FAR* pvReserved; // Reserved. HRESULT (STDAPICALLTYPE FAR* pfnDeferredFillIn) // Pointer to a (struct tagEXCEPINFO FAR*); // function that // fills in help and // description info. SCODE scode; // Return value. } EXCEPINFO, FAR* LPEXCEPINFO; */ [out] EXCEPINFO * pExcepInfo, //若Invoke的返回值为DISP_E_PARAMNOTFOUND或DISP_E_TYPEMISMATCH //putArgErr保存与此错误相应的参数的索引 [out] UINT * puArgErr );IDispatch的使用
HRESULT hr = OleInitialize(NULL);//得到应用程序的CLSIDwchar_tprogid[] = L"InsideCom.Cmpnt1";CLSID clsid;CLSIDFromProgID(progid, &clsid);//创建组件,得到IDispatch指针IDispatch *pDispach = NULL;CoCreateInstance(clsid, NULL,CLSCTX_INPROC_SERVER, IID_IDispatch,(void**)&pDispach);//得到调度函数Fx的DISPIDDISPID dispid;OLECHAR* name = L"Fx";pDispach->GetIDsOfNames( IID_NULL, //必须为IID_NULL &name, //函数名称 1, //函数个数 GetUserDefaultLCID(), &dispid); //DISPID//函数Fx的参数DISPPARAMS dispparamsNoArgs = { NULL,NULL,0,0}pDispach->Invoke(dispid, //DISPID IID_NULL, //必须为IID_NULL GetUserDefaultLCID(), DISPATCH_METHOD, //方法 &dispparamsNoArgs, //方法参数 NULL, //结果 NULL, //例外 NULL); //错误索引以上代码可变成不用参数的任意调度函数,只需要向客户询问ProgID和待用函数的名称即可。 Invoke的威力在于可以被一致地调用,任何组件只要实现了Invoke,均可以用相同的代码使用它。
例外情形
用到Invoke的倒数第二个参数
EXCEPINFO excepinfo;HRESULT hr = pDispach->Invoke(...,&excepinfo);if(FAILED(hr)){ if(hr == DISP_E_EXCEPTION) { if(excepinfo.pfnDeferredFillIn != NULL) { (*(excepinfo.pfnDeferredFillIn))(&excepinfo); } cout<<"Exception information: "<<endl <<"Source: "<<excepinfo.bstrSource<<endl <<"Description: "<<excepinfo.bstrDescription<<ends; }}IDispatch::Invoke的一个实现所实现的函数集被称作一个调度接口。 调度接口的一个图示:
让实现IDispatch::Invoke的COM组件继承IDispatch而不是IUnkown。双重接口也是一种调度接口,使得通过Invoke访问的函数也能直接通过vtbl访问。
示例
构造一个实现双重接口的IX的组件。
//// Server.idl - IDL source for Server.dll//// This file will be processed by the MIDL compiler to// produce the type library (Server.tlb) and marshaling code.//// Interface IX[ object, uuid(32BB8326-B41B-11CF-A6BB-0080C7B2D682), helpstring("IX Interface"), pointer_default(unique), dual, oleautomation]interface IX : IDispatch{ import "oaidl.idl" ; HRESULT Fx() ; HRESULT FxStringIn([in] BSTR bstrIn) ; HRESULT FxStringOut([out, retval] BSTR* pbstrOut) ; HRESULT FxFakeError() ;} ;客户使用双重接口连接到组件: 演示客户如何调用FxStringIn
COM中类型库,是与语言无关的、适合于解释性语言和宏编程语言的C++头文件等价的,为程序的正常运行提供某种程度的保证。 类型库将提供有关组件、接口、方法、属性、参数及结构的类型信息。类型库是一个二进制文件。
类型库的创建
可用IDL、MIDL建立类型库。 MIDL编译server.def后,会生成server.tlb的类型库。
关键语句是library,library之后的方括号中的内容都将被编译到类型库中。
//// Server.idl - IDL source for Server.dll//// This file will be processed by the MIDL compiler to// produce the type library (Server.tlb) and marshaling code.//// Interface IX[ object, uuid(32BB8326-B41B-11CF-A6BB-0080C7B2D682), helpstring("IX Interface"), pointer_default(unique), dual, oleautomation]interface IX : IDispatch{ import "oaidl.idl" ; HRESULT Fx() ; HRESULT FxStringIn([in] BSTR bstrIn) ; HRESULT FxStringOut([out, retval] BSTR* pbstrOut) ; HRESULT FxFakeError() ;} ;//// Component and type library descriptions//[ uuid(D3011EE1-B997-11CF-A6BB-0080C7B2D682), //GUID version(1.0), //版本号 helpstring("Inside COM, Chapter 11 1.0 Type Library") //帮助字符串]library ServerLib{ importlib("stdole32.tlb") ; // Component [ uuid(0C092C2C-882C-11CF-A6BB-0080C7B2D682), helpstring("Component Class") ] coclass Component //定义一个组件 { //具有IX接口的组件 //MIDL将编译生成一个包含组件和IX的类型库 //之所以包含组件,是因为coclass包含library语句 //之所以包含IX,是因为在library语句中引用了它 [default] interface IX ; } ;} ;类型库的分发
在生成了类型库之后,既可以将其作为一个单独的文件发行,也可以将其作为一个资源包含在EXE或DLL中,后者可简化安装过程。
类型库的使用
首先应装载并注册类型库。
//// 装载并注册类型库//HRESULT CA::Init(){ HRESULT hr ; // Load TypeInfo on demand if we haven't already loaded it. if (m_pITypeInfo == NULL) { ITypeLib* pITypeLib = NULL ; // 从Windows注册表中装载指定的类型库 // 返回ITypeLib接口类型的指针 hr = ::LoadRegTypeLib(LIBID_ServerLib, 1, 0, // Major/Minor version numbers 0x00, &pITypeLib) ; if (FAILED(hr)) { trace("LoadRegTypeLib Failed, now trying LoadTypeLib.", hr) ; // 如果注册表中没有,从硬盘上装载 // Get the fullname of the server's executable. char szModule[512] ; DWORD dwResult = ::GetModuleFileName(CFactory::s_hModule, szModule, 512) ; // Split the fullname to get the pathname. char szDrive[_MAX_DRIVE]; char szDir[_MAX_DIR]; _splitpath(szModule, szDrive, szDir, NULL, NULL) ; // Append name of registry. char szTypeLibFullName[_MAX_PATH]; sprintf(szTypeLibFullName, "%s%s%s", szDrive, szDir, szTypeLibName) ; // convert to wide char wchar_t wszTypeLibFullName[_MAX_PATH] ; mbstowcs(wszTypeLibFullName, szTypeLibFullName, _MAX_PATH) ; // 从硬盘上装载指定文件名称的类型库 // 装载之后在注册表中注册 // 但若提供了一个完整的路径名称,不会注册 hr = ::LoadTypeLib(wszTypeLibFullName, &pITypeLib) ; if(FAILED(hr)) { trace("LoadTypeLib Failed.", hr) ; return hr; } // 注册类型库 hr = RegisterTypeLib(pITypeLib, wszTypeLibFullName, NULL) ; if(FAILED(hr)) { trace("RegisterTypeLib Failed.", hr) ; return hr ; } } // 为了获取某个组件或接口信息,将其CLSID或IID传给 // GetTypeInfoOfGuid,会返回一个指向所需项目的ITypeInfo指针 hr = pITypeLib->GetTypeInfoOfGuid(IID_IX, &m_pITypeInfo) ; pITypeLib->Release() ; if (FAILED(hr)) { trace("GetTypeInfoOfGuid failed.", hr) ; return hr ; } } return S_OK ;}注册表中的类型库
IDispatch接口的实现
前面通过GetTypeInfoOfGuid得到了ITypeInfo指针,此指针可用来实现IDispatch接口。
//// IDispatch implementation//HRESULT __stdcall CA::GetTypeInfoCount(UINT* pCountTypeInfo){ trace("GetTypeInfoCount call succeeded.") ; *pCountTypeInfo = 1 ; return S_OK ;}HRESULT __stdcall CA::GetTypeInfo( UINT iTypeInfo, LCID, // This object does not support localization. ITypeInfo** ppITypeInfo){ *ppITypeInfo = NULL ; if(iTypeInfo != 0) { trace("GetTypeInfo call failed -- bad iTypeInfo index.") ; return DISP_E_BADINDEX ; } trace("GetTypeInfo call succeeded.") ; // Call AddRef and return the pointer. m_pITypeInfo->AddRef() ; *ppITypeInfo = m_pITypeInfo ; return S_OK ;}HRESULT __stdcall CA::GetIDsOfNames( const IID& iid, OLECHAR** arrayNames, UINT countNames, LCID, // Localization is not supported. DISPID* arrayDispIDs){ if (iid != IID_NULL) { trace("GetIDsOfNames call failed -- bad IID.") ; return DISP_E_UNKNOWNINTERFACE ; } trace("GetIDsOfNames call succeeded.") ; HRESULT hr = m_pITypeInfo->GetIDsOfNames(arrayNames, countNames, arrayDispIDs) ; return hr ;}HRESULT __stdcall CA::Invoke( DISPID dispidMember, const IID& iid, LCID, // Localization is not supported. WORD wFlags, DISPPARAMS* pDispParams, VARIANT* pvarResult, EXCEPINFO* pExcepInfo, UINT* pArgErr){ if (iid != IID_NULL) { trace("Invoke call failed -- bad IID.") ; return DISP_E_UNKNOWNINTERFACE ; } ::SetErrorInfo(0, NULL) ; trace("Invoke call succeeded.") ; HRESULT hr = m_pITypeInfo->Invoke( static_cast<IDispatch*>(this), dispidMember, wFlags, pDispParams, pvarResult, pExcepInfo, pArgErr) ; return hr ;}异常的引发
1、组件中实现ISupportErrorInfo接口
class CA : public CUnknown, public IX, public ISupportErrorInfo{ ... ... // ISupportErrorInfo virtual HRESULT __stdcall InterfaceSupportsErrorInfo(const IID& riid) { return (riid == IID_IX) ? S_OK : S_FALSE ; } ... ...}2、在IDispatch::Invoke实现中,在ITypeInfo::Invoke实现之前,调用SetErrorInfo(0, NULL)
HRESULT __stdcall CA::Invoke( DISPID dispidMember, const IID& iid, LCID, // Localization is not supported. WORD wFlags, DISPPARAMS* pDispParams, VARIANT* pvarResult, EXCEPINFO* pExcepInfo, UINT* pArgErr){ if (iid != IID_NULL) { trace("Invoke call failed -- bad IID.") ; return DISP_E_UNKNOWNINTERFACE ; } ::SetErrorInfo(0, NULL) ; trace("Invoke call succeeded.") ; HRESULT hr = m_pITypeInfo->Invoke( static_cast<IDispatch*>(this), dispidMember, wFlags, pDispParams, pvarResult, pExcepInfo, pArgErr) ; return hr ;}3、
HRESULT __stdcall CA::FxFakeError(){ trace("FxFakeError is faking an error.") ; // 创建异常信息对象 ICreateErrorInfo* pICreateErr ; // 当发生异常时,调用CreateErrorInfo // 获取一个ICreateErrorInfo指针 HRESULT hr = ::CreateErrorInfo(&pICreateErr) ; if (FAILED(hr)) { return E_FAIL ; } // 使用ICreateErrorInfo指针填充关于错误的信息 // pICreateErr->SetHelpFile(...) ; // pICreateErr->SetHelpContext(...) ; pICreateErr->SetSource(L"InsideCOM.Chap11") ; pICreateErr->SetDescription( L"This is a fake error generated by the component.") ; IErrorInfo* pIErrorInfo = NULL ; hr = pICreateErr->QueryInterface(IID_IErrorInfo, (void**)&pIErrorInfo) ; if (SUCCEEDED(hr)) { // 将ICreateErrorInfo指针作为第2个参数传过去 // 第1个参数是保留的,恒为0 ::SetErrorInfo(0L, pIErrorInfo) ; pIErrorInfo->Release() ; } pICreateErr->Release() ; return E_FAIL ;}新闻热点
疑难解答