COM 组件设计与应用(三)——数据类型
本文摘自:http://www.vckbase.com/index.php/wv/1206
一、前言
上回书介绍了GUID、CLSID、IID和接口的概念。本回的重点是介绍 COM 中的数据类型。咋还不介绍组件程序的设计步骤呀?咳......别着急,别着急!孔子曰:“饭要一口一口地吃”;老子语:“心急吃不了热豆腐”,孙子云:“走一步看一步吧” ...... 先掌握必要的知识,将来写起程序来才会得心应手也:-)
走入正题之前,请大家牢牢记住一条原则:COM 组件是运行在分布式环境中的。比如,你写了一个组件程序—LL或EXE),那么使用者可能是在本机的某个进程内加载组件(INPROC_SERVER);也可能是从另一个进程中调用组件的进程(LOCAL_SERVER);也可能是在这台计算机上调用地球那边计算机上的组件(REMOTE_SERVER)。所以在理解和设计的时候,要时时刻刻想起这句话。快!拿出小本本,记下来!
二、HRESULT 函数返回值
每个人在做程序设计的时候,都有他们各自的哲学思想。拿函数返回值来说,就有好多种形式。
函数返回值返回值信息double sin(double)
浮点数值
计算正玄值BOOL DeleteFile(LPCTSTR)布尔值
文件删除是否成功。如失败,需要GetLastError()才能取得失败原因void * malloc(size_t)内存指针
内存申请,如果失败,返回空指针 NULLLONG RegDeleteKey(HKEY,LPCTSTR)整数
删除注册表项。0表示成功,非0失败,同时这个值就反映了失败的原因UINT DragQueryFile(HDROP,UINT,LPTSTR,UINT)整数
取得拖放文件信息。以不同的参数调用,则返回不同的含义:一会儿表示文件个数,一会儿表示文件名长度,一会儿表示字符长度...... ......
...
...... ......如此纷繁复杂的返回值,如此含义多变的返回值,使得大家在学习和使用的过程中,增加了额外的困难。好了,COM 的设计规范终于对他们进行了统一。组件API及接口指针中,除了IUnknown::AddRef()和IUnknown::Release()两个函数外,其它所有的函数,都以 HRESULT 作为返回值。大家想象一个组件的接口函数比如叫Add(),完成2个整数的加法运算,在C语言中,我们可以如下定义:
HRESULT值含义S_OK0x00000000成功S_FALSE0x00000001函数成功执行完成,但返回时出现错误E_INVALIDARG0x80070057参数有错误E_OUTOFMEMORY0x8007000E内存申请错误E_UNEXPECTED0x8000FFFF未知的异常E_NOTIMPL0x80004001未实现功能E_FAIL0x80004005没有详细说明的错误。一般需要取得 Rich Error 错误信息(注1)E_POINTER0x80004003无效的指针E_HANDLE0x80070006无效的句柄E_ABORT0x80004004终止操作E_ACCESSDENIED0x80070005访问被拒绝E_NOINTERFACE0x80004002不支持接口
图一、HRESULT 的结构
HRESULT 其实是一个双字节的值,其最高位(bit)如果是0表示成功,1表示错误。具体参见 MSDN 之"Structure of COM Error Codes"说明。我们在程序中如果需要判断返回值,则可以使用比较运算符号;switch开关语句;也可以使用VC提供的宏:
图二、BSTR 内存结构
BSTR 是一个指向 UNICODE 字符串的指针,且 BSTR 向前的4个字节中,使用DWORD保存着这个字符串的字节长度( 没有含字符串的结束符)。因此系统就能够正确处理并传送这个字符串到“地球另一 边”了。特别需要注意的是,由于BSTR的指针就是指向 UNICODE 串,因此 BSTR 和 LPOLESTR 可以在一定程度上混用,但一定要注意:
有函数 fun(LPCOLESTR lp),则你调用 BSTR p=...; fun(p); 正确
有函数 fun(const BSTR bstr),则你调用 LPCOLESTR p=...; fun(p); 错误!!!
有关 BSTR 的处理函数:
API 函数说明SysAllocString()申请一个 BSTR 指针,并初始化为一个字符串SysFreeString()释放 BSTR 内存SysAllocStringLen()申请一个指定字符长度的 BSTR 指针,并初始化为一个字符串SysAllocStringByteLen()申请一个指定字节长度的 BSTR 指针,并初始化为一个字符串SysReAllocStringLen()重新申请 BSTR 指针CString 函数
说明
AllocSysString()从 CString 得到 BSTRSetSysString()重新申请 BSTR 指针,并复制到 CString 中CComBSTR 函数
ATL 的 BSTR 包装类。在 atlbase.h 中定义
Append()、AppendBSTR()、AppendBytes()、ArrayToBSTR()、BSTRToArray()、AssignBSTR()、Attach()、Detach()、Copy()、CopyTo()、Empty()、Length()、ByteLength()、ReadFromStream()、WriteToStream()、LoadString()、ToLower()、ToUpper()运算符重载:!,!=,==,<,>,&,+=,+,=,BSTR太多了,但从函数名称不能看出其基本功能。详细资料,查看MSDN 吧。另外,左侧函数,有很多是 ATL 7.0 提供的,VC6.0 下所带的 ATL 3.0 不支持。
由于我们将来主要用 ATL 开发组件程序,因此使用 ATL 的 CComBSTR 为主。VC也提供了其它的包装类 _bstr_t。
五、各种字符串类型之间的转换
1、函数 WideCharToMultiByte(),转换 UNICODE 到 MBCS。使用范例:
A2BSTROLE2AT2AW2AA2COLEOLE2BSTRT2BSTRW2BSTRA2CTOLE2CAT2CAW2CAA2CWOLE2CTT2COLEW2COLEA2OLEOLE2CWT2CWW2CTA2TOLE2TT2OLEW2OLEA2WOLE2WT2WW2T上表中的宏函数,其实非常容易记忆:
2好搞笑的缩写,to 的发音和 2 一样,所以借用来表示“转换为、转换到”的含义。AANSI 字符串,也就是 MBCS。W、OLE宽字符串,也就是 UNICODE。T中间类型T。如果定义了 _UNICODE,则T表示W;如果定义了 _MBCS,则T表示ACconst 的缩写使用范例:
类型字节长度假值真值bool1(char)0(false)1(true)BOOL4(int)0(FALSE)1(TRUE)VT_BOOL2(short int)0(VARIANT_FALSE)-1(VARIANT_TRUE)所以如果你 v.boolVal=true 这样赋值,那么将来 if(VARIANT_TRUE==v.boolVal) 的时候会出问题(-1 != 1)。但是你注意观察,任何布尔类型的“假”都是0,因此作为一个好习惯,在做布尔判断的时候,不要和“真值”相比较,而要与“假值”做比较。
学生:谢谢老师,你太牛了。我对老师的敬仰如滔滔江水,连绵不绝......
学生:我想用 VARIANT 保存字符串,如何做?
老师:VARIANT v; v.vt=VT_BSTR; v.bstrVal=SysAllocString(L"Hello,你好");
学生:哦......我明白了。可是这么操作真够麻烦的,有没有简单一些的方法?
老师:有呀,你可以使用现成的包装类 CComVariant、COleVariant、_variant_t。比如上面三个问题就可以这样书写:CComVariant v1(100),v2(true),v3("Hello,你好"); 简单了吧?!(注4)
学生:老师,我再问最后一个问题,我如何用 VARIANT 保存一个数组?
老师:这个问题很复杂,我现在不能告诉你,我现在告诉你怕你印象不深......(注5)
学生:~!@#$%^&*()......晕!
七、小结
以上所介绍的内容,是基本功,必须熟练掌握。先到这里吧,休息一会儿......更多精彩内容,敬请关注《COM 组件设计与应用(四)》
注1:在后续的 ISupportErrorInfo 接口中介绍。
注2:常见的数据类型,请参考 IDL 文件的说明。(别着急,还没写那......嘿嘿)
注3:跨语言就是各种语言中都能使用COM组件。但啥时候能跨平台呢?
注4:CComVariant/COlevariant/_variant_t 请参看 MSDN。
注5:关于安全数组 SafeArray 的使用,在后续的文章中讨论。