|
2002-12-05· ·丁有和··yesky
2 {; T+ I I v' J8 z 9 h, ?+ f- d# V5 ?
$ `* c' X! \8 x! j* ~: K8 N; M Visual C++.NET涉及到ATL/ATL Server、MFC和托管C++等多种编程方式,不仅功能强大而且应用广泛。在编程中,我们常常会遇到ANSI、Unicode以及BSTR不同编码类型的字符串转换操作。本文先介绍基本字符串类型,然后说明相关的类,如CComBSTR、_bstr_t、CStringT等,最后讨论它们的转换方法,其中还包括使用最新ATL7.0的转换类和宏,如CA2CT、CA2TEX等。6 }5 G) D/ x9 [
4 z& C7 U8 W5 \/ f
一、BSTR、LPSTR和LPWSTR4 t; D: m: J* s* H. g& p
( S* Y2 `& F: h6 d/ C
在Visual C++.NET的所有编程方式中,我们常常要用到这样的一些基本字符串类型,如BSTR、LPSTR和LPWSTR等。之所以出现类似上述的这些数据类型,是因为不同编程语言之间的数据交换以及对ANSI、Unicode和多字节字符集(MBCS)的支持。
& o( ?- b& w' h6 J- q. Y
0 \) y3 o+ y" L5 c2 [+ ^ 那么什么是BSTR、LPSTR以及LPWSTR呢?- v2 @& \2 |) G% b
4 ~# i$ x* u. w! {" g
BSTR(Basic STRing,Basic字符串)是一个OLECHAR*类型的Unicode字符串。它被描述成一个与自动化相兼容的类型。由于操作系统提供相应的API函数(如SysAllocString)来管理它以及一些默认的调度代码,因此BSTR实际上就是一个COM字符串,但它却在自动化技术以外的多种场合下得到广泛使用。图1描述了BSTR的结构,其中DWORD值是字符串中实际所占用的字节数,且它的值是字符串中Unicode字符的两倍。( K' r0 `& f6 V( J+ L0 q! z& J
2 s# l7 C h$ O9 p9 H
LPSTR和LPWSTR是Win32和VC++所使用的一种字符串数据类型。LPSTR被定义成是一个指向以NULL(‘\0’)结尾的8位ANSI字符数组指针,而LPWSTR是一个指向以NULL结尾的16位双字节字符数组指针。在VC++中,还有类似的字符串类型,如LPTSTR、LPCTSTR等,它们的含义如图2所示。2 d( f ^8 s8 G8 X% e2 J+ `; `5 J
3 u3 B) j: D6 u; |2 n" c1 l 例如,LPCTSTR是指“long pointer to a constant generic string”,表示“一个指向一般字符串常量的长指针类型”,与C/C++的const char*相映射,而LPTSTR映射为 char*。5 R5 ]) b6 O! X- g
! L9 d% u8 }( U
一般地,还有下列类型定义:" v4 Q& E: R1 l- `: `; F
- o6 Z( ]7 J6 n# z0 g
#ifdef UNICODE 9 E& X1 ?6 H5 C4 r6 Y3 h8 V0 f: q$ x
typedef LPWSTR LPTSTR;7 t* ?8 \4 W3 D; v7 ^
typedef LPCWSTR LPCTSTR;
2 {! d) \" q# K; u4 g#else
& i5 l2 O5 U5 I0 U0 \* a* _/ A2 Q% x typedef LPSTR LPTSTR;
. j6 l! W# ?- I3 t5 \ typedef LPCSTR LPCTSTR;
9 L& y6 j2 A1 v# K3 G3 ^#endif ) m- ?. o% p& f( }
w0 `, d: Q: G) q n% t, {0 Q 二、CString、CStringA 和 CStringW* w* o! q5 L9 h7 L' o* S0 T
* w# |7 G1 {+ B
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应用程序中经常用到,这里不再重复。
' Z& x2 z' N' E
8 p4 {# ^$ R2 J$ B+ v5 T7 E. Q 三、VARIANT、COleVariant 和_variant_t, \* d, z1 p3 \- B/ o% D7 Y5 a. y* O
! Z: F# q$ Z, D& C$ U8 R6 W
在OLE、ActiveX和COM中,VARIANT数据类型提供了一种非常有效的机制,由于它既包含了数据本身,也包含了数据的类型,因而它可以实现各种不同的自动化数据的传输。下面让我们来看看OAIDL.H文件中VARIANT定义的一个简化版:
- d/ Z( b) C: l! \, J0 w4 w# b3 f- {) H4 ]8 {# G
struct tagVARIANT {5 R C7 e" B! t
VARTYPE vt;) g* d4 h7 y( V W
union {
: z* R( s: ~2 S short iVal; // VT_I2.
' q2 N3 A6 B2 l. w- ] long lVal; // VT_I4.
+ W6 i4 o+ S, J" f float fltVal; // VT_R4.
4 n& X5 ?+ Q0 q* ?; m double dblVal; // VT_R8.5 \; @9 e4 s6 G' p& @* Q4 N: m6 @: _
DATE date; // VT_DATE.
I5 a' B! |9 t/ [ BSTR bstrVal; // VT_BSTR.
8 h) w$ [/ e8 L* W6 {0 [( h …
2 ], v. v! u/ Z0 O1 Z short * piVal; // VT_BYREF|VT_I2.) m2 x- z/ {1 L7 z3 L
long * plVal; // VT_BYREF|VT_I4.
4 z6 D R% }3 z: ~ float * pfltVal; // VT_BYREF|VT_R4.- I+ Y& C3 ^1 R/ e' b" `
double * pdblVal; // VT_BYREF|VT_R8.
. \2 M% L M# [: v7 p DATE * pdate; // VT_BYREF|VT_DATE.$ }/ N; S V) O: v& F
BSTR * pbstrVal; // VT_BYREF|VT_BSTR.; i( b6 e: u* {( m% h$ _
};/ e( A5 ^ N6 Y0 N- Y7 p
};
: a2 @; ^ V5 Z. k) [
3 g, Q/ S L/ _) l# }3 a0 C 显然,VARIANT类型是一个C结构,它包含了一个类型成员vt、一些保留字节以及一个大的union类型。例如,如果vt为VT_I2,那么我们可以从iVal中读出VARIANT的值。同样,当给一个VARIANT变量赋值时,也要先指明其类型。例如:) E0 y6 }$ z- z! s0 ^4 g
9 z; ?7 D: }# D$ N VVARIANT va; y G+ v$ [# p( Q7 ^( r
:: VariantInit(&va); // 初始化
5 L3 C t5 z; v4 T* O4 Z" Lint a = 2002;
7 a4 _% ?+ C" Kva.vt = VT_I4; // 指明long数据类型
" Y: ?! A0 O- \. }va.lVal = a; // 赋值
" v6 \: F8 ^, D2 g2 ]- K' r, `( X% ?1 }( ~# w2 t. t6 g0 A, T
为了方便处理VARIANT类型的变量,Windows还提供了这样一些非常有用的函数:
9 e3 B& Q: }4 w9 Z
t2 z6 k8 b* q1 n VariantInit —— 将变量初始化为VT_EMPTY;
4 h2 B' K. C9 }5 U- V, G$ k) g; i
VariantClear —— 消除并初始化VARIANT;: }+ [6 Q0 s. a. `: p3 I7 l
# n7 G: X' o0 T4 [0 A
VariantChangeType —— 改变VARIANT的类型;
4 K$ p% F4 @- R$ a# ^; \. A
P( R0 H7 `( ?5 s VariantCopy —— 释放与目标VARIANT相连的内存并复制源VARIANT。
0 l' |% V: V% R. w1 z* i1 w
0 h" J/ \% J& c* ]9 L COleVariant类是对VARIANT结构的封装。它的构造函数具有极为强大大的功能,当对象构造时首先调用VariantInit进行初始化,然后根据参数中的标准类型调用相应的构造函数,并使用VariantCopy进行转换赋值操作,当VARIANT对象不在有效范围时,它的析构函数就会被自动调用,由于析构函数调用了VariantClear,因而相应的内存就会被自动清除。除此之外,COleVariant的赋值操作符在与VARIANT类型转换中为我们提供极大的方便。例如下面的代码:: {4 \& v0 x' V& N7 }
6 G" E- m: e7 `9 M% }' d
COleVariant v1("This is a test"); // 直接构造% H1 V. t" \4 U& G# e0 N
COleVariant v2 = "This is a test";
9 F: U* H1 H9 p; G) a5 C// 结果是VT_BSTR类型,值为"This is a test"
; p; u- d8 m* p6 r# @COleVariant v3((long)2002);, F) w( y- \5 a9 ]3 I* t0 l
COleVariant v4 = (long)2002;" @# c2 }- m3 o: x# p' [
// 结果是VT_I4类型,值为2002
, v8 \# ?9 M4 L7 Q! ^
o3 j# X$ H0 \$ W+ t5 e8 B5 S! |) B5 R _variant_t是一个用于COM的VARIANT类,它的功能与COleVariant相似。不过在Visual C++.NET的MFC应用程序中使用时需要在代码文件前面添加下列两句:: t" b$ B! q) i0 q; b8 r4 j& e
, v/ C6 w- m V- v, `
#include "comutil.h"8 j$ N$ m. T a
$ ^1 \$ m2 Z% L1 f4 V
#pragma comment( lib, "comsupp.lib" )
- q# C8 i1 I/ j; P( A: i E, ~1 M; S7 J( n9 d) W9 B: O
四、CComBSTR和_bstr_t q4 s2 E5 N/ p+ P
, g6 z7 i o( y$ O) {- F( j( e: {
CComBSTR是对BSTR数据类型封装的一个ATL类,它的操作比较方便。例如:/ l# [) C8 A5 i
3 p& M0 z3 S3 W1 C5 aCComBSTR bstr1; ) n/ ]# ]( M+ h
bstr1 = "Bye"; // 直接赋值. r# _ k8 s: v% n
OLECHAR* str = OLESTR("ta ta"); // 长度为5的宽字符
0 p' u$ }$ g* R' z( { ~" sCComBSTR bstr2(wcslen(str)); // 定义长度为5
@5 h: O! e/ p& K$ R( m( twcscpy(bstr2.m_str, str); // 将宽字符串复制到BSTR中. V3 ]& [7 X7 w3 s: S+ Y
CComBSTR bstr3(5, OLESTR("Hello World")); 2 F! y6 ]3 d. X+ h5 P; C) u6 ?' X
CComBSTR bstr4(5, "Hello World"); + f' J! M' {" T4 X) T
CComBSTR bstr5(OLESTR("Hey there"));
7 a1 n5 f6 P+ rCComBSTR bstr6("Hey there");
; r# c" Y4 G) ]7 dCComBSTR bstr7(bstr6);
3 d; I6 V; v$ k: x, l6 N. P// 构造时复制,内容为"Hey there" 7 M# P9 j/ D) v1 t6 Y
, Z! d1 ]5 A( u3 Q6 E+ c
_bstr_t是是C++对BSTR的封装,它的构造和析构函数分别调用SysAllocString和SysFreeString函数,其他操作是借用BSTR API函数。与_variant_t相似,使用时也要添加comutil.h和comsupp.lib。$ b$ O2 {% w, ~
9 E# R$ U3 E/ h
五、BSTR、char*和CString转换
5 y& w6 A* p: ~( r+ f0 j0 g/ d2 I# x- d% I$ s% C* X
(1) char*转换成CString
& \4 r, ?5 ~ u R6 L. }4 \3 z) ~. b5 q
若将char*转换成CString,除了直接赋值外,还可使用CString::Format进行。例如:
+ E; C9 c4 T: ?; {" ~7 u6 R. L! @- G
char chArray[] = "This is a test";
& g+ g3 j( c1 \! ~char * p = "This is a test";
/ {* A! o7 x+ `- _& d/ n# D/ _# I9 ^0 _" W
或5 l. _3 G$ c8 v
; J, k2 M4 {* Z# b
LPSTR p = "This is a test";
! M3 ?3 q7 S% v. D$ X7 A9 S8 U+ p& a7 } d
或在已定义Unicode应的用程序中6 f) s: s+ [: A: y
4 d. Y3 {9 T) R7 H, |
TCHAR * p = _T("This is a test");
z- o( t! M( V* |9 t# g8 u
: D' Q q; I) `5 A) {& c% S 或. M5 x9 t0 g+ {" E3 {3 h2 S. s% |
8 g$ s L3 S* f) M- X
LPTSTR p = _T("This is a test");+ v% J, t1 P% `
CString theString = chArray;
1 q2 D- n' B0 UtheString.Format(_T("%s"), chArray);( p3 }6 t" s- z
theString = p; % h: W6 V4 W ]8 C/ u. c' c
, B f7 X# _/ H: K! V+ _
(2) CString转换成char*
2 L# E6 O! h" l5 Q/ @; w6 x+ h2 B, v+ u5 Q/ `
若将CString类转换成char*(LPSTR)类型,常常使用下列三种方法:
M9 N/ K2 o! U, s3 h3 [0 w8 ~, S0 ?; y! r3 r# j. y7 |* o5 h( w) J
方法一,使用强制转换。例如:
9 O- p6 j m9 p( `- \ V" S/ B! `" X: p5 X
CString theString( "This is a test" );( g6 n3 w, H) j" K
LPTSTR lpsz =(LPTSTR)(LPCTSTR)theString; ( L# t% _+ l5 P2 I
c7 ` Q' R1 }/ N
方法二,使用strcpy。例如:
% b3 A; ]% B4 ]# z# n
( J1 _7 ^$ D2 t& x5 |CString theString( "This is a test" );4 N7 V7 t9 }: c8 f0 w/ N+ o
LPTSTR lpsz = new TCHAR[theString.GetLength()+1];, w5 z4 E/ k" C/ H: ^; c$ w
_tcscpy(lpsz, theString);
/ a# }2 ~9 B6 }2 ^ x3 g* _
3 B7 L. i9 S, |3 B% C, U 需要说明的是,strcpy(或可移值Unicode/MBCS的_tcscpy)的第二个参数是 const wchar_t* (Unicode)或const char* (ANSI),系统编译器将会自动对其进行转换。. _3 V' Q: ?. j* u! N
9 e9 Q* t2 ]5 n- B; S. z# k 方法三,使用CString::GetBuffer。例如:
7 P5 Y+ {; C4 L8 C3 H+ [3 T1 t9 W+ r+ O2 e3 w, @, x
CString s(_T("This is a test "));
! ]5 s9 P; V6 L1 ` rLPTSTR p = s.GetBuffer();
3 V8 N% G) i8 \" W% X// 在这里添加使用p的代码
6 k2 ?- M$ ~$ Mif(p != NULL) *p = _T('\0');, N$ U, [: N9 d' b k6 W# Z7 b
s.ReleaseBuffer(); ( A4 y a" p, E P5 q7 [( `
// 使用完后及时释放,以便能使用其它的CString成员函数 % a' _( r8 N0 a( E
, u5 k0 p, `, c, |- K (3) BSTR转换成char*1 N2 b* N7 F5 G4 a. a9 R+ Y4 d
( N1 {4 a& j L) l# P8 j 方法一,使用ConvertBSTRToString。例如:. N3 y9 v# p4 Z/ r1 B' r/ ^$ t
+ }/ o4 {+ A; h- |6 Q- S#include
! A! m8 [' A& m+ G#pragma comment(lib, "comsupp.lib")4 e( x& l1 u! O6 \* i" C8 V
int _tmain(int argc, _TCHAR* argv[]){' R. P' O5 b3 n/ k, M/ v/ T$ ^
BSTR bstrText = ::SysAllocString(L"Test");
4 X3 }# f: j1 Uchar* lpszText2 = _com_util::ConvertBSTRToString(bstrText);
4 y/ P0 I' K' ~8 BSysFreeString(bstrText); // 用完释放
1 A7 {1 y$ }1 F1 W; e+ F( H6 Cdelete[] lpszText2;4 m3 A) v6 ]/ ^" ~
return 0;! M- q G9 V& ?* Z8 p/ a
} : U8 Q" X; g9 d
7 R+ b0 b: E$ @ }7 z 方法二,使用_bstr_t的赋值运算符重载。例如:
P, T7 \6 G8 D/ h5 u+ m! ]7 R* a' \! W8 R( o n( Q
_bstr_t b = bstrText;
) J# |# O# ?+ ~char* lpszText2 = b;
+ ~7 e( [' f/ w& E9 G5 Z
3 V I" ?0 n+ Y, `8 o6 `8 [ (4) char*转换成BSTR z; M5 y$ a9 H% W
8 N9 }6 |9 l z! A1 d' ]; M; U# O6 j: e: ~
方法一,使用SysAllocString等API函数。例如:: y) {0 P/ Z2 |6 `6 Q$ l
4 \9 R6 j4 V& h2 t
BSTR bstrText = ::SysAllocString(L"Test");
! i$ S7 p0 R. ]7 v- x: @+ k. uBSTR bstrText = ::SysAllocStringLen(L"Test",4);2 n4 S' Q( y! g4 b8 l
BSTR bstrText = ::SysAllocStringByteLen("Test",4);
1 J4 i8 i, E) X* F7 |6 Z. Q4 k/ u, k7 S& p; D5 h1 i1 k3 S# f
方法二,使用COleVariant或_variant_t。例如:; w, c1 ^- s# z) u' I) E
2 n* Y& f1 e, ?+ u2 t
//COleVariant strVar("This is a test");& r2 C3 y/ R9 u/ A0 s" M
_variant_t strVar("This is a test");* W# E2 y& Z, L, N5 t# q# u# J' J' \
BSTR bstrText = strVar.bstrVal;
9 L6 x2 {: n+ }# s \, t7 s7 M0 O4 E2 N& R. Y, n6 z \$ v
方法三,使用_bstr_t,这是一种最简单的方法。例如:
, Z3 u+ y& X; n% r
" D) @2 s K+ f& uBSTR bstrText = _bstr_t("This is a test");
% m! K3 ~4 l" F" S. r) g9 }* y* U+ K
) }& \* e: A% `7 [2 K2 S7 r; [ 方法四,使用CComBSTR。例如:6 r. u" G% r$ w# b
" L# E5 J7 Q6 n% Q$ M
BSTR bstrText = CComBSTR("This is a test"); + y5 K9 l3 X) J3 l* @: w
5 I# C6 ~6 D+ [: ?
或, M- M- e c1 Z2 n
; K6 B: X3 y6 F9 D2 d c+ v
CComBSTR bstr("This is a test");+ w9 h2 m8 o8 \3 q
BSTR bstrText = bstr.m_str; 1 S* v1 m! \0 n h- O
4 t( X9 k$ N6 H 方法五,使用ConvertStringToBSTR。例如:
$ X' v8 q8 ~ z$ J i [! B$ H
. h+ e( U8 m8 v- z, \4 v+ z. ^6 C A0 F% pchar* lpszText = "Test";% K- {/ x. L+ N& G! b- A
BSTR bstrText = _com_util::ConvertStringToBSTR(lpszText); : p2 N& w# u4 V# g. c
+ \( Q! q8 N! f+ z, C/ {) p1 D
(5) CString转换成BSTR+ }/ ~ |, M0 J) ^6 F) m
$ \" a7 ?- H9 x4 }
通常是通过使用CStringT::AllocSysString来实现。例如:
7 n& m6 ^$ m3 E, W9 J/ q2 _ H+ Z" ~! ?4 g: I6 H
CString str("This is a test");+ l4 ]! e. u; o" r
BSTR bstrText = str.AllocSysString();
- R5 u3 y" k I) M+ j* c4 h3 T…
# M0 z! H# b! c. xSysFreeString(bstrText); // 用完释放
0 Y0 {& j2 Y: p& P
0 @# g K# p: D7 E& ?! B$ g (6) BSTR转换成CString( N$ x# E8 V6 m9 _ }& M8 S
. [6 _# S3 `$ y) z5 h
一般可按下列方法进行:2 q0 N4 v8 t) ^# l& O9 v/ S
1 I9 k7 a5 Z# W. }8 XBSTR bstrText = ::SysAllocString(L"Test");& {( A* c5 F6 P& S2 ?+ l' y
CStringA str;8 T8 v1 {7 a/ N& z3 y. I
str.Empty();& {( l4 t D" i; H6 `7 {$ e
str = bstrText; . j) o& z3 e7 x3 a8 ]. j
. ^0 S% A4 [0 M+ _
或6 M( f% ^+ V3 r8 D% s8 ~. _
+ Q3 B6 y- ~- ]! i0 s Y1 a& c1 S( YCStringA str(bstrText); ! @6 B# \* h+ x
0 f5 Y |1 r2 f# e2 j; V: Y
(7) ANSI、Unicode和宽字符之间的转换
/ o8 M) ~& z* h/ } }
4 X: U" p2 g c2 P h/ [9 B 方法一,使用MultiByteToWideChar将ANSI字符转换成Unicode字符,使用WideCharToMultiByte将Unicode字符转换成ANSI字符。5 Y M" ]. V# [ ^0 Y3 [
) W! G0 ^5 S( V, B% F* G0 b 方法二,使用“_T”将ANSI转换成“一般”类型字符串,使用“L”将ANSI转换成Unicode,而在托管C++环境中还可使用S将ANSI字符串转换成String*对象。例如:0 I& t9 x( J1 {& F+ R, I1 \
; [# l4 z7 y, f# u7 D1 k! D2 zTCHAR tstr[] = _T("this is a test");
, i9 H" L% ?0 ^. k) g$ |; fwchar_t wszStr[] = L"This is a test";
: g" H- F- D2 @9 BString* str = S”This is a test”; / J( }% K6 F3 e' R$ @5 d3 ~$ h6 R
+ O1 s8 b/ l! f* W) ?( z& B 方法三,使用ATL 7.0的转换宏和类。ATL7.0在原有3.0基础上完善和增加了许多字符串转换宏以及提供相应的类,它具有如图3所示的统一形式:
; a; h6 M( d! j1 P* `$ N/ }+ w- L
: B1 L3 d- E# c Q! z: N3 K: r 其中,第一个C表示“类”,以便于ATL 3.0宏相区别,第二个C表示常量,2表示“to”,EX表示要开辟一定大小的缓冲。SourceType和DestinationType可以是A、T、W和OLE,其含义分别是ANSI、Unicode、“一般”类型和OLE字符串。例如,CA2CT就是将ANSI转换成一般类型的字符串常量。下面是一些示例代码:
- w0 Y3 C c) k7 g! i: O, E/ ?0 N9 d" J
LPTSTR tstr= CA2TEX<16>("this is a test");
* p) f+ }9 V0 Q( m N; P2 uLPCTSTR tcstr= CA2CT("this is a test");: B- Y8 u( i: Q7 B6 E- K7 M
wchar_t wszStr[] = L"This is a test";
' k# a0 ]7 l5 ?: h4 Q" u$ ?char* chstr = CW2A(wszStr);
' }% y1 N9 w' j; Q, N9 d; V9 D4 \
4 F. U- Z- R& d9 L8 `0 w 六、结语5 v7 z" A1 v4 O% \1 C# D& V* e9 \# U% k* n
7 h& \0 M( z2 t 几乎所有的程序都要用到字符串,而Visual C++.NET由于功能强大、应用广泛,因而字符串之间的转换更为频繁。本文几乎涉及到目前的所有转换方法。当然对于.NET框架来说,还可使用Convert和Text类进行不同数据类型以及字符编码之间的相互转换。 |
|