自动化服务器:实现了IDispatch接口的COM组件。
自动化控制程序:通过IDispatch接口同自动化服务器进行通信的COM客户。
IDispatch
通过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);
//得到应用程序的CLSID
wchar_tprogid[] = L"InsideCom.Cmpnt1";
CLSID clsid;
CLSIDFromProgID(progid, &clsid);
//创建组件,得到IDispatch指针
IDispatch *pDispach = NULL;
CoCreateInstance(clsid,
NULL,CLSCTX_INPROC_SERVER,
IID_IDispatch,(void**)&pDispach);
//得到调度函数Fx的DISPID
DISPID 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 ;
}