|
|
2002-12-05· ·丁有和··yesky; ]+ A5 F7 N' q, r; ^9 l
- w8 s. K' _4 q; {, Y3 \& N. s' p6 g4 i/ \/ k x
Visual C++.NET涉及到ATL/ATL Server、MFC和托管C++等多种编程方式,不仅功能强大而且应用广泛。在编程中,我们常常会遇到ANSI、Unicode以及BSTR不同编码类型的字符串转换操作。本文先介绍基本字符串类型,然后说明相关的类,如CComBSTR、_bstr_t、CStringT等,最后讨论它们的转换方法,其中还包括使用最新ATL7.0的转换类和宏,如CA2CT、CA2TEX等。
4 G& g# ^2 L9 {0 x, l {9 o9 m5 j+ k! s) C9 J
一、BSTR、LPSTR和LPWSTR' H* g O, ?9 Y, `7 L
/ h% T0 a I6 y. k9 `4 S 在Visual C++.NET的所有编程方式中,我们常常要用到这样的一些基本字符串类型,如BSTR、LPSTR和LPWSTR等。之所以出现类似上述的这些数据类型,是因为不同编程语言之间的数据交换以及对ANSI、Unicode和多字节字符集(MBCS)的支持。
5 Y& J/ ^ ?6 K: ^; K' `+ y
! i+ ^4 T6 r- I- E3 u {# V, n$ w& ] 那么什么是BSTR、LPSTR以及LPWSTR呢?
9 M" ^- Y& }+ d0 f
2 x4 {5 b5 V7 Q! x1 J6 c$ X; c BSTR(Basic STRing,Basic字符串)是一个OLECHAR*类型的Unicode字符串。它被描述成一个与自动化相兼容的类型。由于操作系统提供相应的API函数(如SysAllocString)来管理它以及一些默认的调度代码,因此BSTR实际上就是一个COM字符串,但它却在自动化技术以外的多种场合下得到广泛使用。图1描述了BSTR的结构,其中DWORD值是字符串中实际所占用的字节数,且它的值是字符串中Unicode字符的两倍。
& k5 `! q$ R" k5 p2 V
1 \ @' g1 C, D1 i3 N* ` LPSTR和LPWSTR是Win32和VC++所使用的一种字符串数据类型。LPSTR被定义成是一个指向以NULL(‘\0’)结尾的8位ANSI字符数组指针,而LPWSTR是一个指向以NULL结尾的16位双字节字符数组指针。在VC++中,还有类似的字符串类型,如LPTSTR、LPCTSTR等,它们的含义如图2所示。
4 e0 {: a3 p7 E4 I
8 \* ~/ H4 B2 T; ~3 ^' [ 例如,LPCTSTR是指“long pointer to a constant generic string”,表示“一个指向一般字符串常量的长指针类型”,与C/C++的const char*相映射,而LPTSTR映射为 char*。* ^: h* E1 @+ I; \ Y9 X+ O
! G* \, u0 D# R
一般地,还有下列类型定义:) ]( h; Z" b8 ^+ Z$ E& n- c% q
6 G; H- c% j! o+ h#ifdef UNICODE & l* ]; P6 _$ ^/ l
typedef LPWSTR LPTSTR;7 \% z2 J' W5 o* T) x: m
typedef LPCWSTR LPCTSTR;
8 U% R/ Z o$ j1 f1 v0 @& j#else $ m5 ~* i7 L7 d% g8 D9 w, y# N" ]$ d
typedef LPSTR LPTSTR;
7 P) P- X e" w7 ~6 x typedef LPCSTR LPCTSTR;
/ ?9 x7 t4 H& a#endif - K# b8 v2 b% h; `* o+ l( t# c3 e& {: m
1 s+ Z% q1 F t" e$ G& v# l$ G- a 二、CString、CStringA 和 CStringW7 ~9 p; q- m" ~" H
. a* D8 Y! R- o2 x 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应用程序中经常用到,这里不再重复。
* \% V" Q& \: t! q5 d L0 j5 X1 r. }* E2 V- d! c' g
三、VARIANT、COleVariant 和_variant_t
% w1 t0 I4 l5 @2 `8 s; n* @# q* z' x
在OLE、ActiveX和COM中,VARIANT数据类型提供了一种非常有效的机制,由于它既包含了数据本身,也包含了数据的类型,因而它可以实现各种不同的自动化数据的传输。下面让我们来看看OAIDL.H文件中VARIANT定义的一个简化版:6 Y8 \; ], E7 @- ]2 O
) c6 {. G3 N& c4 X8 G! nstruct tagVARIANT {( m" K" H; t& ~% g1 I; e5 K: h
VARTYPE vt;
3 F8 @+ x$ B3 ?. }' R# h0 ]# B' @# ^ union {* z, v4 P0 ^/ E0 T; c8 m
short iVal; // VT_I2.
+ k! G# S4 M7 b long lVal; // VT_I4.
% L7 p6 C- A, j1 ^, M float fltVal; // VT_R4.
2 A0 G& H4 l( V o1 i- V double dblVal; // VT_R8.9 H7 L% ?# L4 G( I6 S$ N
DATE date; // VT_DATE.) x6 C8 O0 x2 z1 M
BSTR bstrVal; // VT_BSTR.4 S1 E+ S* _# |& S3 J
…
t5 S7 \! Y" X) b7 i short * piVal; // VT_BYREF|VT_I2.
2 {1 _1 i6 Z8 X v# u k long * plVal; // VT_BYREF|VT_I4.3 h F& y- p2 f* N* [
float * pfltVal; // VT_BYREF|VT_R4./ @8 `4 K3 m1 K# c. ^- Y( K3 |' m
double * pdblVal; // VT_BYREF|VT_R8./ n+ Z- {5 {# R+ H1 r& w
DATE * pdate; // VT_BYREF|VT_DATE.
5 x/ d4 {& _5 ]. k BSTR * pbstrVal; // VT_BYREF|VT_BSTR.
( m% B- s) B) s z4 y8 Z* j/ M2 a };
5 y' B! G& w. c; l7 c }' W' u9 c};
5 }* n% r. y0 L. X8 f
. c7 }/ D" b1 b; [3 m 显然,VARIANT类型是一个C结构,它包含了一个类型成员vt、一些保留字节以及一个大的union类型。例如,如果vt为VT_I2,那么我们可以从iVal中读出VARIANT的值。同样,当给一个VARIANT变量赋值时,也要先指明其类型。例如:
. W" T3 {; ^9 b
4 X$ k' R# o# }5 p3 sVARIANT va;
6 Y, g' t, M% c& b* k+ |' x$ c( \. }:: VariantInit(&va); // 初始化$ f. [) ^8 ^: G v$ v4 o
int a = 2002;( a5 |6 a0 _* ^: l+ v8 x _" \/ Q
va.vt = VT_I4; // 指明long数据类型
5 t/ G$ t+ t1 u8 I% n6 Qva.lVal = a; // 赋值
3 p9 ?. d. Q E& w0 v0 r( U. n3 C5 ^$ \8 N- B
为了方便处理VARIANT类型的变量,Windows还提供了这样一些非常有用的函数:. C! K5 A" q: Z4 u; ]
# P. a" n. G/ g3 |; @3 P% ]
VariantInit —— 将变量初始化为VT_EMPTY;
$ M* p: B7 ^; F i8 w
$ p5 V' R: k [1 S& d' ^+ x VariantClear —— 消除并初始化VARIANT;
# w+ R3 p: Z' O/ v, `
$ R6 s+ A# j9 D VariantChangeType —— 改变VARIANT的类型;
, K) F4 O5 I: L4 U* b" r. d4 Z; @8 n
VariantCopy —— 释放与目标VARIANT相连的内存并复制源VARIANT。
7 g, v: z3 Y+ o2 L* T6 G# n+ q! g6 |) v; ~4 a
COleVariant类是对VARIANT结构的封装。它的构造函数具有极为强大大的功能,当对象构造时首先调用VariantInit进行初始化,然后根据参数中的标准类型调用相应的构造函数,并使用VariantCopy进行转换赋值操作,当VARIANT对象不在有效范围时,它的析构函数就会被自动调用,由于析构函数调用了VariantClear,因而相应的内存就会被自动清除。除此之外,COleVariant的赋值操作符在与VARIANT类型转换中为我们提供极大的方便。例如下面的代码:
4 }& F0 N& a( C7 {" M! F5 V% k7 ?, [! `& ]. u+ x
COleVariant v1("This is a test"); // 直接构造
- R. F; e1 f4 P+ ~ w& L. z eCOleVariant v2 = "This is a test"; 4 s- n" C% n& s9 [8 Y0 O
// 结果是VT_BSTR类型,值为"This is a test"
: ^* q$ d- T/ i" g" J, nCOleVariant v3((long)2002);
. C1 ?* L2 [+ ~, D6 s2 ~) |COleVariant v4 = (long)2002;' k' J, p7 G3 f$ V" e& h
// 结果是VT_I4类型,值为2002 6 z! `0 L# c5 A7 M9 f s
, {. e& M. E+ w, g, g3 `3 ? _variant_t是一个用于COM的VARIANT类,它的功能与COleVariant相似。不过在Visual C++.NET的MFC应用程序中使用时需要在代码文件前面添加下列两句:
4 q* p a; B% W& ~0 N8 R' {8 y0 {5 y( m. x b' ^
#include "comutil.h"1 r R1 F, c3 I6 _; r
# h3 _) M X; C- a #pragma comment( lib, "comsupp.lib" )
) L* N' ^& ~; C! e+ t& o2 S0 ^$ z4 G9 L
四、CComBSTR和_bstr_t
/ m8 i6 _5 @" \9 F, W) {4 ]" o) U: F
CComBSTR是对BSTR数据类型封装的一个ATL类,它的操作比较方便。例如:
+ o- |( [3 J# i6 g6 F4 M7 |2 x8 z- H
CComBSTR bstr1; * W& @- |6 C/ O! _7 y. P$ l
bstr1 = "Bye"; // 直接赋值4 b8 s" m1 t! J: n1 O' d& p; r
OLECHAR* str = OLESTR("ta ta"); // 长度为5的宽字符9 x- P7 t; ^. N7 S0 [
CComBSTR bstr2(wcslen(str)); // 定义长度为5 m1 @7 m2 X* E7 [9 r. [6 a! J
wcscpy(bstr2.m_str, str); // 将宽字符串复制到BSTR中
' f; F( V$ a, T3 f- J8 @CComBSTR bstr3(5, OLESTR("Hello World")); 5 t; ` ^; ?/ z$ R& M
CComBSTR bstr4(5, "Hello World");
4 w9 V0 ~1 [; l% a: q+ X8 lCComBSTR bstr5(OLESTR("Hey there")); : r) B* ]; B# g/ U8 o! f1 ^
CComBSTR bstr6("Hey there");
4 i- i: t8 ?* p6 v* UCComBSTR bstr7(bstr6); - k" @" z2 t' j& S" q" ^9 s4 `
// 构造时复制,内容为"Hey there" " N- p A& E6 D9 i& b
4 S8 u+ T9 U3 G1 F& I5 c _bstr_t是是C++对BSTR的封装,它的构造和析构函数分别调用SysAllocString和SysFreeString函数,其他操作是借用BSTR API函数。与_variant_t相似,使用时也要添加comutil.h和comsupp.lib。
5 g) K& q9 [8 |. T ^4 U+ ]: A
8 u' I7 ~- W1 r 五、BSTR、char*和CString转换% m7 I9 {" U% Q% L }9 g9 ?" a
! {' T; W& _- B5 _0 `6 s: @5 p
(1) char*转换成CString
- o0 c! g' Y) N6 D" s1 Z
( _) e9 c! w: K 若将char*转换成CString,除了直接赋值外,还可使用CString::Format进行。例如:
2 W5 X' S. a# {0 V6 o* h2 C! |/ @' `# Z7 t# J C1 F6 r! |4 `; _! O
char chArray[] = "This is a test";* o8 Z- b9 L t' s7 H% @
char * p = "This is a test";
7 B% r5 E* r( a. p% O3 i
7 E2 @0 |- T0 @! H$ V; @ 或
& v, c* f2 _, r4 S% P6 ~# p9 F
" e, G" S# a2 x) i) C4 VLPSTR p = "This is a test"; & Q* X5 `+ ~1 O. E
; X& @! |# {/ \$ x& n( z+ V4 u, K 或在已定义Unicode应的用程序中
, S1 f( X) f4 `- H% i" w7 b. w9 z
/ U' |0 I( t! P. {7 @TCHAR * p = _T("This is a test");
6 b) ^7 `; w- a1 T- d7 g
" [6 P# M" k3 I9 u! i 或6 w* c x7 n$ U2 l1 H7 M1 K
9 E. r) X9 J. L) S, @* a- u+ e( {
LPTSTR p = _T("This is a test");
/ J% v p+ i3 h6 s. U1 qCString theString = chArray;# {3 g0 N4 W! D2 T% h
theString.Format(_T("%s"), chArray);& |: ^2 x* c5 k% o* v
theString = p;
# K; p. R. e6 M8 y" J3 @( j; |) m6 X. `2 K
(2) CString转换成char*
! Z8 V4 I, u9 Y S' N; M, U+ ^- s+ A
若将CString类转换成char*(LPSTR)类型,常常使用下列三种方法:
. A ~5 c* m' `( a2 k- h; m7 N4 i. D$ i9 b V
方法一,使用强制转换。例如:
3 H) ?+ O. z# N- _8 O) \" V |3 r
$ T( J- E! p1 d" w8 G. jCString theString( "This is a test" );: q# z q9 r: b, S2 d, a# C6 w
LPTSTR lpsz =(LPTSTR)(LPCTSTR)theString; ; t; q' }7 H- p- B2 x# X- {
; o0 Q/ x$ c$ N' l
方法二,使用strcpy。例如:
7 g0 @2 E, _) y7 d: |. D) `% {8 N' f$ `- d C3 h# n {
CString theString( "This is a test" );
3 F' V; y1 Z9 O6 y1 _LPTSTR lpsz = new TCHAR[theString.GetLength()+1];
! q! T6 K$ {8 n, m$ b4 ^$ d, ?" K_tcscpy(lpsz, theString); ( W7 b. c+ U, c/ |; [& `& B
1 O, ?7 W3 c6 w# j: {0 D8 t/ D 需要说明的是,strcpy(或可移值Unicode/MBCS的_tcscpy)的第二个参数是 const wchar_t* (Unicode)或const char* (ANSI),系统编译器将会自动对其进行转换。
) k _2 O* J& |3 Q/ E/ G: N& ]+ l8 K3 v) N1 j* w! F/ Q, k
方法三,使用CString::GetBuffer。例如:1 c$ X X n1 r% P: y V" l6 g
+ _) R2 v) g \) Q& o, dCString s(_T("This is a test "));* U, E1 l! z1 n# a3 U
LPTSTR p = s.GetBuffer();9 v m5 Z" z" J6 @/ j# h
// 在这里添加使用p的代码
3 X; d3 j( k) u+ r8 P$ Rif(p != NULL) *p = _T('\0');
4 {* C$ n1 g1 X$ j$ s1 @; S3 as.ReleaseBuffer(); : Z R7 Y5 Y+ Q
// 使用完后及时释放,以便能使用其它的CString成员函数
- |' V% ]( X( Z5 p! Y
+ W% r$ c3 ]% p1 V; C (3) BSTR转换成char*
9 o8 H) L6 E$ s) r
7 J% N& J) a. J' D3 o7 ~ 方法一,使用ConvertBSTRToString。例如:4 i. c3 w' h% ~" K, w- N. C0 ]: ]
5 d% i4 ~' H* o
#include
4 u+ X2 s* a. f4 ?5 `#pragma comment(lib, "comsupp.lib")! t- f2 a/ S2 D4 ^" b
int _tmain(int argc, _TCHAR* argv[]){
& S O" b, _; ^# M: Y! h- c3 lBSTR bstrText = ::SysAllocString(L"Test");
! M+ k% S. K6 k( uchar* lpszText2 = _com_util::ConvertBSTRToString(bstrText);
9 A) z; U8 B5 H; R" KSysFreeString(bstrText); // 用完释放
: v' Y- W# B/ X3 Gdelete[] lpszText2;
2 v5 a4 b1 B5 }$ ~+ y- \return 0;) L& I2 ^, s& G
}
2 v: C5 |" f6 f! V. F1 {! T) ~ }' b3 C5 t" M, w( ~9 I" {! r' B
方法二,使用_bstr_t的赋值运算符重载。例如:7 N7 i* C, Z3 d5 |/ r2 G& H- N
! Z8 ~7 t! \, h3 _4 W! X. H
_bstr_t b = bstrText; a* _' `2 j; {# q U
char* lpszText2 = b; % R0 F- ]! k. Q. L
7 u0 M; `* A- k5 H (4) char*转换成BSTR
% h: ^5 u* H3 B( J# [' n: t2 K1 Q; U3 n; ]
方法一,使用SysAllocString等API函数。例如:
: [. J0 S6 m# l6 C/ e/ i
& a" X& \$ p4 U& l! w' Q( H9 U$ ZBSTR bstrText = ::SysAllocString(L"Test");
7 n: H# \* H* d* m4 z/ sBSTR bstrText = ::SysAllocStringLen(L"Test",4);
0 t. S& @. s) q8 ]. ?5 @BSTR bstrText = ::SysAllocStringByteLen("Test",4);
* {2 S1 y2 w' x* Y) K$ B) P9 j$ O- Y4 ?6 a* g. a6 {5 n
方法二,使用COleVariant或_variant_t。例如:+ V3 h% T7 p, l% s/ `$ {2 R& _ r
+ ~$ r4 e+ L6 Q' C
//COleVariant strVar("This is a test");
5 V i& U5 ~# i2 p_variant_t strVar("This is a test");9 D' ~+ z, o# o
BSTR bstrText = strVar.bstrVal; % `7 l7 H4 p- I5 |: ?# H- M7 G8 w
! E8 ?# Q- g C8 t+ m/ B, n; ]
方法三,使用_bstr_t,这是一种最简单的方法。例如:
- _* e, q. I: f7 p; e$ Q
# E @0 a2 ?$ L) lBSTR bstrText = _bstr_t("This is a test");
- N8 ^9 G6 U! R. n) w0 x' O0 y1 U" L5 n) X( C+ m
方法四,使用CComBSTR。例如:
9 ^# i6 B% W; u: {. b* X: I0 @4 { A, H+ z2 k6 i0 e& q1 @
BSTR bstrText = CComBSTR("This is a test"); + G( ?; k' k" G+ N# {$ g( _9 |' P
# s' L% X# K4 F8 {9 G, f 或- i+ C i6 @) T3 Z9 E0 B
0 P3 C5 N) J4 v
CComBSTR bstr("This is a test");/ I1 [& P0 T$ q8 q
BSTR bstrText = bstr.m_str; : f9 t" k* S& E7 Y; i
6 ?! ^. M" _ `, m/ r
方法五,使用ConvertStringToBSTR。例如:3 n. q, l0 M; {8 D' a3 \( I& M
8 [ N7 n O, P( @1 } Achar* lpszText = "Test";- v5 s7 ]" ~; B0 M) ~. c. {' I1 G/ k
BSTR bstrText = _com_util::ConvertStringToBSTR(lpszText); 9 ?8 G1 @ s$ N* X3 [7 [* U$ V9 A
, ?0 F# B$ @+ @( C) b1 f! w (5) CString转换成BSTR+ A: U4 G9 U; I2 @% G5 g: U
$ k. }8 a9 q2 x" s) b0 y 通常是通过使用CStringT::AllocSysString来实现。例如:
3 g7 k+ o( Q& E4 t1 P0 r! ~8 a% M2 f$ b- j+ l% g
CString str("This is a test");# A) L$ d9 A) I0 d
BSTR bstrText = str.AllocSysString();0 _! i+ {& M0 o; P1 J( N$ C
…# v+ x. r& f; O2 ?
SysFreeString(bstrText); // 用完释放
! a/ C/ {" {5 c5 X! t2 n+ b4 O! b R3 X
(6) BSTR转换成CString. z n7 G2 { U. L9 w- f
3 t& n8 B1 L4 M5 R 一般可按下列方法进行:5 j6 f+ W# k* ?- q" V
- D! h7 a9 ]/ n' J! z2 v
BSTR bstrText = ::SysAllocString(L"Test");7 d9 p {8 `, w. D+ o
CStringA str;0 p, g+ C7 A/ g# H0 @, E4 m
str.Empty();
% L# o2 N0 o& a' \! B8 a. Wstr = bstrText; ' V: G8 N# v- [2 B- S. G9 f
) O w$ X& O4 J4 A- P q" x 或
' L4 S4 \+ v. D& P5 c$ E) y( J6 A) P+ x- d! I
CStringA str(bstrText);
3 T7 G- `; Y% k% b: l* W1 X5 K. X2 [; @6 k. Q+ Y
(7) ANSI、Unicode和宽字符之间的转换
/ g, w1 P# G( y# z* J: u% Z$ Q0 z/ H* O$ `0 I
方法一,使用MultiByteToWideChar将ANSI字符转换成Unicode字符,使用WideCharToMultiByte将Unicode字符转换成ANSI字符。
; E J+ n: @7 r
) a6 q4 [5 x9 \5 \6 k 方法二,使用“_T”将ANSI转换成“一般”类型字符串,使用“L”将ANSI转换成Unicode,而在托管C++环境中还可使用S将ANSI字符串转换成String*对象。例如:
! I# u. w$ q$ k; F3 Q5 x; u V6 L) u( W) [3 V
TCHAR tstr[] = _T("this is a test");
" V, J; M; X0 ~% I! s) ?. rwchar_t wszStr[] = L"This is a test";
2 }: c" ^# h) Y$ _( }0 J2 |1 Q/ J8 KString* str = S”This is a test”; : |9 f/ e# Z. b, R6 D! p5 R6 v
$ j0 ^5 o; A6 z( V 方法三,使用ATL 7.0的转换宏和类。ATL7.0在原有3.0基础上完善和增加了许多字符串转换宏以及提供相应的类,它具有如图3所示的统一形式:
& |: i4 [+ T7 C d' S
0 X7 r9 d' S6 v: R( U 其中,第一个C表示“类”,以便于ATL 3.0宏相区别,第二个C表示常量,2表示“to”,EX表示要开辟一定大小的缓冲。SourceType和DestinationType可以是A、T、W和OLE,其含义分别是ANSI、Unicode、“一般”类型和OLE字符串。例如,CA2CT就是将ANSI转换成一般类型的字符串常量。下面是一些示例代码:, o9 q# F2 g- H0 r
7 g) _7 ~' i* F% N
LPTSTR tstr= CA2TEX<16>("this is a test");9 y1 P. x6 z% {9 X
LPCTSTR tcstr= CA2CT("this is a test");' P; f! ^: M% X* I p
wchar_t wszStr[] = L"This is a test";
$ i4 x% d- ]5 V' Schar* chstr = CW2A(wszStr);
* @' e$ H& f2 Z' p* T
! A" F R, ~- ?2 l: V9 m2 H, g: h 六、结语1 y$ x o5 Q) P
$ G0 L+ G8 b+ O$ f* s# t- A
几乎所有的程序都要用到字符串,而Visual C++.NET由于功能强大、应用广泛,因而字符串之间的转换更为频繁。本文几乎涉及到目前的所有转换方法。当然对于.NET框架来说,还可使用Convert和Text类进行不同数据类型以及字符编码之间的相互转换。 |
|