|
|
2002-12-05· ·丁有和··yesky
7 G7 d2 n0 i$ T( a3 A- m / P7 R/ m* p4 U5 t$ t% J; _
# W9 P: D' Y/ F8 I) P
Visual C++.NET涉及到ATL/ATL Server、MFC和托管C++等多种编程方式,不仅功能强大而且应用广泛。在编程中,我们常常会遇到ANSI、Unicode以及BSTR不同编码类型的字符串转换操作。本文先介绍基本字符串类型,然后说明相关的类,如CComBSTR、_bstr_t、CStringT等,最后讨论它们的转换方法,其中还包括使用最新ATL7.0的转换类和宏,如CA2CT、CA2TEX等。
6 y/ L: M s- E
2 l( y" q) r. E 一、BSTR、LPSTR和LPWSTR
; F, s6 v/ r7 L- g) ~( I( q# m3 I
在Visual C++.NET的所有编程方式中,我们常常要用到这样的一些基本字符串类型,如BSTR、LPSTR和LPWSTR等。之所以出现类似上述的这些数据类型,是因为不同编程语言之间的数据交换以及对ANSI、Unicode和多字节字符集(MBCS)的支持。
* w* [; P( t% v5 N; i$ }
+ Q% c0 n2 f5 U9 L/ ?5 R( G 那么什么是BSTR、LPSTR以及LPWSTR呢? \0 R* x- m6 x; ^5 w+ m3 u
: Z% l% w0 z4 p0 i8 X: Q- }2 H
BSTR(Basic STRing,Basic字符串)是一个OLECHAR*类型的Unicode字符串。它被描述成一个与自动化相兼容的类型。由于操作系统提供相应的API函数(如SysAllocString)来管理它以及一些默认的调度代码,因此BSTR实际上就是一个COM字符串,但它却在自动化技术以外的多种场合下得到广泛使用。图1描述了BSTR的结构,其中DWORD值是字符串中实际所占用的字节数,且它的值是字符串中Unicode字符的两倍。
0 A. M. s! u2 w2 f% a0 B w% x; p3 S' ~+ ?. Y; l& M, Z0 t# E
LPSTR和LPWSTR是Win32和VC++所使用的一种字符串数据类型。LPSTR被定义成是一个指向以NULL(‘\0’)结尾的8位ANSI字符数组指针,而LPWSTR是一个指向以NULL结尾的16位双字节字符数组指针。在VC++中,还有类似的字符串类型,如LPTSTR、LPCTSTR等,它们的含义如图2所示。7 d P* M6 L, y1 A" S: X
( |' o/ |( u& h' v
例如,LPCTSTR是指“long pointer to a constant generic string”,表示“一个指向一般字符串常量的长指针类型”,与C/C++的const char*相映射,而LPTSTR映射为 char*。. u3 D6 T4 J( z) y
0 F R" |7 B* Y 一般地,还有下列类型定义:- D/ f& t8 A3 B% N
$ R) v1 H; F! y( H8 R7 a& d
#ifdef UNICODE $ a8 Y. _) p/ C/ u& a$ ]' T
typedef LPWSTR LPTSTR;5 K5 I, f, M5 E; j, m
typedef LPCWSTR LPCTSTR; ( U& C% B4 y& j H8 f
#else ' r$ H# r5 T" z3 H* h: k5 U9 `+ C
typedef LPSTR LPTSTR; 0 I9 r# H$ z) s6 c% B( f
typedef LPCSTR LPCTSTR; ' A& b. x' ^8 D5 ?( [! A
#endif
& D1 ] E0 N; d8 x4 F4 u
O5 X7 q- H, Q! K: S N, Q5 {$ l0 j 二、CString、CStringA 和 CStringW$ U) v, m* l+ }) |, f
# z. A, A+ N K, l. V3 o' r. L
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应用程序中经常用到,这里不再重复。9 o$ C- _( u W4 ], d+ \
) E) B% j) o( y: |, d* Q
三、VARIANT、COleVariant 和_variant_t8 W1 k9 g% O$ D, z- r+ q9 E
6 q4 C6 v _0 D% N
在OLE、ActiveX和COM中,VARIANT数据类型提供了一种非常有效的机制,由于它既包含了数据本身,也包含了数据的类型,因而它可以实现各种不同的自动化数据的传输。下面让我们来看看OAIDL.H文件中VARIANT定义的一个简化版:
% n T+ f" Z: O3 [- c' C9 F; O) b9 o; I/ i* f! K; A' H
struct tagVARIANT {" _4 A9 ?# { d$ k x% U
VARTYPE vt;& R; M1 m5 E( t, i9 p
union {0 G& f; U% u+ h z9 Q
short iVal; // VT_I2.
% [: b$ E2 G+ Q: L1 s; b8 A4 P long lVal; // VT_I4.
: ?2 O r& w, i- \ float fltVal; // VT_R4.5 ?, z: ~: k: [0 u% U
double dblVal; // VT_R8.; ?" Z1 w; S0 s5 o
DATE date; // VT_DATE.
5 V3 I, S0 E! B$ B BSTR bstrVal; // VT_BSTR.9 [# m5 H J$ |$ ^+ a' e
…
; z U$ C9 d8 C( ^$ w short * piVal; // VT_BYREF|VT_I2.
) c! ]( ]4 n/ B" u; a1 p8 q long * plVal; // VT_BYREF|VT_I4.
. |( {/ O% X* ]5 Y2 Y: Y8 x float * pfltVal; // VT_BYREF|VT_R4.7 Z9 g7 R" t6 H7 y3 [! Z
double * pdblVal; // VT_BYREF|VT_R8.
6 T- m j% N% w. @* P; [ DATE * pdate; // VT_BYREF|VT_DATE.! m/ _7 b" j0 U+ b4 A
BSTR * pbstrVal; // VT_BYREF|VT_BSTR.) u2 ] {( M" D% L: o# t4 L/ {
};7 M1 F# z; R2 i. z) T
}; : @. @- q7 D$ K: ]
- }9 Y. a# W; ?1 p9 J 显然,VARIANT类型是一个C结构,它包含了一个类型成员vt、一些保留字节以及一个大的union类型。例如,如果vt为VT_I2,那么我们可以从iVal中读出VARIANT的值。同样,当给一个VARIANT变量赋值时,也要先指明其类型。例如:
! l/ t( y9 A: x3 S, `1 E; F- O9 d+ p9 N; i1 }$ M1 Y, c4 f+ X$ l
VARIANT va;
/ P0 n2 w. ]- U; H) `:: VariantInit(&va); // 初始化1 J# I* u- T7 k% L4 J
int a = 2002;
! M i3 A) F5 Mva.vt = VT_I4; // 指明long数据类型
# M! }4 x, f+ {4 X L) a; j1 [va.lVal = a; // 赋值 6 h( {; R0 G# t% J" c7 k8 D) m2 S/ F0 }
: {0 I% E1 ^0 {0 A$ y
为了方便处理VARIANT类型的变量,Windows还提供了这样一些非常有用的函数:9 O, z$ q% z2 [. [, z7 f" I, C
; {$ U# F7 A" i7 Q; U) L" P) m' m
VariantInit —— 将变量初始化为VT_EMPTY;& E1 i/ {* ^) h x9 ]
+ t7 [ A; ]/ P& C/ X# T4 _5 t
VariantClear —— 消除并初始化VARIANT;
D7 M# l/ p& M M1 q) ^: M1 T2 T+ L3 t. b7 l
VariantChangeType —— 改变VARIANT的类型;
% ?% N8 u0 b1 \5 z+ T& W. V- D6 T4 ?/ p
VariantCopy —— 释放与目标VARIANT相连的内存并复制源VARIANT。
- {0 A3 m: C) D! h1 t S- y% p2 ^. S9 S0 \- j/ j5 J; B
COleVariant类是对VARIANT结构的封装。它的构造函数具有极为强大大的功能,当对象构造时首先调用VariantInit进行初始化,然后根据参数中的标准类型调用相应的构造函数,并使用VariantCopy进行转换赋值操作,当VARIANT对象不在有效范围时,它的析构函数就会被自动调用,由于析构函数调用了VariantClear,因而相应的内存就会被自动清除。除此之外,COleVariant的赋值操作符在与VARIANT类型转换中为我们提供极大的方便。例如下面的代码:% z* \9 S0 s5 } F+ s
* y1 P1 b1 \4 H% e
COleVariant v1("This is a test"); // 直接构造
" M% L& S+ A# F) a0 U9 K0 A, f0 oCOleVariant v2 = "This is a test";
( b/ r. u! P1 q* s// 结果是VT_BSTR类型,值为"This is a test"
$ v6 J6 Y# l% {) {6 |$ `COleVariant v3((long)2002);0 _" S& G. a( k( z9 C; F1 d
COleVariant v4 = (long)2002;8 }( O6 y2 e& c3 x; X( E
// 结果是VT_I4类型,值为2002
2 i: S8 U. S* K$ y; F2 r1 B+ d3 _0 ~+ L: {' t" M6 `' L4 z0 b' d
_variant_t是一个用于COM的VARIANT类,它的功能与COleVariant相似。不过在Visual C++.NET的MFC应用程序中使用时需要在代码文件前面添加下列两句:
1 b" G, p& M0 B/ V; }- \0 ]2 `( y9 Z1 }9 u( b
#include "comutil.h"
( i: C/ c1 G( B- z/ t. _9 z- a7 p+ u( X) l8 P
#pragma comment( lib, "comsupp.lib" ). N! k o8 [$ I& L
! Y! R N3 g1 A4 U% h 四、CComBSTR和_bstr_t# L, K/ A8 a+ R" s- T, A4 Y
5 O1 v& u' K1 r) {9 v CComBSTR是对BSTR数据类型封装的一个ATL类,它的操作比较方便。例如:( e( l4 G5 R6 U
7 M2 ~) }5 B' I2 _
CComBSTR bstr1;
4 A5 b) o% f* Z' y# o. X) {/ k5 Mbstr1 = "Bye"; // 直接赋值# q9 w# g. v3 C3 A4 ?4 |
OLECHAR* str = OLESTR("ta ta"); // 长度为5的宽字符
& K& d/ ^6 `4 ]6 q- o! X- y) pCComBSTR bstr2(wcslen(str)); // 定义长度为5. ^7 X/ S7 ]0 b& k5 Z* v0 S
wcscpy(bstr2.m_str, str); // 将宽字符串复制到BSTR中
# R1 k- d' U2 T1 F% [. e+ OCComBSTR bstr3(5, OLESTR("Hello World"));
3 M7 I* }* {" p7 l1 [& UCComBSTR bstr4(5, "Hello World");
7 ], A+ W* U$ L9 B5 tCComBSTR bstr5(OLESTR("Hey there")); ; k; [' L) ?- X5 h/ c# z; _0 q
CComBSTR bstr6("Hey there"); ; s& x% n5 |9 f" d$ W2 [
CComBSTR bstr7(bstr6); ' ]0 o/ A! J; |
// 构造时复制,内容为"Hey there" 4 J, r2 v1 J) ~9 L
" A" j& n- X$ ? _bstr_t是是C++对BSTR的封装,它的构造和析构函数分别调用SysAllocString和SysFreeString函数,其他操作是借用BSTR API函数。与_variant_t相似,使用时也要添加comutil.h和comsupp.lib。
7 J& {. l' x8 S0 k/ E
7 G6 f* j4 Y4 V0 G" O" A 五、BSTR、char*和CString转换
) j+ K4 @( x' o) q0 Z o) `+ t
$ S+ t% e H3 l/ t (1) char*转换成CString
* I: k# M3 c/ y/ B( a4 R" M4 e
: G9 C2 U5 m$ x 若将char*转换成CString,除了直接赋值外,还可使用CString::Format进行。例如:
; A; }* o, q# M, R6 H% {& ^- w" {5 s' r; l5 X3 e
char chArray[] = "This is a test";3 a! z% h1 C5 W- Q# A: g9 a
char * p = "This is a test";
~# c# F. h* y. n4 I! h
" p2 y* u7 K/ p+ d( V 或6 b% E, U! a* Q2 o1 N
1 S7 F6 Z q1 l$ ~3 V+ SLPSTR p = "This is a test";
+ f# ^5 V1 N3 K7 W9 R& }4 M
2 Y, k/ T1 `. q% f$ L 或在已定义Unicode应的用程序中
o+ V+ g4 P0 l$ `
1 b6 z9 i5 F/ ~4 E! M! MTCHAR * p = _T("This is a test"); ; [' w" Q# l4 _- N
3 s6 s( r9 l6 G7 @ 或
' \# H' ?3 w0 _
+ r. m6 P9 H, S$ M, ?9 ?- y3 q9 }& eLPTSTR p = _T("This is a test");
c* [- u$ [: [( }CString theString = chArray;9 j9 }1 x9 I% k+ }
theString.Format(_T("%s"), chArray);' c$ }; n0 W: e, X- ~% a) a/ }! k
theString = p;
3 p2 {) e6 f( Y; B3 a6 @9 T6 |5 I0 R+ Y" h1 t i6 B; F8 f+ m
(2) CString转换成char* e5 k( n$ ^+ O) f/ @) F
# c3 p9 _8 E8 B- b
若将CString类转换成char*(LPSTR)类型,常常使用下列三种方法:
) H- n- B5 @+ n9 {2 M# ]- p3 A0 R2 t; c( @$ E A
方法一,使用强制转换。例如:& ]9 S/ S1 T; b1 N1 z5 @: @( M
8 g) ^- S K3 M( @2 f& V
CString theString( "This is a test" );' m4 |( t% Q' U( U
LPTSTR lpsz =(LPTSTR)(LPCTSTR)theString; 9 l6 _- W' Z+ ~/ m* J# ^' h% _8 M2 ^
5 D# T) ~ L/ c) \ 方法二,使用strcpy。例如:; c2 R! c J. q5 ]" W
+ X8 V3 u X1 g7 n8 e$ H; Q/ fCString theString( "This is a test" );# R! |# L* Z! x- ?: D. |8 P
LPTSTR lpsz = new TCHAR[theString.GetLength()+1];) Z6 Y9 a3 }9 f7 ?" k# X
_tcscpy(lpsz, theString);
2 h9 b) H% `3 a8 W* s
, i+ _. E0 O' ] 需要说明的是,strcpy(或可移值Unicode/MBCS的_tcscpy)的第二个参数是 const wchar_t* (Unicode)或const char* (ANSI),系统编译器将会自动对其进行转换。: s: R/ @1 ]& `1 `6 c3 y" d
3 D( }/ z( C) R9 [, E3 K( o
方法三,使用CString::GetBuffer。例如:( X) ?! g8 E/ e
) F8 V, u- {6 h p; s. T2 {9 dCString s(_T("This is a test "));
* K1 N& k! I( O Q1 bLPTSTR p = s.GetBuffer();+ N s+ _6 s3 p8 r* w+ w5 [, ~
// 在这里添加使用p的代码4 W& Z$ L& P9 ]# ?
if(p != NULL) *p = _T('\0');
: u! k( v+ \% z1 f( P9 Ns.ReleaseBuffer();
" a4 u0 e; [" h$ q; r( O- h/ q// 使用完后及时释放,以便能使用其它的CString成员函数 . B# N# O+ a) m' `
/ q0 [5 g7 F8 V$ e3 P( G* E+ A (3) BSTR转换成char*
[! x# Y% z+ F/ B0 l ^
L4 z( i' ^7 x0 z# H" P 方法一,使用ConvertBSTRToString。例如:
* z6 f; m4 _/ t5 \
/ R" A E; u- ^5 ^9 b8 M5 u# C/ u0 l#include
6 t: m, ^5 l/ ]/ H i' A( z#pragma comment(lib, "comsupp.lib")7 j+ R/ V. E1 o. y/ \
int _tmain(int argc, _TCHAR* argv[]){
. S, G# a$ w: P4 Z+ XBSTR bstrText = ::SysAllocString(L"Test");$ F4 W* X+ S; U( B: D# X' Q, s& q
char* lpszText2 = _com_util::ConvertBSTRToString(bstrText);) [9 ] h6 G9 G' d$ W% |
SysFreeString(bstrText); // 用完释放. H j6 V1 W+ t. N; F
delete[] lpszText2;
8 f. s1 W: l4 D) w6 v1 [return 0;5 ^' j8 b) G) a. p8 \4 X( s% m. T
}
1 Z& Y8 y9 p+ E. x! l9 [0 Y& c, c$ v+ w
方法二,使用_bstr_t的赋值运算符重载。例如:& Y% m" J: f4 i& \! C5 j
) g2 Y( t* K2 E/ S5 i- G_bstr_t b = bstrText;3 N4 }4 |0 o# @7 L7 ~. a: X
char* lpszText2 = b;
9 Z8 W5 p' O* m: F9 y/ g3 ?
- p" \$ m- l+ ]* i2 t& _, G( N (4) char*转换成BSTR
2 ^( z1 b" G# o& m: s) i
2 ^0 E' Z2 T+ z% J 方法一,使用SysAllocString等API函数。例如:
; I' z, K% u" K$ G& d8 h# f6 Y
! |3 h3 H! d' j X* K) IBSTR bstrText = ::SysAllocString(L"Test");6 { C4 u# m4 Y0 i
BSTR bstrText = ::SysAllocStringLen(L"Test",4);; n, q- m o0 O& @, [6 R; W; z
BSTR bstrText = ::SysAllocStringByteLen("Test",4); ) k7 C( g9 Z j( X$ ?
( F3 I/ @* Z% K+ ~ 方法二,使用COleVariant或_variant_t。例如:6 k3 U% l# i* }8 k/ S
8 O5 `4 \/ A# `# E6 v$ R9 ~//COleVariant strVar("This is a test");. l* e- C/ L; G/ R
_variant_t strVar("This is a test");
9 m! }- a# J5 [# @+ p. eBSTR bstrText = strVar.bstrVal; * B( f. V5 m& A/ b) S
; z$ L) ~, Z: M- T
方法三,使用_bstr_t,这是一种最简单的方法。例如:. i# D |. q9 [7 f
# K$ `8 J4 ?0 x# h$ w- T9 c0 @+ dBSTR bstrText = _bstr_t("This is a test"); . [* i; n* X) Z4 d- a
' U7 |1 G* T8 V' h8 n& S& q+ u
方法四,使用CComBSTR。例如:' L2 T1 ?0 K( Z
: p! x O! I- zBSTR bstrText = CComBSTR("This is a test");
?: L* M" [& M" h* \7 ?1 c$ v+ A5 ]3 I# R1 S! v O) C* T
或' H, J2 B3 r! ?7 W: ~: C
) i4 Z3 K' Y. }, S) ]5 ]/ c' f
CComBSTR bstr("This is a test");$ a; u$ _9 Z/ I) c: R* m
BSTR bstrText = bstr.m_str;
, n6 n- r9 d0 `# D+ ^% W3 T* N. H0 w( U# ?* R
方法五,使用ConvertStringToBSTR。例如:( K/ Q1 W5 n( [! @' z
0 y, \6 Z. b M4 ]char* lpszText = "Test";
3 z: J/ q# @2 x& |# b+ B8 GBSTR bstrText = _com_util::ConvertStringToBSTR(lpszText);
/ {; {: H5 c) C, {4 B4 E5 H" s5 B4 \! T; ^7 f: _* `6 y/ V
(5) CString转换成BSTR# E" u0 ~3 Y0 ~" Z3 L
: t9 Z5 H4 V9 f- z
通常是通过使用CStringT::AllocSysString来实现。例如:# m& v% L9 B( k- H5 Z% w
: I$ w' T, [5 v) [$ w
CString str("This is a test");: V; _3 O4 F" v
BSTR bstrText = str.AllocSysString();. y2 j* s2 V% z* \' {: M5 }
…( A0 P% a8 g; i4 j, m
SysFreeString(bstrText); // 用完释放
% U( ]: Z+ _" c2 p; F& l9 [4 t4 c! K) H1 Z; c1 _$ d+ [5 D
(6) BSTR转换成CString2 N6 M; O) M: b9 @/ Q
5 K3 A; I1 I5 G% L8 }' a4 \ 一般可按下列方法进行:
3 D3 C9 C! n% [0 r9 [
+ l7 r4 r, Q1 ?+ \ ]BSTR bstrText = ::SysAllocString(L"Test");3 e( I) T' p3 H q& i9 Q, o
CStringA str;
- U4 J- J$ q/ ?' B8 Qstr.Empty();
% O7 f t) c- ]% k, {str = bstrText;
7 C2 V0 L- G, B3 }% F; e7 o$ G
9 T$ w( n) M. Z; k5 R; C 或9 C2 x7 W; C& p; D6 |& @ o% E
! ^5 q; r, I; X) fCStringA str(bstrText);
4 b; @/ g1 d, B6 s5 q4 y) r/ _. \1 j/ i' F9 y1 A
(7) ANSI、Unicode和宽字符之间的转换, [( |% h7 l- c
# h3 r" }7 b1 v% w$ j
方法一,使用MultiByteToWideChar将ANSI字符转换成Unicode字符,使用WideCharToMultiByte将Unicode字符转换成ANSI字符。1 t5 N7 F& j: a" Y" k
0 z2 I) h6 Q! D: B$ I
方法二,使用“_T”将ANSI转换成“一般”类型字符串,使用“L”将ANSI转换成Unicode,而在托管C++环境中还可使用S将ANSI字符串转换成String*对象。例如:
* e3 s0 k! i& c% @5 R' ?6 z* x# F8 g7 p* p7 A
TCHAR tstr[] = _T("this is a test");0 J0 ~* J: \. |; O6 B. q& G+ G
wchar_t wszStr[] = L"This is a test";
& p1 {4 x/ H. ~ j1 Y$ j& K2 lString* str = S”This is a test”; ) U5 i, R: J3 T$ |& F! X
! }! `0 O; f1 K
方法三,使用ATL 7.0的转换宏和类。ATL7.0在原有3.0基础上完善和增加了许多字符串转换宏以及提供相应的类,它具有如图3所示的统一形式:
+ O* H8 K+ z" R9 G s; t" |, F: E5 ?4 p
其中,第一个C表示“类”,以便于ATL 3.0宏相区别,第二个C表示常量,2表示“to”,EX表示要开辟一定大小的缓冲。SourceType和DestinationType可以是A、T、W和OLE,其含义分别是ANSI、Unicode、“一般”类型和OLE字符串。例如,CA2CT就是将ANSI转换成一般类型的字符串常量。下面是一些示例代码:
# E. j2 @8 g- J' ]
0 f7 l" }+ S& yLPTSTR tstr= CA2TEX<16>("this is a test");
. D& i) i/ n2 j* N7 ~5 h. LLPCTSTR tcstr= CA2CT("this is a test");1 ~- y1 ^$ k) L) l! g
wchar_t wszStr[] = L"This is a test";8 h/ { a3 V0 w0 O& C* v9 E
char* chstr = CW2A(wszStr);
* G5 I# Q @+ C( |& ^- p8 M1 ?& c$ r% L- m
六、结语9 O& x! d3 |/ K9 k J+ n$ Y
" W B2 A! B% U [/ ^" ?. X* z 几乎所有的程序都要用到字符串,而Visual C++.NET由于功能强大、应用广泛,因而字符串之间的转换更为频繁。本文几乎涉及到目前的所有转换方法。当然对于.NET框架来说,还可使用Convert和Text类进行不同数据类型以及字符编码之间的相互转换。 |
|