|
|
2002-12-05· ·丁有和··yesky
( _8 y8 s4 H6 K: @( X3 m4 L
: {6 t \; W; i {: s* `% P9 r$ z# e# P! m) H
Visual C++.NET涉及到ATL/ATL Server、MFC和托管C++等多种编程方式,不仅功能强大而且应用广泛。在编程中,我们常常会遇到ANSI、Unicode以及BSTR不同编码类型的字符串转换操作。本文先介绍基本字符串类型,然后说明相关的类,如CComBSTR、_bstr_t、CStringT等,最后讨论它们的转换方法,其中还包括使用最新ATL7.0的转换类和宏,如CA2CT、CA2TEX等。* A' d! [( F5 R8 `3 m0 h* {
& |* V( {% P' [5 c2 O. ]. t
一、BSTR、LPSTR和LPWSTR
: R7 D9 X) l9 L* Z; p: c; f* K
$ r* x$ T1 N9 ]' Z- w4 N 在Visual C++.NET的所有编程方式中,我们常常要用到这样的一些基本字符串类型,如BSTR、LPSTR和LPWSTR等。之所以出现类似上述的这些数据类型,是因为不同编程语言之间的数据交换以及对ANSI、Unicode和多字节字符集(MBCS)的支持。
6 w4 O2 J/ X5 o; }4 r3 Q( k- T& ?+ j2 \8 U8 i
那么什么是BSTR、LPSTR以及LPWSTR呢?
/ u" g+ S' y5 S5 Z. V! U3 H" _ t* E3 `4 c8 n
BSTR(Basic STRing,Basic字符串)是一个OLECHAR*类型的Unicode字符串。它被描述成一个与自动化相兼容的类型。由于操作系统提供相应的API函数(如SysAllocString)来管理它以及一些默认的调度代码,因此BSTR实际上就是一个COM字符串,但它却在自动化技术以外的多种场合下得到广泛使用。图1描述了BSTR的结构,其中DWORD值是字符串中实际所占用的字节数,且它的值是字符串中Unicode字符的两倍。
+ r& ^- I( i1 i4 G9 r! O; }, [' L& ^
LPSTR和LPWSTR是Win32和VC++所使用的一种字符串数据类型。LPSTR被定义成是一个指向以NULL(‘\0’)结尾的8位ANSI字符数组指针,而LPWSTR是一个指向以NULL结尾的16位双字节字符数组指针。在VC++中,还有类似的字符串类型,如LPTSTR、LPCTSTR等,它们的含义如图2所示。' P- t: ]8 D. L/ ]
$ B9 W( ?: h K! y2 U% [- [& r1 t! D# [
例如,LPCTSTR是指“long pointer to a constant generic string”,表示“一个指向一般字符串常量的长指针类型”,与C/C++的const char*相映射,而LPTSTR映射为 char*。6 P2 a* x6 W5 k) }% w0 E
& r9 R# V$ ~( Z5 t/ \
一般地,还有下列类型定义:. a9 \# X* L9 S+ W* u& M3 J
& w5 \' ^# i* ~1 m8 U1 [. l0 t#ifdef UNICODE # b- \7 ~/ ?' T" o1 h/ y
typedef LPWSTR LPTSTR;
e9 G+ C9 b% e3 f* \& ^ typedef LPCWSTR LPCTSTR;
; {9 Z* [, E0 [. x3 _#else
" s# X% J7 Y8 M6 c$ B! h: V* l8 |1 G' R typedef LPSTR LPTSTR;
3 ?5 i- R V3 @ typedef LPCSTR LPCTSTR;
# W' ]! W% H# S' a. |- e$ {7 K#endif
k4 c/ G. k& P( T) Z; H6 U# ~9 {0 _+ Y
二、CString、CStringA 和 CStringW& {8 A# @2 b) n% J: L0 o! `
8 x" K0 ?* u* v* L y' R2 Y* d. u Visual C++.NET中将CStringT作为ATL和MFC的共享的“一般”字符串类,它有CString、CStringA和CStringW三种形式,分别操作不同字符类型的字符串。这些字符类型是TCHAR、char和wchar_t。TCHAR在Unicode平台中等同于WCHAR(16位Unicode字符),在ANSI中等价于char。wchar_t通常定义为unsigned short。由于CString在MFC应用程序中经常用到,这里不再重复。
* j/ q7 H- d8 _) X* c2 z2 @! t7 M& c# j
三、VARIANT、COleVariant 和_variant_t
3 m. o6 i6 K* m; b9 k, ]7 Q6 o. A' x7 M0 F/ B1 z& s5 K @5 m7 a
在OLE、ActiveX和COM中,VARIANT数据类型提供了一种非常有效的机制,由于它既包含了数据本身,也包含了数据的类型,因而它可以实现各种不同的自动化数据的传输。下面让我们来看看OAIDL.H文件中VARIANT定义的一个简化版:
3 t& { `$ s z1 @# E
2 _8 }" r- S! k3 j: bstruct tagVARIANT {
: M* a& a$ g6 |7 b VARTYPE vt;5 e0 h9 h8 p6 B7 Q6 Y# {" |7 {# I
union {
; k# o0 }" Z1 i+ W3 J& c9 |7 U9 k" Y3 h short iVal; // VT_I2.
[, @6 K' [; c) a long lVal; // VT_I4.. {. x9 ?6 N' Q! _
float fltVal; // VT_R4.9 O9 H; I& z: z' c
double dblVal; // VT_R8.! t+ E- a/ L0 e0 C% C
DATE date; // VT_DATE.+ n; X4 B- k( l; K6 n$ F( ?! v
BSTR bstrVal; // VT_BSTR.
- L3 `& Y! M c …0 Z9 P% w X& ^: D/ \2 e$ M
short * piVal; // VT_BYREF|VT_I2.* w. K" s4 l8 p8 l2 ]
long * plVal; // VT_BYREF|VT_I4.! W& T5 c T2 }% x
float * pfltVal; // VT_BYREF|VT_R4.
. D, _9 i* t/ V4 C5 M- H" y double * pdblVal; // VT_BYREF|VT_R8.
% e) }' m+ |* D2 A( G& U# z& d6 E DATE * pdate; // VT_BYREF|VT_DATE.
% x% Y K k% @0 g5 g5 | BSTR * pbstrVal; // VT_BYREF|VT_BSTR.' @. z" [9 P( q6 H
};
3 Q8 w$ z; T" O# Q8 j( R}; + a8 k* d' D. ]4 D, c) S) M7 h& u) [2 [
% ^' ~+ |- F4 a) P
显然,VARIANT类型是一个C结构,它包含了一个类型成员vt、一些保留字节以及一个大的union类型。例如,如果vt为VT_I2,那么我们可以从iVal中读出VARIANT的值。同样,当给一个VARIANT变量赋值时,也要先指明其类型。例如:
) [$ }" X: {0 {5 D7 O' G0 g% h
/ J6 Q6 x' b4 e ^VARIANT va;6 x" V- H' ~9 P ~4 s! s
:: VariantInit(&va); // 初始化- H3 [6 l6 `( S4 W4 J% ]
int a = 2002;) q6 ^* P) O6 \8 O, h: e4 n
va.vt = VT_I4; // 指明long数据类型
0 }6 P" |9 B6 C! S) jva.lVal = a; // 赋值 3 I; I/ e* R! I2 ~
$ G. E& m" C* q# \( @ 为了方便处理VARIANT类型的变量,Windows还提供了这样一些非常有用的函数:
; B! G# j& X( |- i/ t( Q, C
; V) K( }6 D3 X; D2 | VariantInit —— 将变量初始化为VT_EMPTY;( i' o% B3 s: S0 e3 O, U
6 k0 V6 F r* z4 D' {; V VariantClear —— 消除并初始化VARIANT;( E5 x: E' C4 C' e z& {2 [
8 }7 b; u( ?7 E VariantChangeType —— 改变VARIANT的类型;& V/ ~2 T1 d; l+ N$ v( Y/ a6 H
3 \+ b4 U, M" r VariantCopy —— 释放与目标VARIANT相连的内存并复制源VARIANT。
4 ]9 a. Y6 } S3 t8 U6 p8 j: \7 e$ X$ f J8 \0 q- r' N* v7 k" o% `
COleVariant类是对VARIANT结构的封装。它的构造函数具有极为强大大的功能,当对象构造时首先调用VariantInit进行初始化,然后根据参数中的标准类型调用相应的构造函数,并使用VariantCopy进行转换赋值操作,当VARIANT对象不在有效范围时,它的析构函数就会被自动调用,由于析构函数调用了VariantClear,因而相应的内存就会被自动清除。除此之外,COleVariant的赋值操作符在与VARIANT类型转换中为我们提供极大的方便。例如下面的代码:$ E" Z+ i( b0 A" T$ q8 n
" S5 l( J1 R3 Q; a1 J5 }
COleVariant v1("This is a test"); // 直接构造; H- ^9 D+ G5 I" p' F/ \2 p
COleVariant v2 = "This is a test";
+ {1 u! U& p0 T- k+ c// 结果是VT_BSTR类型,值为"This is a test"3 G6 ^4 m9 q+ e2 j" L/ B1 Q
COleVariant v3((long)2002);
) ~3 j/ W9 X5 e6 Y$ NCOleVariant v4 = (long)2002;
; R5 @5 h, C) ?1 V6 A/ p6 n// 结果是VT_I4类型,值为2002 , b9 G9 B4 X$ g& p
+ |0 ~& h6 k# c! }1 [0 _; h
_variant_t是一个用于COM的VARIANT类,它的功能与COleVariant相似。不过在Visual C++.NET的MFC应用程序中使用时需要在代码文件前面添加下列两句:
' ~ t( @# K# a' J9 a/ a4 V
' H2 M' z1 a+ X0 G$ X1 P) _ #include "comutil.h"0 N; }& Z$ e5 j4 ?% N# ^# ~: {
( z8 ~7 W; `. o #pragma comment( lib, "comsupp.lib" )
3 t+ o# o/ c* H! U
8 [: O9 q) x! I- u& | 四、CComBSTR和_bstr_t
* K2 h% G8 ^7 p, F6 D2 G1 G) f4 N0 X- f: W7 I. e
CComBSTR是对BSTR数据类型封装的一个ATL类,它的操作比较方便。例如:
: l9 V: n" _# b+ j5 a3 d
! Z+ _8 C. Z9 }. QCComBSTR bstr1;
8 f; l8 R+ S, Q, o2 x# B/ H1 Fbstr1 = "Bye"; // 直接赋值. N0 n0 G6 D( \0 ?, b
OLECHAR* str = OLESTR("ta ta"); // 长度为5的宽字符
9 v6 `2 ^* B* N, YCComBSTR bstr2(wcslen(str)); // 定义长度为54 {, I ?1 L/ R/ K! d F4 _
wcscpy(bstr2.m_str, str); // 将宽字符串复制到BSTR中 }1 u5 e+ y; O/ p6 K
CComBSTR bstr3(5, OLESTR("Hello World")); 0 P! }! h( P* P+ C% \" _1 Y: M8 r5 H; H
CComBSTR bstr4(5, "Hello World");
5 a% N' G3 u+ H; g2 V1 N# S# ^CComBSTR bstr5(OLESTR("Hey there")); 3 z* D0 N1 b4 u6 n
CComBSTR bstr6("Hey there");
1 G# n* d+ {, l0 L% VCComBSTR bstr7(bstr6);
' J* b1 N% N! \; Z9 Q+ _" {1 n// 构造时复制,内容为"Hey there" ( R+ }3 t/ w8 X% w
' H5 \' @$ c n5 X8 L _bstr_t是是C++对BSTR的封装,它的构造和析构函数分别调用SysAllocString和SysFreeString函数,其他操作是借用BSTR API函数。与_variant_t相似,使用时也要添加comutil.h和comsupp.lib。& `: ?' y5 s5 f. q5 s
~/ N( g0 ~8 i- S( t$ G8 E5 {8 F 五、BSTR、char*和CString转换+ g7 H% \- ~9 s
, Z) P! V! V, ^- i: I* D$ P$ r( J
(1) char*转换成CString7 W- `% o7 n' @. X! D
. d" m+ d) t# j, Z1 v0 _ y 若将char*转换成CString,除了直接赋值外,还可使用CString::Format进行。例如:
( Y6 w. U+ x1 }* n( b3 {
' p) b5 x6 J) v; l1 C1 E* h6 Mchar chArray[] = "This is a test";
8 {$ k6 Z/ y' n! V2 Ochar * p = "This is a test"; " v6 X4 h# G, _* L/ Y
8 N. c6 F+ Q: y# H. l( Q 或" T% }" r4 L) U% B! A3 R& H* J
- `1 N) m' R8 e! g9 zLPSTR p = "This is a test"; ' F8 M6 D! o5 V+ z. R1 K
2 ?2 N+ i v* l. Q% v4 b
或在已定义Unicode应的用程序中
* k# Y. B- _8 ~3 a5 p7 p4 X; e1 P7 _; d2 G4 y( a) T
TCHAR * p = _T("This is a test");
" {% b% `9 b3 K9 T- D% p" F( ^0 m$ B; l1 s, q( x/ K
或
% U6 ?& O5 t# w# @* u. Q }# F/ A9 M/ Z- q
LPTSTR p = _T("This is a test");0 H( {% p, v' [
CString theString = chArray;
' d$ O$ C1 I' k |8 P: PtheString.Format(_T("%s"), chArray);# H3 X/ c k- ]4 d% w1 T8 e+ g
theString = p;
* L/ b- \. m3 q; g J- f1 C/ f0 q H) A$ b, o3 N) s
(2) CString转换成char*; g# X1 n! w7 Q8 O* ?5 n
" m. b5 N, F9 E4 A' k
若将CString类转换成char*(LPSTR)类型,常常使用下列三种方法:
0 K; _2 e0 X5 f9 `
$ A1 u% S7 F* s 方法一,使用强制转换。例如:
o# }; O$ b+ d' {0 ]% R2 J( p& P- B2 Z+ H; g5 `
CString theString( "This is a test" );" z; R4 r' D, ~3 \7 |
LPTSTR lpsz =(LPTSTR)(LPCTSTR)theString;
2 c0 u: ^1 Q4 B2 m+ D: e6 r$ Z& [8 T9 }0 y/ S' L8 I; I1 n; c( f& S! T# ~1 @
方法二,使用strcpy。例如:/ P! l0 p& V2 u% ]. r1 E" }/ r
* @8 g% l3 X1 o N0 y, P1 ?; h( g$ `CString theString( "This is a test" );1 U! J6 z0 B, m, X1 }
LPTSTR lpsz = new TCHAR[theString.GetLength()+1];
9 `6 J4 Y- N5 A q& u1 E_tcscpy(lpsz, theString); / d& ?% T2 V! H* Z# r6 h- K
, ?% R& E. d/ [0 e
需要说明的是,strcpy(或可移值Unicode/MBCS的_tcscpy)的第二个参数是 const wchar_t* (Unicode)或const char* (ANSI),系统编译器将会自动对其进行转换。' w5 ~1 P6 @0 O, ^
8 K1 y" \0 L0 q3 B5 \, H/ ?
方法三,使用CString::GetBuffer。例如:& Q+ {/ K; I- k& q
4 ~: Y5 u9 }$ c0 {8 Z. T; qCString s(_T("This is a test "));1 T2 \( _6 ~- `" B+ c& x
LPTSTR p = s.GetBuffer();0 E C9 E5 T9 H6 ~% u
// 在这里添加使用p的代码9 k. M! b- |& }7 v' [
if(p != NULL) *p = _T('\0');
2 J# F! k- W) Z& _s.ReleaseBuffer();
! ~: y3 @. K9 D) {, }// 使用完后及时释放,以便能使用其它的CString成员函数
3 R9 N8 S; [9 ~, O, P7 Y
% n0 f; a; A! {( | (3) BSTR转换成char*
. |6 S4 z2 {8 s! S1 |' N% [- k5 B' M+ V1 B# p' u" y
方法一,使用ConvertBSTRToString。例如:
* v x, x7 x% j( k* Y' ]- K
7 C0 @& ~. ^/ I, m#include
- P/ {& C) F7 z4 B; e, ]#pragma comment(lib, "comsupp.lib"), X' A q' l1 ?, e6 F
int _tmain(int argc, _TCHAR* argv[]){/ |4 M: F5 C3 q/ ~6 m' _4 Z5 S# ] K7 e
BSTR bstrText = ::SysAllocString(L"Test");1 v/ g+ S$ w& ]8 ]) {' [
char* lpszText2 = _com_util::ConvertBSTRToString(bstrText);0 q) W& B4 m/ q3 T* l+ Y; H! R! H
SysFreeString(bstrText); // 用完释放6 {& t8 Y, n o7 A" H7 M
delete[] lpszText2;9 V. S3 x$ B, u- {1 d
return 0;
2 f* Z/ _: W m}
! k2 c% ^$ [5 r+ k6 v3 {/ z
1 {7 v+ v# z x3 a- j- P/ [+ ?: N5 G 方法二,使用_bstr_t的赋值运算符重载。例如:3 A k# i. Z" D% x) ^+ @' b* ]
: O1 G' F9 d( N_bstr_t b = bstrText;! A/ H- \8 Y1 A* X
char* lpszText2 = b;
9 O; f8 h2 c% Z# H7 s) M
7 x7 ?! [/ [- y6 M4 B (4) char*转换成BSTR- v0 N+ E6 X- c* s# U- V
7 R8 K+ j% I* h( H
方法一,使用SysAllocString等API函数。例如:3 p- p2 s: u. M: E2 P: G
% u" P5 }" A; u: X' l- o! ^) {) q
BSTR bstrText = ::SysAllocString(L"Test");
- A3 @* z2 i! V0 ^BSTR bstrText = ::SysAllocStringLen(L"Test",4);
& p5 l1 Z( B# \) |9 Y' w! j9 uBSTR bstrText = ::SysAllocStringByteLen("Test",4);
+ |1 A; Q2 ~. M, z& C. b
- b0 I' s1 \9 V 方法二,使用COleVariant或_variant_t。例如:
. s! X$ P" g( D# k$ ~- |# B- m/ W
9 u: x- ]2 x! k" f6 d! D//COleVariant strVar("This is a test");
& n6 Q% T: \2 @2 |% P7 N_variant_t strVar("This is a test");$ u* I# w8 f* R) l/ w( D' ^+ a
BSTR bstrText = strVar.bstrVal;
- R) l A: N7 G1 z' S
' n' T2 _- o; V. V6 y/ s1 o; m9 Z 方法三,使用_bstr_t,这是一种最简单的方法。例如:
) C. m- q% Y }( i
$ |! q! p2 g" J. v0 n% OBSTR bstrText = _bstr_t("This is a test"); ! g" o6 H8 ?0 r* F! A; M0 K7 d; L
' O. t! a: l5 F/ g: F 方法四,使用CComBSTR。例如:
- k \6 ]/ k; ^2 D1 w3 s7 a# }
* V1 K3 h# i S2 x6 p5 YBSTR bstrText = CComBSTR("This is a test"); , b$ f- @$ R% S0 P. Y9 Y7 p
" f5 @: [5 \ I 或; ], E& g( ^1 d) a
T3 x$ N. P0 z
CComBSTR bstr("This is a test");9 ]+ [; ?- d6 ]4 S3 x
BSTR bstrText = bstr.m_str; 8 ]; x0 y) {+ f3 o
( c. a: n0 i2 t a 方法五,使用ConvertStringToBSTR。例如:1 Y2 P' Y, S: V! Z9 s: f+ t
# @0 [4 A: Q3 p: u+ Y5 B$ D/ W7 p2 L8 S8 H. V
char* lpszText = "Test";
% A/ B" C2 a; _% S7 l1 LBSTR bstrText = _com_util::ConvertStringToBSTR(lpszText);
% I3 l! \+ a5 I/ b1 F7 \2 H' G) E+ d
(5) CString转换成BSTR
# P7 Q8 d% O5 N
( H% `, v8 m; V. ?0 I/ F ? 通常是通过使用CStringT::AllocSysString来实现。例如:# T A9 d+ v- y- C1 u+ e
$ R. P. ^8 E$ B/ w. \/ {; r
CString str("This is a test");
: B' Y0 q5 p+ X7 z% m8 P Y0 Z1 p# xBSTR bstrText = str.AllocSysString();
4 n g: h- X- z! X$ N7 a8 ^2 p…5 Q# M7 x: V4 n p0 O2 N) M4 X, h
SysFreeString(bstrText); // 用完释放 4 _7 h9 S1 c0 p8 s9 i
! k" [4 H/ ?; n7 g8 g (6) BSTR转换成CString
1 M6 H5 d4 \8 k, V+ ]! N: O1 C
N! P- l- k$ J' L- @" [ 一般可按下列方法进行:
0 B0 Q( x7 j* \9 S q- g1 J3 X% t. n% z; C' y, _
BSTR bstrText = ::SysAllocString(L"Test");
* M D8 t! y2 ?CStringA str;
3 T- y9 \; g5 V) N y4 astr.Empty();
; z: q" b5 l Vstr = bstrText; : H" k& h% [; }
* t- M! l {/ H+ |+ B5 a: ]8 U7 r 或: |- u7 w1 P7 _: n4 _* ^
- w/ z. Z: ~5 n) T& bCStringA str(bstrText); . i! P. b! G+ r( [
' Z, B6 c1 `: r1 y/ T v+ I
(7) ANSI、Unicode和宽字符之间的转换* s7 g- A! h8 I' a+ I& y# a- a
4 _: P* B0 V7 Y+ n" O0 Y$ U
方法一,使用MultiByteToWideChar将ANSI字符转换成Unicode字符,使用WideCharToMultiByte将Unicode字符转换成ANSI字符。
2 _" ]4 u/ N* B- U0 D3 N; R/ ]0 \* G2 H8 s) p1 ~; s* d
方法二,使用“_T”将ANSI转换成“一般”类型字符串,使用“L”将ANSI转换成Unicode,而在托管C++环境中还可使用S将ANSI字符串转换成String*对象。例如:
0 _* b2 `4 I% L- x p0 }6 K. e' K) t; S. ?9 }/ ?* S; m
TCHAR tstr[] = _T("this is a test");) T! m+ i. p0 m$ O3 B: v* G
wchar_t wszStr[] = L"This is a test";
! S) v/ \# n* P7 z+ EString* str = S”This is a test”;
4 w$ V; _- u1 j2 L
1 o9 Z8 c& C2 z0 a" ?- b$ V 方法三,使用ATL 7.0的转换宏和类。ATL7.0在原有3.0基础上完善和增加了许多字符串转换宏以及提供相应的类,它具有如图3所示的统一形式:
# f* j) i: n, }0 Z* O9 ^. n2 e1 G; m
其中,第一个C表示“类”,以便于ATL 3.0宏相区别,第二个C表示常量,2表示“to”,EX表示要开辟一定大小的缓冲。SourceType和DestinationType可以是A、T、W和OLE,其含义分别是ANSI、Unicode、“一般”类型和OLE字符串。例如,CA2CT就是将ANSI转换成一般类型的字符串常量。下面是一些示例代码:
- `- h0 k" h2 e4 x2 Q- w
7 w! }: B0 ]. F& ~LPTSTR tstr= CA2TEX<16>("this is a test");
4 J+ i) j* B7 O- B7 D+ GLPCTSTR tcstr= CA2CT("this is a test");
, O9 z: J6 o: B" lwchar_t wszStr[] = L"This is a test";
5 s L/ r4 X J7 q0 J7 w" Schar* chstr = CW2A(wszStr);
- x6 W( g3 X* C9 M
+ N7 k7 _" g' o. ] 六、结语
- J# _* E; W r5 R" q; ] ^# o( ?1 Z: ^5 [' g7 r* N% o
几乎所有的程序都要用到字符串,而Visual C++.NET由于功能强大、应用广泛,因而字符串之间的转换更为频繁。本文几乎涉及到目前的所有转换方法。当然对于.NET框架来说,还可使用Convert和Text类进行不同数据类型以及字符编码之间的相互转换。 |
|