COM组件设计与应用(九)
IDispatch 接口 for vc6.0
作者:杨老师
一、前言
终于写到了第九回,我也一直期盼着写这回的内容耶,为啥呢?因为自动化(automation)是非常常用、非常有用、非常精彩的一个 COM 功能。由于 WORD、EXCEL 等 OFFICE 软件提供了“宏”的功能,就连我们使用的VC开发环境也提供了“宏”功能,更由于 HTML、ASP、JSP 等都要依靠脚本(Script)的支持,更体现出了自动化接口的重要性。
如果你使用 vc6.0 的开发环境,请继续阅读。
如果你使用 vc.net 2003,请阅读下一回。
二、IDispatch接口
如果是编译型语言,那么我们可以让编译器在编译的时候装载类型库,也就是装载接口的描述。在第七回文 章当中,我们分别使用了 #include 方法和 #import 方法来实现的。装载了类型库后,编译器就知道应该如何编译接口函数的调用了---这叫“前绑定”。但是,如果想在脚本语言中使用组件,问题就大了,因为脚 本语言是解释执行的,它执行的时候不会知道具体的函数地址,怎么办?自动化接口就为此诞生了---“后绑定”。
自动化组件,其实就是实现了 IDispatch 接口的组件。IDispatch 接口有4个函数,解释语言的执行器就通过这仅有的4个函数来执行组件所提供的功能。IDispatch 接口用 IDL 形式说明如下:(注1)
[以上 IDispatch 接口函数的讲解,我们留到后回中进行介绍。如何在组件程序中实现这些函数那?还好,还好,就象 IUnknown 一样,MFC 和 ATL 都帮我们已经完成了。本回我们着重介绍组件的编写,下回则介绍组件的调用方法。
object,
uuid(00020400-0000-0000-C000-000000000046), // IDispatch 接口的 IID = IID_IDispatch
pointer_default(unique)
]
interface IDispatch : IUnknown
{
typedef [unique] IDispatch * LPDISPATCH; // 转定义 IDispatch * 为 LPDISPATCH
HRESULT GetTypeInfoCount([out] UINT * pctinfo); // 有关类型库的这两个函数,咱们以后再说
HRESULT GetTypeInfo([in] UINT iTInfo,[in] LCID lcid,[out] ITypeInfo ** ppTInfo);
HRESULT GetIDsOfNames( // 根据函数名字,取得函数序号(DISPID)
[in] REFIID riid,
[in, size_is(cNames)] LPOLESTR * rgszNames,
[in] UINT cNames,
[in] LCID lcid,
[out, size_is(cNames)] DISPID * rgDispId
);
[local] // 本地版函数
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
);
[call_as(Invoke)] // 远程版函数
HRESULT RemoteInvoke(
[in] DISPID dispIdMember,
[in] REFIID riid,
[in] LCID lcid,
[in] DWORD dwFlags,
[in] DISPPARAMS * pDispParams,
[out] VARIANT * pVarResult,
[out] EXCEPINFO * pExcepInfo,
[out] UINT * pArgErr,
[in] UINT cVarRef,
[in, size_is(cVarRef)] UINT * rgVarRefIdx,
[in, out, size_is(cVarRef)] VARIANTARG * rgVarRef
);
}
long CDispSimple::Add(long n1, long n2)3-10:编译注册
{
return n1 + n2;
}
BSTR CDispSimple::Upper(LPCTSTR str)
{
CString strResult(str);
strResult.MakeUpper();
return strResult.AllocSysString();
}
STDMETHODIMP CDispSimple::Add(VARIANT v1, VARIANT v2, VARIANT *pVal)刚才卖的关子,现在开始揭密了......加法函数Add()不使用long类型,而使用VARIANT的好处是:函数内部动态判断参数类型,如果是整数 则进行整数加法,如果是字符串,则进行字符串加法(字符串加法就是字符串连接哈)。也就是说,如果参数是VARIANT,那么我们就可以实现函数的可变参 数类型呀。怪怪个咙,真爽!
{
::VariantInit( pVal ); // 永远初始化返回值是个好习惯
CComVariant v_1( v1 );
CComVariant v_2( v2 );
if((v1.vt & VT_I4) && (v2.vt & VT_I4) ) // 如果都是整数类型
{ // 这里比较没有使用 == ,而使用了运算符 & ,你知道这是为什么吗?
v_1.ChangeType( VT_I4 ); // 转换为整数
v_2.ChangeType( VT_I4 ); // 转换为整数
pVal->vt = VT_I4;
pVal->lVal = v_1.lVal + v_2.lVal; // 加法
}
else
{
v_1.ChangeType( VT_BSTR ); // 转换为字符串
v_2.ChangeType( VT_BSTR ); // 转换为字符串
CComBSTR bstr( v_1.bstrVal );
bstr.AppendBSTR( v_2.bstrVal ); // 字符串连接
pVal->vt = VT_BSTR;
pVal->bstrVal = bstr.Detach();
}
return S_OK;
}
STDMETHODIMP CDispSimple::Upper(BSTR str, BSTR *pVal)
{
*pVal = NULL; // 永远初始化返回值是个好习惯
CComBSTR s(str);
s.ToUpper(); // 转换为大写
*pVal = s.Copy();
return S_OK;
}