这两天被BSTR折腾了不行,特从网上搜来,做资料查阅
字符串是一个文本类型数据(字符)的数组,我们要处理一个字符串需要考虑这么几点:
第一, 字符类型。不同的操作系统,或者统一操作系统内会有不同的字符类型比如说,, char, unsigned char, wchar类型。
第二, 和字符集不同。如UNICODE, SBCS(ANSI), MBCS, DBCS等不同的字符集。而且统一字符集下会有不同代码页(CODE PAGE)。
第三, 如何决定字符串长度。如有的语言把字符串中的NUL作为字符串结尾,而另外一些则在字符串首位标定该字符串的长度。
第四, 字符串相关的资源管理问题。由于长度一般不同,所以经常会为字符串动态分配内。而这些内存的收回由于所有权不同而不同,不同的语言处理方式也不一样。
所有这些不同会使得字符串的处理也不尽相同。
Windows平台内本身就有好几种字符集,相应用不同的字符类型来表示。
n UNICODE wchar, wchar*
16位的多语言字符集。
n MBCS/DBCS unsigned char, unsigned char*
混合长度的字符集。有的字符用一个字节表示,而有些字符用几个字节表示。VC中他们是等效的。
n ANSI(SBCS) char, char*
面对这么做纷繁复杂的情况,我们比较头疼。希望我们在程序内部用一种统一的字符集进行编码。同时,为了让软件发布在不同操作系统下,我们最好能在编译的时候来决定具体使用哪一种编码。
根据这一要求Microsoft定义了一种统一的数据类型TCHAR/_TCHAR来编写统一的代码。我们在编码的时候统一使用这种数据类型,然后在编译的时候来更具编译选项来确定目标代码所用的字符集。为此,Microsoft在C运行库里添加了tchar.h以支持TCHAR及其处理函数。WIN32环境内的Microsoft在winnt.h中也定义了TCHAR及其处理函数。同时,有相应的一整套TCHAR字符串处理函数。
另外对于每个需要字符串的win32 API都有两个版本xxxxA和xxxxW,他们会通过编译宏来进行切换。
使用方法:
确认是否包含了WINNT.H。一般Wizard生成的文件中都已经包含了该文件;
在程序内严格使用TCHAR/LPTSTR/LPCTSTR来表示字符/字符串;
每个文字或者字符串都要用宏_TEXT/__TEXT/_T/__T包起来;
处理字符串的时候用_tcsXXX(同strXXX, wcsXXX相对应)函数集。
使用两个预处理符号进行编译_UNICODE/UNICODE, _MBCS来确定TCHAR和内在字符类型之间的映射。UNICODE: TCHAR->wchar, _MBCS: TCHAR->char
如果编译时定义了_MBCS符号,则所有的TCHAR字符被映射成char字符,并且预处理器会去掉所有的_T和_TEXT宏的变种,不改变字符或字符串(分别创建一个MBCS字符或字符串)
若编译时未定义任何与处理器符号,则所有的TCHAR字符会被映射成char字符,并且预处理器会去掉所有的_T和_TEXT宏的变种,不改变字符或字符串(分别创建一个ANSI字符或字符串)
COM字符串类型
COM是一个语言中立硬件结构中立的模型。因此,它需要一个语言中立、硬件结构中立的文本数据类型。由于不同的平台使用的字符类型不同,所以很难有一种类型能使和COM的需要。所以COM就定义了一种文本数据类型——OLECHAR。在大多数平台上,包括WIN32平台都把OLECHAR解释成wchar,而在一些16位操作系统下,如win95, Macintonsh OS, OLECHAR会被解释成CHAR。
当我们使用C/C++定义接口的方法传递字符串的时候或者从接口接受一个字符串的时候,经常会用OLECHAR*,在构建一个OLECHAR*的时候需要用宏OLESTR()把字符串包起来。但是如果我们使用其它语言定义的接口时,字符串经常是BSTR类型。
对于BSTR,他实际上就是OLECHAR*,不过他还有一些特殊的含义
n BSTR指向的数组的前缀是该字符串的长度。
n 字符数组是以NUL字符作为结尾。
n 纪录的长度是字节为单位,而不是字符,并且不包括NUL终止符。
n 字符数组内部可以包含嵌入的NUL字符。
n 他必须用SysAllocString和SysFreeString函数来分配和释放。
n NULL BSTR意味着一个空字符串。
n 复制BSTR意味着制作字符串的一个拷贝,不是简单的复制指针。
注意:BSTR的前缀是BSTR指向数组的前边四个字节。这段内存会被::SysStringxxxx管理。所以如果把BSTR当作OLECHAR*有时是可以用的,因为他们指向一个字符串数组。但是,OLECHAR*会以NUL为结尾,会把内嵌NUL的BSTR截断。反过来,我们不能把OLECHAR*看作BSTR,虽然他们都指向一个字符数组,但是他前边的四个字节的内容是无效的,也就是BSTR的长度无效。这个时候让::SysStringxxxx来管理他的话会出问题。
字符串的转换
即使我们在程序内部都进行了统一编码。但是,如果涉及到对第三方程序的应用,不可避免的要引起字符处理的混乱。我们希望能够把这些涉外的字符串和程序内部的字符串进行转换。这样可以维护程序的一致性。
ATL提供了一系列的转换宏,在必要的时候他可以在前边提到的字符类型之间进行转换。这些宏的命名使用“<源类型缩写>2<目标类型缩写>”的形式。具体缩写如下所示:
T TCHAR类型指针——LPTSTR
W Unicode wchar类型指针——LPWSTR
A MBCS/ANSI char类型指针——LPSTR
OLE COM OLECHAR类型的指针——LPOLESTR
BSTR COM BSTR类型字符串。
C C/C++中的const修饰符
在宏转换的时候,不可避免的要产生一些临时变量(如字符串长度),这些变量会在栈上分配。如果我们在一个循环内进行这些转换,就会有很多临时变量产生而不能得到及时的释放。所以我们定义一个宏USES_CONVERSION(只需要一次)来纪录这些临时变量,从而大大节约了内存。
辅助函数
当我们在编写一个WIN32的组件时执行了一个操作——复制OLECHAR字符串。由于在WIN32系统上OLECHAR实际上是wchar所以,在WINNT上是通过lstrcpyW来实现的。但是如果把这个组件放在win95上,由于他没有实现lstrcpyW,所以程序调用就会出问题。同样涉及到OLECHAR字符串的长度获取以及字符便利等等。在这里ATL提供了
OLECHAR* ocscpy (LPOLESTR dest, LPCOLESTR src);
size_t ocslen(LPOLESTR s);
LPOLESTR CharNextO(LPCOLESTR lp);
三个函数,使得这些操作在NT和95下都能正确运行。
心得
对于COM内部统一使用TCHAR/LPTSTR来说是很容易理解的。需要明确的一点就是TCHAR的映射问题。我们建立组件的时候应根据特定的操作系统来使用最优的字符串处理方法。对于WIN NT我们一般选用UNICODE编译选项,但是编译结果却不能在WIN9X下正确运行(WIN9X不支持UNICODE)。所以在不涉及到OLECHAR的情况下为了使的构建出来的组件即能在NT下运行也能在WIN9X下运行我们一般选用_MBCS编译选项。
但是,如果涉及到了OLECHAR,问题就比较复杂了。因为在一般情况OLECHAR都当作wchar,如果我们无法回避对OLECHAR*的直接操作,就必须使用上边提到的一些辅助函数。如果这些辅助函数不够用,从而使用了wchar的相应函数,那么在win9x下就会出现运行错误。
以上说的都是win32平台上的应用。如果这个组件要在16位操作系统下运行,那么就需要注意OLECHAR的映射。由于在16位操作系统下一个OLECHAR就是一个char,所以在WIN32平台下如果没有添加_OLE2ANSI选项,编译出来的组件(有涉及到OLECHAR的接口)就根本不能用,反过来,在16操作系统下编译出来的组件也不能在32位操作系统下运行。这些问题是的我们发布一个“万能”组建变得很困难。所以,还是根据不同的目标操作系统进行编译、发布。这样反而利用了这些操作系统的优点。 还有一个问题就是OLECHAR*,和BSTR的关系了。我们前边也介绍了他们的区别和联系。我想组件是一个语言中立的结构,所以尽量能让他不被一种语言限定。而OLECHAR*作为字符串只能C/C++的语言环境下使用,限制了组件的应用范围。故而还是尽量使用BSTR进行字符串的操作。
从前边的叙述中可以看出BSTR的含义很丰富,限制也比较多。这样我们在使用过程中就得很小心。实际编程过程中,BSTR的应用又很频繁。为了避免一些不必要的麻烦,ATL提供了一个类把他包装起来,使得我们不必过多的关心他的细节。
CComBSTR把一个BSTR作为成员变量,通过各种方法对他进行维护。
以下是该类的具体内容及其注意事项:
构造函数和析构函数
Ø CComBSTR(); m_str = NULL;
BSTR为NULL的时候,有时候同于一个指向控字符串””的指针。如VB中IF “”=P的返回值会为真。但是,很多BSTR相关的WIN32 API不一定会有此认同,他们要求被操作的BSTR必须不为空。如SysStringLen
Ø CComBSTR(LPOLESTR pSrc);
不停的复制字符直到NUL终止符。
Ø CComBSTR(int size, LPOLESTR pSrc);
首先创建一个size大小的BSTR,然后copy pSrc中复制包括NUL在内的任何字符,然后加一个NUL作后缀。如果pSrc为空,则构造一个大小为size的没有初始化的BSTR。
Ø CComBSTR(int size);
则构造一个大小为size的没有初始化的BSTR
Ø CComBSTR(const CComBSTR src)
调用了对象的Copy方法。
由于Copy是更具自己的长度构造的一个拷贝,所以能够正确复制(包括内嵌NUL字符)
Ø #ifndef OLE2ANSI
CComBSTR(LPCSTR pSrc);
CComBSTR(int size, LPCSTR pSrc);
#endif
这两个函数同上边的两个函数基本一致,不同的是,他们不能有内嵌NUL。而且当定义了OLE2ANSI的时候LPCSTR = LPOLESTR,下边的函数就不能再有了。
Ø CComBSTR(REFGUID src)
生成一个包含GUID的字符串。
析构函数~ CComBSTR() {::SysFreeString(m_str)}
应为他会自动释放BSTR,所以在处理异常处理的时候非常有用。
注意,内嵌NUL的字符串。不是说只有按照BSTR的含义去理解LPOLESTR才能得到内嵌NUL的字符串。因为LPOLESTR实际上是一个OLECHAR的数组,他的长度有数组的性质来决定,所以在NUL后边还可以有其他内容。只是我们把它仅仅理解城LPOLESTR的时候,他的有效长度仅仅是第一个NUL之前的长度。所以会出现CComBSTR(int size, LPOLESTR pSrc)来构造内嵌NUL的字符串。但是这样的数组绝对不是BSTR,因为BSTR的首字节记录了该串的长度。
初始化
CComBSTR& operator=(const CComBSTR& src)
CComBSTR& operator=(LPCOLESTR pSrc)
#ifndef OLE2ANSI
CComBSTR& operator=(LPCSTR pSrc)
#endif
bool LoadString(HINSTANCE hInst, UINT nID)
bool LoadString(UINT nID)
这三个初始化函数都是先把m_str原来所指的BSTR释放,然后根据参数进行初始化。第一个初始化函数能够准确的(包含NUL字符)制造一个副本。而后边两个只能按照以NUL结尾的字符串进行初始化。第四个是根据hInst资源句柄去加载字符串,而最后一个其实也一样,只不过资源句柄是一个全局变量_pModule->m_hInstResource,_pModule在服务器对象初始化的时候确定(在DllMain或WinMain中调用CComModule::Init)。
操作符
Ø operator BSTR() const
当显式地或者隐式地把一个CComBSTR对象强制转换成一个BSTR对象的时候就会调用这个方法
Ø BSTR* operator&()
注意这个方法返回的是内部变量的地址,如果我们在改变这个变量之前没有释放原来的字符串会导致内存泄漏。但是,用它来接收一个BSTR的时候很有用,我们这样可以直接用CComBSTR来管理BSTR的生命周期。
HRESULT get_Name(/*out*/BSTR* pName);
CComBSTR bstrName;
Get_Name(bstrName);
Ø HRESULT CopyTo(BSTR* pbstr)
精确地构造一个副本,用它来给调用这构造返回值比较合适。注意,我们需要显式地式方pbstr。
Ø BSTR Detach()
他返回了m_str,而且把自己清空,如果利用Copy的话就会构造一个副本,效率比较低。CComBSTR析构的时候不会做任何事情。但是,我们需要显式地式方它的返回值。
Ø void Attach(BSTR src)
接收一个BSTR用CComBSTR来维护他的生命周期。如果把一个BSTR附加在一个非空的CComBSTR上,就会产生内存泄漏。
Ø void Empty()
释放内存并且清空CComBSTR。
Ø 一个非空CComBSTR调用Attach之前一般调用Empty()。
连接字符
HRESULT Append() 4个
HRESULT AppendBSTR(BSTR p)
CComBSTR& operator+=(const CComBSTR& bstrSrc)
当参数是BSTR或者CComBSTR的时候会附加整个BSTR,但如果是LPCOLECHAR或LPCSTR会添以NUL结尾的字符串。
字符大小写转换
ToLower ToUpper。实现的时候总是OLE2T->XXX->T2OLE。所以一般是由损失的,因为专成TCHAR字符串的时候会议NUL终止。内嵌NUL的字符串会出问题。
比较操作
bool operator!() const
bool operator<(BSTR bstrSrc) const
bool operator<(LPCSTR pszSrc) const
bool operator==(BSTR bstrSrc) const
bool operator==(LPCSTR pszSrc) const
在比较参数为BSTR的时候,内部会用wcscmp来比较,所以在字符串转换的时候会因为NUL造成损失。
对永久性的支持。
HRESULT WriteToStream(IStream* pStream)
他把表示BSTR长度(字节为单位SysStringByteLen)的ULONG计数值写入流中。紧接着写入BSTR。
HRESULT ReadFromStream(IStream* pStream)
他是上一过程的相反,但是如果一个非空的CComBSTR调用该函数是事先不会清空,会产生内存泄漏。
心得:
首先就是OLECHAR*和BSTR的区别和联系。编译器认定他们是相同的,但是他们确确实实不同。使用的时候应该非常注意才对。
另外就是涉及到内嵌NUL的字符串的处理。CComBSTR做的并不完美。
还有就是内存问题。自己时刻注意什么时候应该释放内存。
转自:http://zhikangs.spaces.live.com/blog/