一 BSTR及CComBSTR
MSDN文档中说,BSTR是四字节长度前缀的,NULL结尾的宽字符串,称之为VB字符串或BINARY字符串。
依次查找几个头文件,不难发现,BSTR被typedef成OLECHAR *,而OLECHAR被typedef成WCHAR(未定义OLE2ANSI预处理宏)或char (定义OLE2ANSI预处理宏);继续顺藤摸瓜,WCHAR被typedef成wchar_t,而wchar_t被typedef成unsigned short。
最终的结果就是,BSTR被typedef成unsigned short *(未定义OLE2ANSI预处理宏)或char *(定义OLE2ANSI预处理宏)。
原来BSTR只是一个指针。
本文不考虑定义预处理宏OLE2ANSI的情况。
在COM编程中,凡是使用BSTR的地方,我们可以使用类CComBSTR来代替BSTR。CComBSTR封装了BSTR,通过各种重载构造函数和操作符重载,仅仅使用对象定义,type cast等简单语句,就可以实现BSTR与各种类型字符串,包括LPSTR,LPOLESTR之间的转换,赋值,连接,比较等操作。
二 ATL字符串转换宏
除了CComBSTR类支持各种字符串类型到BSTR的转换以外,ATL还有一组字符串转换宏,可以方便的进行各种类型字符串之间的转换。这些宏有统一的形式:X2[C]Y或Z2BSTR。其中X,Y,Z可以为A,W,T,OLE中的任一种,X与Y不相同;C表示转换的目标类型为const——因此,这样的组合方式总共有4x3x2+4=28种。
根据预处理宏_UNICODE定义与否,T被替代成W或A,所以这28个转换宏最终可以归结为两类:一类是A和W之间的转换,另一类是A,W到BSTR的转换。
下面看看第一类中A2W的实现。
// Exerpt from ATLCONV.H
#define A2W(lpa) (\
((_lpa = lpa) == NULL) ? NULL : (\
_convert = (lstrlenA(_lpa)+1),\
ATLA2WHELPER((LPWSTR) alloca(_convert*2), _lpa, _convert)))
A2W是支持从ANSI字符串到UNICODE字符串的转换宏。可以看到,A2W的主体是一个?:表达式,根据源字符串是否为NULL,对冒号之前或之后的表达式进行求值。冒号后的部分又是一个逗号表达式:先计算源字符串的长度,再调用被定义为同名宏(大小写不同)的转换函数,转换函数中调用MultiByteToWideChar系统函数进行具体的转换,逗号表达式的值是转换函数的返回值。最终,转换宏的值是NULL或者转换函数的返回值。
值得注意的是,在A2W宏定义中引用了类似_lpa, _convert等标识符,那么这些标识符是什么,又是哪里来的呢?
这就是为什么在使用这些字符串转换宏之前,需要统一加上一句
USES_CONVERSION;
USES_CONVERSION也是宏定义,就是用来声明转换宏中使用的标识符的。
另外,MSDN中特别提到,A和W之间的转换(A2W,W2A),以及通过预处理宏_UNICODE翻译成A和W之间转换的宏,它们无一例外的都是从调用函数栈上分配空间存放转换结果字符串。因此,转换结果字符串在调用函数返回后自动被清除,也就是不能保留转换结果用于调用函数外使用。
与之相对,再看看第二类,A,W,T,OLE到BSTR的转换宏实现。
// Exerpt from ATLCONV.H
inline BSTR OLE2BSTR(LPCOLESTR lp) {return ::SysAllocString(lp);}
#if defined(_UNICODE)
// in these cases the default (TCHAR) is the same as OLECHAR
inline BSTR T2BSTR(LPCTSTR lp) {return ::SysAllocString(lp);}
inline BSTR A2BSTR(LPCSTR lp) {USES_CONVERSION; return A2WBSTR(lp);}
inline BSTR W2BSTR(LPCWSTR lp) {return ::SysAllocString(lp);}
#elif defined(OLE2ANSI)
// in these cases the default (TCHAR) is the same as OLECHAR
inline BSTR T2BSTR(LPCTSTR lp) {return ::SysAllocString(lp);}
inline BSTR A2BSTR(LPCSTR lp) {return ::SysAllocString(lp);}
inline BSTR W2BSTR(LPCWSTR lp) {USES_CONVERSION; return ::SysAllocString(W2COLE(lp));}
#else
inline BSTR T2BSTR(LPCTSTR lp) {USES_CONVERSION; return A2WBSTR(lp);}
inline BSTR A2BSTR(LPCSTR lp) {USES_CONVERSION; return A2WBSTR(lp);}
inline BSTR W2BSTR(LPCWSTR lp) {return ::SysAllocString(lp);}
#endif
同样,除了OLE2BSTR以外,A,W,T到BSTR的转换根据是否定义预处理宏_UNICODE进行不同的处理,最终归结为两种类型的转换:A2BSTR和W2BSTR。对于A2BSTR,由于BSTR也就是W,
所以A2BSTR的转换在理论上同A2W应该相同。但实际上,A2BSTR调用函数A2WBSTR,A2WBSTR内部从堆上分配空间,再调用系统转换函数MultiByteToWideChar进行转换——这就是A,W之间的转换与2BSTR类型的转换的根本不同之处;W2BSTR就简单了,由于BSTR即是W字符串,因此不需要实际转换,只需分配空间并拷贝源串作为转换结果即可。
对于OLE2BSTR,由于BSTR是从OLECHAR定义来的(见上面BSTR类型定义),因此不管预处理宏如何定义(包括OLE2ANSI是否定义),二者的类型始终是一致的,因此,从OLE到BSTR,并不需要进行实际的转换,只需分配空间并拷贝源串作为转换结果即可。
总结下来,28个字符串转换宏中,根据结果字符串存放位置分为两类,一类是A,W之间的转换宏,这一类宏的转换结果字符串放在调用函数的栈空间,调用函数返回会该空间自动清除,第二类为目的类型为BSTR的转换宏,其转换结果字符串放在系统堆,在调用函数返回后结果字符串仍然存在。这是两类转换宏之间的最显著差别。
测试代码
// TestATLX2Y.cpp
#include <iostream>
#include <atlbase.h>
using namespace std;
#ifdef _UNICODE
#define TCOUT wcout
#else
#define TCOUT cout
#endif
int main()
{
#ifdef _UNICODE
TCOUT << _T("----- Test with _UNICODE --------") << endl;
#else
TCOUT << _T("----- Test without _UNICODE --------") << endl;
#endif
LPTSTR lptstr=_T("TCHAR is either char or wchar_t");
LPSTR lpstr="How can I indicate i'm a LPSTR?";
LPWSTR lpwstr=L"I am a wide-character string";
TCOUT << _T("lptstr:") << lptstr << endl;
cout << "lpstr:" << lpstr << endl;
wcout << L"lpwstr:" << lpwstr << endl;
USES_CONVERSION;
TCOUT << _T("A2T:") << A2T(lpstr) << endl;
wcout << L"A2W:" << A2W(lpstr) << endl;
cout << "T2A:" << T2A(lptstr) << endl;
wcout << L"T2W:" << T2W(lptstr) << endl;
cout << "W2A:" << W2A(lpwstr) << endl;
TCOUT << _T("W2T:") << W2T(lpwstr) << endl;
BSTR bstr;
wcout << L"A2BSTR:" << (bstr=A2BSTR(lpstr)) << endl;
::SysFreeString(bstr);
wcout << L"W2BSTR:" << (bstr=W2BSTR(lpwstr)) << endl;
wcout << L"T2BSTR:" << (bstr=T2BSTR(lptstr)) << endl;
// How to know if we need to free the space pointered by returned value of T2BSTR?
if((void *)bstr!=(void *)lptstr) ::SysFreeString(bstr);
wcout << L"OLE2BSTR:" << OLE2BSTR(lpwstr) << endl;
return 0;
}
代码运行结果: