深入解析ATL(第二版ATL8.0)(2.12.2节)

第二章 字符串和文本

翻译:赖仪灵
出处:http://blog.csdn.net/laiyiling, http://www.cppblog.com/techlab
声明:版权归原作者拥有,请勿随意转载此翻译文档,保留一切权利。

字符串引入了多种不同的字符集。COM组件通常需要使用多个不同的字符集,并在不同的字符集之间相互转换。ATL提供了一些字符串转换类,需要时这些类会自动把字符集进行转换,如果不需要,它们什么也不做。

CComBSTR类是一个智能字符串类。它能完全根据BSTR字符串的含义进行分配、拷贝和释放字符串。CComBSTR实例可以用于大部分使用BSTR的地方,但不是所有地方都合适。

CString类是ATL新增的一个类,来源于MFC。此类可以处理分配、拷贝、格式化,以及很多的高级字符串处理特征。它可以管理ANSI和Unicode数据,以及在处理自动化方法参数时可以同BSTR字符串相互转换。通过CString类,你甚至可以控制、自定义类中字符串数据的内存管理方法。

2.1 字符串数据类型、转换类和辅助函数

2.1.1 回顾文本数据类型

在C++编程中处理文本数据类型是有点头痛的。主要的问题是文本数据类型不止一种,而是多种。这里使用文本数据类型(Text data type)指一般意义上的字符数组。通常,除了把字符数组当作文本字符串外,不同的操作系统和编程语言还给字符数组赋予了其他不同的含义(比如:NUL终止字符或者长度前缀)。

在选择文本数据类型时,我们需要做几个决定。第一:决定数组由什么字符类型组成。当我们向操作系统传递字符串时(比如文件名称),有的系统要求使用ANSI字符,有的系统要求使用EBCDID字符,有的既可以使用ANSI,也可以使用Unicode字符。有时还会使用特殊的字符集,比如多/双字节字符集(MBCS/DBCS),对它们的细节本书不做讨论。

第二:你必须考虑在程序中希望使用什么字符集来操作文本。源代码使用的字符集并不一定要与运行程序的操作系统默认字符集相同。当然,使用相同的字符集会更方便;但是程序和操作系统应该可以使用不同的字符集。我们必须“简单地”转换操作系统传入、传出的所有字符串。

第三:必须决定文本字符串的长度。一些语言如C和C++,以及一些操作系统如Windows 9X/NT/XP和UNIX,使用结束字符NUL来划定文本字符串的结尾。其他语言如Microsoft的Visual Basic解释器、Microsoft的JAVA虚拟机、Pascal更愿意用一个明确的前缀长度指明文本字符串的字符个数。

第四:在实际中,文本字符串带来的资源管理问题。文本字符串的长度一般不同。这样就很难在栈上给字符串分配内存,而且文本字符串也可能根本不适合在栈上。所以,文本字符串通常是动态分配的。理所当然,这就要求在最后释放文本字符串。资源管理引出了文本字符串所有者的概念。只有所有者才能释放字符串仅此一次。在不同的组件之间传递文本字符串时所有权变得非常重要。

更糟糕的是,两个COM对象可以位于两台不同的计算机上,而且他们运行着不同的操作系统,甚至文本字符串的字符集也是不同的。比如你可以用Visual Basic编写一个运行在Windows XP上的COM对象。我们可能把文本字符串传递到另一个用C++编写的运行在IBM主机上的COM对象。因此,我们需要一个标准的文本数据类型,使得不同环境的COM对象都能够理解。

COM使用OLECHAR字符数据类型。COM文本字符串是一个以NUL字符结尾的OLECHAR字符数组;指向字符串的指针是LPOLECHAR。作为规则,COM接口的文本字符串参数必须用LPOLESTR类型。当方法没有改变字符串时,参数应该是LPCOLESTR类型,也就是一个OLECHAR字符数组的常量指针。

大多数情况下(并不是所有),OLECHAR类型和我们编写代码时用的字符类型不同。有时候(并不是所有)OLECHAR类型和我们传递给操作系统的字符类型也不相同。这就意味着,根据不同的要求,有时候需要把一种文本字符串转为其他的字符集,有时候又不需要。

不幸的是,编译选项的改变(如Windows XP Unicode编译或者Windows CE编译)可能改变这种要求。因为,之前不需要转换的字符串现在可能需要转换,或者相反。我们当然不希望在改变编译选项的时候就重新编写全部的代码。因此,ATL提供了一些方便的字符串转换宏,实现文本字符串不同字符集之间的转换,并且能根据调用转换的要求作出相应变化。

Windows字符数据类型

现在,我们集中在Windows平台。基于Windows的COM组件通常混合使用下面的四种文本数据类型:
1)、Unicode、一种描述16位多语言字符编码的字符规范,也称“宽字符”。Windows NT/XP操作系统内部就是使用Unicode字符集。现代计算机上使用的所有字符,包括技术符号和特殊出版字符,都可以在Unicode中唯一描述。固定的字符大小可以简化使用国际化字符集时的编程工作。在C/C++语言中,用wchar_t数组描述宽字符的字符串,对应的字符串指针类型是wchar_t*。
2)、MBCS/DBCS、多字节字符集是一种混合宽度字符集,其中的一些字符由不止一个字节组成。一般,Windows 9X操作系统使用MBCS来描述字符。双字节字符集(DBCS)是一种特殊的多字节字符集类型。它包含一些单字节的字节和一些描述特殊区域的符号的双字节字符。比如日、中文和韩文。

在C/C++语言中,我们使用unsigned char类型的数组表示MBCS/DBCS字符串,对应的指针类型是unsigned char*。有时候,一个字符的长度是一个unsigned char,有时候不止一个。它使得字符串的处理很有意思,特别是试图通过字符串进行回退工作时。在Visual C++中,MBCS始终等于DBCS。现在字符集还不支持超过两个字节的字符。

3)、ANSI、在英语中,我们可以描述所有的字符,包括一些西欧语言,仅仅使用8比特。支持这种语言的Windows版本使用了一种退化的MBCS版本,称为Microsoft Windows ANSI字符集,它当中没有多字节字符存在。Microsoft Windows ANSI字符集是ISO 8859/X的核心加上一些附加字符,最初是基于ANSI草案标准的。

ANSI字符集把字母和数字按照ASCII同样的方式进行映射。但是,ANSI不支持控制字符,不能映射一些符号,包括重音字母,都没有映射到标准ASCII。所有的Windows字体都是定义在ANSI字符集。为了对应,ANSI也称为单字节字符集(SBCS)。

在C/C++中,我们用char数组表示ANSI字符串,对应的指针类型的char*。每个字符都总是一个char长度。Visual C++中char默认为signed char。因为MBCS字符是unsigned而ANSI字符默认是signed字符,与使用MBCS字符相比,使用ANSI字符的表达式可以进行不同的求值。

4)、TCHAR/_TCHAR、它是Microsoft一种特殊的普通文本数据类型,可以根据编译选项映射为Unicode字符、MBCS字符、ANSI字符。使用这种字符类型进行一般的编码可以编译为上述三种字符集任何一种。它简化了国际化市场的代码开发。C运行库定义了_TCHAR类型,而Windows操作系统则定义为TCHAR类型,它们的相同的。

Microsoft特有的C运行库头文件tchar.h,定义了特殊文本数据类型_TCHAR。ANSI C/C++编译器依从了实现器的命名需求:以下划线做前缀。当我们没有定义__STDC__预编译符号时(Visual C++默认没有定义此符号),表示我们不需要ANSI顺从命名需求。在这种情况下,如果特殊文本数据类型的名称没有定义,tchar.h文件也会定义了另外一个符号TCHAR作为其别名。winnt.h也是Microsoft特有的Win32操作系统头文件,定义特殊文本数据类型为TCHAR。此文件是操作系统特有的,因此这个符号不需要使用下划线前缀。

Win32 APIs和字符串

所有需要字符串参数的Win32 API都有两个版本:一个需要Unicode参数,另一个需要MBCS参数。在不能使用MBCS的Windows版本中,MBCS版本的API使用ANSI参数。比如,SetWindowText函数实际上是不存在的。实际上有两个函数:SetWindowTextW,需要Unicode字符串参数;SetWindowTextA,需要MBCS/ANSI字符串参数。

在Windows NT/2000/XP操作系统内部实际上只使用Unicode字符串。因此,当我们在系统Windows NT/2000/XP上调用SetWindowTextA时,函数会把指定的字符串转换为Unicode然后再调用SetWindowTextW。Windows 9X操作系统不能直接支持Unicode。在操作系统Windows 9X上,当SetWindowTextW返回错误时,SetWindowTextA就会被调用工作。微软的MSLU库提供了Win9X上几乎所有Unicode函数的实现。

现在,我们就面临一个艰难的决策。我们可以用Unicode编码字符串编写高效的组件,只能在Windows 2000以上而不能在Windows 9X运行。可以用Unicode字符串的MSLU编码,可以运行在两种平台,只是在Windows 9X下会有性能影响。可以用MBCS/ANSI编码字符串编写更通用的组件,可以运行在两种平台,只是在Windows 2000上不会有性能提高。做为选择,我们不需要直接回答,而是在编程源代码的过程中来决定编译时支持什么类型的字符集。

通过一些编码标准和预处理方法,使我们编码时好像只有单个API函数 SetWindowText,而它需要TCHAR类型的字符串参数。我们可以在编译时指定想要编译什么类型的组件。比如,编码调用SetWindowText函数同时指定一个TCHAR缓冲。当把组件编译为Unicode时,调用SetWindowTextW,参数是wchar_t缓冲。当把组件编译为MBCS/ANSI时,调用的是SetWindowTextA,参数是char缓冲。

当编写基于Windows的COM组件时,我们一般应该使用TCHAR字符类型来描述组件内部使用的字符。此外,与操作系统交互的所有字符也都应该使用TCHAR。同样地,应该使用TEXT或者__TEXT宏包围所有的文字字符或字符串。

在tchar.h中定义了功能相同的宏_T、__T、_TEXT,这些宏把字符或者字符串编译为通用文本字符或者文字。winnt.h也定义了功能相同的宏TEXT、__TEXT,它们仍然与_T、__T和_TEXT一样。(这里仅仅是用五种相同的方法完成同样的功能。)在本章的例子使用__TEXT,因为它是在winnt.h中定义。我更愿意使用_T,因为它使源代码更简洁。

如果编码时不知道操作系统,那么我们更倾向于包含tchar.h,使用_TCHAR通用文本数据类型,因为这样可以稍微减少和操作系统的捆绑程度。但是,我们讨论的是在编译时、给特定的操作系统版本用最优化的文本处理方法建立组件。这就要求我们使用在winnt.h中定义的TCHAR数据类型。而且,TCHAR没有_TCHAR那么刺眼,也更容易输入。大多数代码已经通过windows.h隐式的包含了winnt.h,而tchar.h是必须显式包含的。各种各样的优点都支持使用TCHAR,因此在本书的例子中,使用它作为通用文本数据类型。

这就表示我们可以为不同的市场需求或者效率原因编译特殊版本的组件。这些类型和宏定义在winnt.h头文件。

在操作TCHAR类型的字符串时,我们也必须使用不同字符串运行库函数集合。我们熟悉的函数strlen、strcpy等等只能处理char字符类型。稍微陌生的函数wcslen、wcscpy等函数操作wchar_t类型字符。而且,我们更为陌生的_mbslen、_mbscpy等函数能处理多字节字符类型。因为TCHAR有时是wchar_t类型,有时是保存ANSI自负的char类型,有时是保存多字节字符(通常是unsigned)的char类型。我们需要一个相应的能处理TCHAR字符类型的函数集合。

在tchar.h头文件中定义了很多有用的通用文本映射字符串操作函数。这些函数使用TCHAR类型做参数,因此这些函数都用_tcs作名称前缀(_t字符集)。比如,_tcslen与C运行库的函数strlen相同。_tcslen函数使用TCHAR参数,而strlen函数用char字符做参数。

用预处理控制通用文本映射

两个预处理符号和宏控制了TCHAR数据类型到应用程序内部实际使用类型的映射关系。

UNICODE/_UNICODE:Windows操作系统API的头文件使用UNICOD预处理符号。C/C++运行库头文件使用_UNICODE符号。特别的是,我们可以同时定义这两个符合,也可以一个也不定义。当使用_UNICODE符号编译时,tchar.h把TCHAR映射为wchar_t字符。每个字符的前缀宏_T、__T、_TEXT,或者字符串字母的前的大写字母L(分别创建Unicode字符或字母)。当使用wnnt.h定义的UNICODE符号编译时,TCHAR字符被映射为wchar_t字符。_TEXT和__TEXT宏作为字符串前缀或者字符串字母前的大写字母L(分别创建Unicode字符或字母)。_tcsXXX系列函数也被映射到相应的_wcsXXX函数。

_MBCS:当使用定义的_MBCS符号编译时,所有的TCHAR字符都映射为char字符,预处理器会忽略所有的_T和__TEXT宏变量,保持字符或者字母不变(分别创建ANSI字符或字母)。_tcsXX函数被映射到相应的strXXX函数。

我们可以使用通用文本数据类型和函数编写通用文本兼容的代码。下面是一个反转和连接通用文本字符串数据的例子:

TCHAR *reversedString, *sourceString, *completeString;
reversedString = _tcsrev (sourceString);
completeString = _tcscat (reversedString, __TEXT("suffix"));

当我们在没有定义任何预处理符号时编译上面的代码,预处理会产生下面的输出:

char *reversedString, *sourceString, *completeString;
reversedString = _strrev (sourceString);
completeString = strcat (reversedString, "suffix");

当我们定义_UNICODE预处理符号再编译时,预处理会产生如下的输出:

wchar_t *reversedString, *sourceString, *completeString;
reversedString = _wcsrev (sourceString);
completeString = wcscat (reversedString, L"suffix");

当我们定义_MBCS预处理符号再编译时,预处理会产生如下的输出:

char *reversedString, *sourceString, *completeString;
reversedString = _mbsrev (sourceString);
completeString = _mbscat (reversedString, "suffix");

COM字符数据类型Win16

COM使用两种字符数据类型:

OLECHAR:我们在操作系统上编译代码时COM使用此种数据类型。对于Win32操作系统,它是wchar_t字符数据类型,对Win16类型,是char字符类型。对于Mac OS是char字符类型。对于Solaris OS是wchar_t字符类型。对于仍然未知的操作系统,也许有人知道。现在就让我们假想有一种抽象数据类型称为OLECHAR。COM使用它,不取决于它映射到何种特殊的内部数据类型。

BSTR:一些COM组件使用的特殊字符串类型。BSTR是一种带长度前缀、带许多特殊语义的OLECHAR字符数组。

现在,让我们看看稍微复杂点的情况:我们希望编写能在编译时选择使用字符类型的代码。因此,我们在内部进行严格的TCHAR字符串操作。如果希望调用COM方法,并传递一些字符串。我们必须根据方法的参数给其传递OLECHAR或者BSTR字符串。我们的组件所使用的字符串也许会,也许不会是正确的字符格式,这取决于编译选项。这些工作都是由超级宏完成。

ATL字符串转换类

ATL提供了许多的字符串转换类,必要时在我们前述的各种字符类型之间转换。如果编译选项使得源、目的字符类型相同,那么这些类不执行任何转换。在atlconv.h文件中实现了几种不同的转换逻辑,头文件也使用typedef和预处理#define语句,以使我们在使用这些转换类时能更加方便。这些类命名时都使用了各种字符数据类型的缩写:

T表示一个指向Win32的TCHAR字符的指针,典型的LPTSTR参数。
W表示一个指向Unicode的wchar_t字符的指针,典型的LPWSTR参数。
A表示一个指向MBCS/ANSI的char字符的指针,典型的LPSTR参数。
OLE表示一个指向COM OLECHAR字符的指针,典型的LPOLESTR参数。
C表示C/C++的const修饰符。

所有的类名称都采用统一的C<源格式缩写>2<目标缩写>格式。比如,CA2W类把LPSTR转为LPWSTR。当在名称当中有C(不包括第一C它代表类)时,就给缩写添加了一个const修饰符。比如CT2CW类把LPTSTR转为LPCWSTR。

这些类的实际行为取决于我们定义的预处理符号(参考表2.1)。注意ATL转换类和宏把OLE和W等同对待。

表2.1 字符集预处理符号
定义的预处理符号 T转为… OLE转为…
None A W
_UNICODE W W

表2.2列出了ATL的字符转换宏。


如你所见,表2.2没有列出BSTR字符转换类。本章的下一节会介绍CComBSTR类,它实现了一种BSTR类型转换的首选机制。

查看atlconv.h头文件的内容,你就会发现一些类的定义实际上调用了六个比较小的类。比如,当定义了_UNICODE时,CT2A变为CW2A,而CW2A本身是CW2AEX模板类的typedef定义。类型定义仅仅应用到CW2AEX的模板参数。另外,前面所有类名总是把OLE映射为W,因此COLE2T变为CW2T,在Unicode编译是它被定义为CW2W。因为CW2W的源、目标类型相同,因此实际上并不需要进行转换。最后,唯一的六个模板类是:CA2AEX、CA2CAEX、CA2WEX、CW2AEX、CW2CWEX和CW2WEX。只有CA2WEX和CW2AEX有不同的源和目的类型,因此实际上只有这两个类完成实际的工作。因此,我们在表2.2列出的所有转换类最后都会被扩展为这唯一的两个类。这两个类的定义和实现原理相同,因为我们明白了CA2WEX就能明白另一个类的原理。

template< int t_nBufferLength = 128 >
class CA2WEX {
CA2WEX( LPCSTR psz );
CA2WEX( LPCSTR psz, UINT nCodePage );
...
public:
LPWSTR m_psz;
wchar_t m_szBuffer[t_nBufferLength];
...
};

类的定义实际上非常简单。模板参数指定了保存字符串数据的固定静态缓存区的大小。这表示大部分的字符串转换操作都不需要再分配动态内存就能完成。如果字符串转换的请求超过了模板参数的大小,CA2WEX使用malloc分配其他的空间。

CA2WEX提供了两个构造函数。一个接受LPCSTR参数并用Win32 API函数MultiByteToWideChar执行转换工作。默认时,类使用ANSI代码页作为当前本地线程执行转换。第二个构造函数允许指定预备的代码页管理如何执行转换。这个值被直接传递给MultiByteToWideChar,可以参考在线文档,了解关于各种Win32字符转换函数接受的代码页的细节信息。

使用这个转换类的最简单方法就是接受缓存区的默认大小参数。因此,ATL提供了简单的typedef定义来简化它:

typedef CA2WEX<> CA2W;

使用这个转换类,我们只需要像下面这样写简单的代码:
void PutName (LPCWSTR lpwszName);

void PutName(LPCSTR lpsz) {
PutName(CA2W(lpsz));
}

在实际运用中,另外两种情况也比较常见:

1.接受通用文本字符串,传递给需要OLESTR为输入参数的方法。
2.接受OLESTR,传递给需要通用文本字符串为输入参数的方法。

转换类也可以很容易的应付这两种情况:

void PutAddress(LPOLESTR lpszAddress);

void RegisterAddress(LPTSTR lpsz) {
PutAddress(CT2OLE(lpsz));
}

void PutNickName(LPTSTR lpszName);

void RegisterAddress(LPOLESTR lpsz) {
PutNickName(COLE2T(lpsz));
}

内存管理要点

虽然这些转换类是非常的方便,但是如果使用不正确我们就可能陷入到它的一些缺陷当中。转换类为这些转换后的字符串分配内存,在类的析构函数里释放。这对我们非常有用,我们就不需要在担心内存分配的问题了。但是,这也意味着像下面这样的代码会发生崩溃:

LPOLESTR ConvertString(LPTSTR lpsz) {
return CT2OLE(lpsz);
}

上面我们返回了一个指向调用函数栈的指针(它在函数返回前失效)。如果字符串是临时的,或者指针指向的是栈的数组,它们都将在函数返回前被释放。

最糟糕的是:根据我们宏选择的不同,代码也许会工作正常,也可能会在第一次从ANSI转换到UNICODE时发生崩溃(通常在发布的前两天)。为了避免这种情况,当我们在不止一个语句需要时,应该确保把转换后的字符串拷贝到另外的缓冲(或者使用字符串类)。

ATL字符串辅助函数

有时如果希望拷贝一个OLECHAR字符的字符串。碰巧你也知道OLECHAR字符在Win32操作系统上就是宽字符。当编写Win32版本的组件时,可能需要调用Win32操作系统函数lstrcpyW,它实现拷贝宽字符。不幸的是,Windows NT/2000支持Unicode,实现了lstrcpyW,但是Windows 95不支持。使用了lstrcpyW函数的组件在Win95上不能正常运行。

作为代替lstrcpyW,可以使用ATL的字符串辅助函数ocscpy拷贝OLECHAR字符串。它能在Windows NT/2000和Windows 95上正常工作。ATL字符串辅助函数ocslen返回OLECHAR字符串的长度。尽管它替代的lstrlenW函数可以在两个系统上运行,但是为了对称考虑,我们仍然使用ocslen函数。

OLECHAR* ocscpy(LPOLESTR dest, LPOLESTR src);
size_t ocslen(LPCOLESTR s);

同样的,Win32的CharNextW操作系统函数也不能在Win95上运行。因此,ATL提供CharNext0字符串辅助函数,以实现OLECHAR*字符串的递增,返回指向下一个字符。它不会递增超过NUL终止字符的指针。

LPOLESTR CharNext0(LPCOLESTR lp);

ATL字符串转换宏

前面讨论的字符串转换类是在ATL 7引入。ATL 3使用了一系列的宏代替实现。事实上,这些宏仍然在ATL基础代码中使用。比如,下面就是atlctl.h文件的部分内容:

STDMETHOD(Help)(LPCOLESTR pszHelpDir) {
T* pT = static_cast<T*>(this);
USES_CONVERSION;
ATLTRACE(atlTraceControls,2,
_T("IPropertyPageImpl::Help/n"));
CComBSTR szFullFileName(pszHelpDir);
CComHeapPtr<OLECHAR>
pszFileName(LoadStringHelper(pT->m_dwHelpFileID));
if (pszFileName == NULL)
return E_OUTOFMEMORY;
szFullFileName.Append(OLESTR("//"));
szFullFileName.Append(pszFileName);
WinHelp(pT->m_hWnd, OLE2CT(szFullFileName),
HELP_CONTEXTPOPUP, NULL);
return S_OK;
}

这些宏与转换类非常相似,除了开始的字母C是宏名称非类名。因此,把tchar转换为olechar应使用T20LE(s)。

在转换宏和类之间主要有两个区别。第一,宏需要一些本地变量才能工作;USES_CONVERSION宏在使用转换宏的所有地方都需要(它声明本地变量)。第二个不同点是转换缓存的位置。

在转换类中,如果缓存不大,则是以成员变量存储在栈上,如果太大则是存储在堆。而转换宏总是使用栈存储。它们调用运行时函数_alloca来在本地栈上分配额外空间。

尽管_alloca速度很快,仍有一些严重的确定。栈空间会在函数结束后释放,因此如果在循环中使用转换,程序可能会因栈空间用尽而崩溃。







2.2 智能的BSTR封装类CComBSTR

2.2.1 COM字符串数据类型回顾:BSTR

COM是一种语言独立,硬件结构独立的模型。因此,它需要语言独立、硬件结构独立的文本数据类型。COM定义了一种特殊文本数据类型OLECHAR,用来描述COM在特殊平台上使用的文本数据。在多数平台,包括所有的Win32 Windows平台,OLECHAR数据类型是由wchar_t数据类型用typedef定义得来。也就是说,在大多数平台,COM文本数据类型与C/C++的宽字符数据类型,包括Unicode字符是相等的。在另一些平台,比如在16位的Windows操作系统,OLECHAR是标准C数据类型,包括ANSI字符,用typedef定义。一般,我们应该把所有的COM接口字符串参数定义位OLECHAR*类型。

COM也定义了另一种称为BSTR的文本数据类型。BSTR是一个带长度前缀的OLECHAR类型字符串。从效率上考虑,大多数的解释环境更愿意使用长度前缀的字符串。比如,带长度前缀的字符串不需要耗时进行扫描查找NUL结束字符来判断字符串的长度。事实上,NUL结束字符是一种语言特有概念,最开始属于C/C++语言特有的。Microsoft Visual Basic解释器、Microsoft Java虚拟机和大多数的脚本语言,比如VBScript和Jscript内部都使用BSTR表示字符串。

因此,当我们从一个C/C++定义的接口组件中的某个方法传递或者接收字符串时,通常应该使用OLECHAR*数据类型。但是,如果需要使用其他语言定义的接口,一般来说字符串参数应该是BSTR数据类型。关于BSTR数据类型有很多文档化的、难懂的语义说明,使我们使用BSTR变得很繁重,对C++开发人员来说也很容易发生错误。

BSTR类型有如下的属性:

1、BSTR是指针,指向一个带长度前缀的OLECHAR字符数组。
2、BSTR是指针数据类型。它指向数组的第一个字符。长度是以整数存储在数组中紧接第一个字符前面的位置。
3、字符数组是以NUL字符结尾的。
4、前缀长度以字节位单位,不是字符,不包括结束字符NUL。
5、在字符数组内部可能包括有效的NUL字符。
6、BSTR必须使用SysAllocString和SysFreeString函数族进行分配和释放。
7、NULL的BSTR指针表示空字符串
8、BSTR是非引用计数的,因此,两次引用同一字符串的内容必须指向两个单独的BSTR。换句话说,拷贝一个BSTR暗示字符串的复制操作,而不是简单的拷贝指针。

正因为这些专用语义繁多,把这些细节都封装成一个可重用类将非常有用。ATL就提供了这样的类:CComBSTR。
原文地址:https://www.cnblogs.com/hehe520/p/6330104.html