【转载】COM 组件设计与应用(三)——数据类型

原文:http://vckbase.com/index.php/wv/1206.htmlphp

COM 组件设计与应用 系列文章:http://vckbase.com/index.php/piwz?&p=1html

1、前言数组

上回书介绍了GUID、CLSID、IID和接口的概念。本回的重点是介绍 COM 中的数据类型。咋还不介绍组件程序的设计步骤呀?咳......别着急,别着急!孔子曰:“饭要一口一口地吃”;老子语:“心急吃不了热豆腐”,孙子云:“走一步看一步吧” ...... 先掌握必要的知识,未来写起程序来才会驾轻就熟也:-)安全

走入正题以前,请你们紧紧记住一条原则:COM 组件是运行在分布式环境中的。好比,你写了一个组件程序(DLL或EXE),那么使用者多是在本机的某个进程内加载组件(INPROC_SERVER);也多是从另外一个进程中调用组件的进程(LOCAL_SERVER);也多是在这台计算机上调用地球那边计算机上的组件(REMOTE_SERVER)。因此在理解和设计的时候,要时时刻刻想起这句话。快!拿出小本本,记下来!服务器

2、HRESULT 函数返回值网络

每一个人在作程序设计的时候,都有他们各自的哲学思想。拿函数返回值来讲,就有好多种形式。分布式

函数 返回值 返回值信息
double sin(double)

浮点数值ide

计算正玄值
BOOL DeleteFile(LPCTSTR)

布尔值函数

文件删除是否成功。如失败,须要GetLastError()才能取得失败缘由
void * malloc(size_t)

内存指针学习

内存申请,若是失败,返回空指针 NULL
LONG RegDeleteKey(HKEY,LPCTSTR)

整数

删除注册表项。0表示成功,非0失败,同时这个值就反映了失败的缘由
UINT DragQueryFile(HDROP,UINT,LPTSTR,UINT)

整数

取得拖放文件信息。以不一样的参数调用,则返回不一样的含义:
一下子表示文件个数,一下子表示文件名长度,一下子表示字符长度
......  ......

...

......  ......

如此纷繁复杂的返回值,如此含义多变的返回值,使得你们在学习和使用的过程当中,增长了额外的困难。好了,COM 的设计规范终于对他们进行了统一。组件API及接口指针中,除了IUnknown::AddRef()和IUnknown::Release()两个函数外,其它全部的函数,都以 HRESULT 做为返回值。你们想象一个组件的接口函数好比叫Add(),完成2个整数的加法运算,在C语言中,咱们能够以下定义:

1. long Add( long n1, long n2 )
2. {
3. return n1 + n2;
4. }

还记得刚才咱们说的原则吗?COM 组件是运行在分布式环境中的。也就是说,这个函数可能运行在“地球另外一边”的计算机上,既然运行在那么遥远的地方,就有可能出现服务器关机、网络掉线、运行超时、对方不在服务区......等异常。因而,这个加法函数,除了须要返回运算结果之外,还应该返回一个值------函数是否被正常执行了。

1. HRESULT Add( long n1, long n2, long *pSum )
2. {
3. *pSum = n1 + n2;
4. return S_OK;
5. }

若是函数正常执行,则返回 S_OK,同时真正的函数运行结果则经过参数指针返回。若是遇到了异常状况,则COM系统通过判断,会返回相应的错误值。常见的返回值有:

HRESULT 含义
S_OK 0x00000000 成功
S_FALSE 0x00000001 函数成功执行完成,但返回时出现错误
E_INVALIDARG 0x80070057 参数有错误
E_OUTOFMEMORY 0x8007000E 内存申请错误
E_UNEXPECTED 0x8000FFFF 未知的异常
E_NOTIMPL 0x80004001 未实现功能
E_FAIL 0x80004005 没有详细说明的错误。通常须要取得 Rich Error 错误信息(注1)
E_POINTER 0x80004003 无效的指针
E_HANDLE 0x80070006 无效的句柄
E_ABORT 0x80004004 终止操做
E_ACCESSDENIED 0x80070005 访问被拒绝
E_NOINTERFACE 0x80004002 不支持接口

图1、HRESULT 的结构

HRESULT 实际上是一个双字节的值,其最高位(bit)若是是0表示成功,1表示错误。具体参见 MSDN 之"Structure of COM Error Codes"说明。咱们在程序中若是须要判断返回值,则可使用比较运算符号;switch开关语句;也可使用VC提供的宏:

1. HRESULT hr = 调用组件函数;
2. if( SUCCEEDED( hr ) ){...} // 若是成功
3. ......
4. if( FAILED( hr ) ){...} // 若是失败
5. ......

3、UNICODE

计算机发明后,为了在计算机中表示字符,人们制定了一种编码,叫ASCII码。ASCII码由一个字节中的7位(bit)表示,范围是0x00 - 0x7F 共128个字符。他们觉得这128个数字就足够表示abcd....ABCD....1234 这些字符了。

咳......说英语的人就是“笨”!后来他们忽然发现,若是须要按照表格方式打印这些字符的时候,缺乏了“制表符”。因而又扩展了ASCII的定义,使用一个字节的所有8位(bit)来表示字符了,这就叫扩展ASCII码。范围是0x00 - 0xFF 共256个字符。

咳......说中文的人就是聪明!中国人利用连续2个扩展ASCII码的扩展区域(0xA0之后)来表示一个汉字,该方法的标准叫GB-2312。后来,日文、韩文、阿拉伯文、台湾繁体(BIG-5)......都使用相似的方法扩展了本地字符集的定义,如今统一称为 MBCS 字符集(多字节字符集)。这个方法是有缺陷的,由于各个国家地区定义的字符集有交集,所以使用GB-2312的软件,就不能在BIG-5的环境下运行(显示乱码),反之亦然。

咳......说英语的人终于变“聪明”一些了。为了把全世界人民全部的全部的文字符号都统一进行编码,因而制定了UNICODE标准字符集。UNICODE 使用2个字节表示一个字符(unsigned shor int、WCHAR、_wchar_t、OLECHAR)。这下终于好啦,全世界任何一个地区的软件,能够不用修改地就能在另外一个地区运行了。虽然我用 IE 浏览日本网站,显示出我不认识的日文文字,但至少不会是乱码了。UNICODE 的范围是 0x0000 - 0xFFFF 共6万多个字符,其中光汉字就占用了4万多个。嘿嘿,中国人赚大发了:0)

在程序中使用各类字符集的方法:

01. const char * p = "Hello"// 使用 ASCII 字符集
02. const char * p = "你好"// 使用 MBCS 字符集,因为 MBCS 彻底兼容 ASCII,多数状况下,咱们并不严格区分他们
03. LPCSTR p = "Hello,你好"// 意义同上
04.  
05. const WCHAR * p = L"Hello,你好"// 使用 UNICODE 字符集
06. LPCOLESTR p = L"Hello,你好"// 意义同上
07.  
08. // 若是预约义了_UNICODE,则表示使用UNICODE字符集;若是定义了_MBCS,则表示使用 MBCS
09. const TCHAR * p = _T("Hello,你好");
10. LPCTSTR p = _T("Hello,你好"); // 意义同上

在上面的例子中,T是很是有意思的一个符号(TCHAR、LPCTSTR、LPTSTR、_T()、_TEXT()...),它表示使用一种中间类型,既不明确表示使用 MBCS,也不明确表示使用 UNICODE。那到底使用哪一种字符集那?嘿嘿......编译的时候决定吧。设置条件编译的方式是:VC6中,"Project\Settings...\C/C++卡片 Preprocessor definitions" 中添加或修改 _MBCS、_UNICODE;VC.NET中,"项目\属性\配置属性\常规\字符集"而后用组合窗进行选择。使用 T 类型,是很是好的习惯,严重推荐!

4、BSTR

COM 中除了使用一些简单标准的数据类型外(注2),字符串类型须要特别重点地说明一下。还记得原则吗?COM 组件是运行在分布式环境中的。通俗地说,你不能直接把一个内存指针直接做为参数传递给COM函数。你想一想,系统须要把这块内存的内容传递到“地球另外一 边”的计算机上,所以,我至少须要知道你这块内存的尺寸吧?否则让我如何传递呀?传递多少字节呀?!而字符串又是很是经常使用的一种类型,所以 COM 设计者引入了 BASIC 中字符串类型的表示方式---BSTR。BSTR 实际上是一个指针类型,它的内存结构是:(输入程序片断 BSTR p = ::SysAllocString(L"Hello,你好");断点执行,而后观察p的内存)

图2、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 获得 BSTR
SetSysString() 从新申请 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。

5、各类字符串类型之间的转换

一、函数 WideCharToMultiByte(),转换 UNICODE 到 MBCS。使用范例:

01. LPCOLESTR lpw = L"Hello,你好";
02. size_t wLen = wcslen( lpw ) + 1;  // 宽字符字符长度,+1表示包含字符串结束符
03.  
04. int aLen=WideCharToMultiByte(  // 第一次调用,计算所需 MBCS 字符串字节长度
05. CP_ACP,
06. 0,
07. lpw,  // 宽字符串指针
08. wLen, // 字符长度
09. NULL,
10. 0,  // 参数0表示计算转换后的字符空间
11. NULL,
12. NULL);
13.  
14. LPSTR lpa = new char [aLen];
15.  
16. WideCharToMultiByte(
17. CP_ACP,
18. 0,
19. lpw,
20. wLen,
21. lpa,  // 转换后的字符串指针
22. aLen, // 给出空间大小
23. NULL,
24. NULL);
25.  
26. // 此时,lpa 中保存着转换后的 MBCS 字符串
27. ... ... ... ...
28. delete [] lpa;

二、函数 MultiByteToWideChar(),转换 MBCS 到 UNICODE。使用范例:

01. LPCSTR lpa = "Hello,你好";
02. size_t aLen = strlen( lpa ) + 1;
03.  
04. int wLen = MultiByteToWideChar(
05. CP_ACP,
06. 0,
07. lpa,
08. aLen,
09. NULL,
10. 0);
11.  
12. LPOLESTR lpw = new WCHAR [wLen];
13. MultiByteToWideChar(
14. CP_ACP,
15. 0,
16. lpa,
17. aLen,
18. lpw,
19. wLen);
20. ... ... ... ...
21. delete [] lpw;

三、使用 ATL 提供的转换宏。

A2BSTR OLE2A T2A W2A
A2COLE OLE2BSTR T2BSTR W2BSTR
A2CT OLE2CA T2CA W2CA
A2CW OLE2CT T2COLE W2COLE
A2OLE OLE2CW T2CW W2CT
A2T OLE2T T2OLE W2OLE
A2W OLE2W T2W W2T

上表中的宏函数,其实很是容易记忆:

2 好搞笑的缩写,to 的发音和 2 同样,因此借用来表示“转换为、转换到”的含义。
A ANSI 字符串,也就是 MBCS。
W、OLE 宽字符串,也就是 UNICODE。
T 中间类型T。若是定义了 _UNICODE,则T表示W;若是定义了 _MBCS,则T表示A
C const 的缩写

使用范例:

01. #include < atlconv.h >
02.  
03. void fun()
04. {
05. USES_CONVERSION;  // 只须要调用一次,就能够在函数中进行屡次转换
06.  
07. LPCTSTR lp = OLE2CT( L"Hello,你好") );
08. ... ... ... ...
09. // 不用显式释放 lp 的内存,由于
10. // 因为 ATL 转换宏使用栈做为临时空间,函数结束后会自动释放栈空间。
11. }

使用 ATL 转换宏,因为不用释放临时空间,因此使用起来很是方便。可是考虑到栈空间的尺寸(VC 默认2M),使用时要注意几点:

一、只适合于进行短字符串的转换;

二、不要试图在一个次数比较多的循环体内进行转换;

三、不要试图对字符型文件内容进行转换,由于文件尺寸通常状况下是比较大的;

四、对状况 2 和 3,要使用 MultiByteToWideChar() 和 WideCharToMultiByte();

6、VARIANT

C++、BASIC、Java、Pascal、Script......计算机语言多种多样,而它们各自又都有本身的数据类型,COM 产生目的,其中之一就是要跨语言(注3)。而 VARIANT 数据类型就具备跨语言的特性,同时它能够表示(存储)任意类型的数据。从C语言的角度来说,VARIANT 实际上是一个结构,结构中用一个域(vt)表示------该变量到底表示的是什么类型数据,同时真正的数据则存贮在 union 空间中。结构的定义太长了(虽然长,但其实很简单)你们去看 MSDN 的描述吧,这里给出如何使用的简单示例:

学生:我想用 VARIANT 表示一个4字节长的整数,如何作?

老师:VARIANT v; v.vt=VT_I4; v.lVal=100;

学生:我想用 VARIANT 表示布尔值“真”,如何作?

老师:VARIANT v; v.vt=VT_BOOL; v.boolVal=VARIANT_TRUE;

学生:这么麻烦?我能不能 v.boolVal=true; 这样写?

老师:不能够!由于 

类型 字节长度 假值 真值
bool 1(char) 0(false) 1(true)
BOOL 4(int) 0(FALSE) 1(TRUE)
VT_BOOL 2(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)

学生:~!@#$%^&*()......晕!

7、小结

以上所介绍的内容,是基本功,必须熟练掌握。先到这里吧,休息一下子......更多精彩内容,敬请关注《COM 组件设计与应用(四)》

 

注1:在后续的 ISupportErrorInfo 接口中介绍。

注2:常见的数据类型,请参考 IDL 文件的说明。(别着急,还没写那......嘿嘿)

注3:跨语言就是各类语言中都能使用COM组件。但啥时候能跨平台呢?

注4:CComVariant/COlevariant/_variant_t 请参看 MSDN。

注5:关于安全数组 SafeArray 的使用,在后续的文章中讨论。 

相关文章
相关标签/搜索