|
|
2002-12-05· ·丁有和··yesky+ X6 _9 l8 b, ^+ |
2 k' ~2 |$ D3 i% d5 K0 f) M' |9 O1 t" E
Visual C++.NET涉及到ATL/ATL Server、MFC和托管C++等多种编程方式,不仅功能强大而且应用广泛。在编程中,我们常常会遇到ANSI、Unicode以及BSTR不同编码类型的字符串转换操作。本文先介绍基本字符串类型,然后说明相关的类,如CComBSTR、_bstr_t、CStringT等,最后讨论它们的转换方法,其中还包括使用最新ATL7.0的转换类和宏,如CA2CT、CA2TEX等。! l. ~7 K9 @* a# c8 C/ A5 A
5 ~: f& P8 l) N0 R5 i4 o; a
一、BSTR、LPSTR和LPWSTR
+ Z" G f( G Q9 n! q4 R* u. d
1 @* U6 ?. F+ ], x, G) g X 在Visual C++.NET的所有编程方式中,我们常常要用到这样的一些基本字符串类型,如BSTR、LPSTR和LPWSTR等。之所以出现类似上述的这些数据类型,是因为不同编程语言之间的数据交换以及对ANSI、Unicode和多字节字符集(MBCS)的支持。1 Z) E* h: z6 C
* t P8 @9 b0 @) F" J, p 那么什么是BSTR、LPSTR以及LPWSTR呢?
9 d3 Q6 ]2 h6 c' h: H- w& c5 \" N/ O, V/ l0 i
BSTR(Basic STRing,Basic字符串)是一个OLECHAR*类型的Unicode字符串。它被描述成一个与自动化相兼容的类型。由于操作系统提供相应的API函数(如SysAllocString)来管理它以及一些默认的调度代码,因此BSTR实际上就是一个COM字符串,但它却在自动化技术以外的多种场合下得到广泛使用。图1描述了BSTR的结构,其中DWORD值是字符串中实际所占用的字节数,且它的值是字符串中Unicode字符的两倍。9 o' A7 n( N4 G$ H! P
7 t; I( E9 V% j5 p8 r- m. { LPSTR和LPWSTR是Win32和VC++所使用的一种字符串数据类型。LPSTR被定义成是一个指向以NULL(‘\0’)结尾的8位ANSI字符数组指针,而LPWSTR是一个指向以NULL结尾的16位双字节字符数组指针。在VC++中,还有类似的字符串类型,如LPTSTR、LPCTSTR等,它们的含义如图2所示。
% @! n/ S, e- N! W* x E8 D9 {0 u+ b+ Z) o; r+ V4 o+ b
例如,LPCTSTR是指“long pointer to a constant generic string”,表示“一个指向一般字符串常量的长指针类型”,与C/C++的const char*相映射,而LPTSTR映射为 char*。3 o2 C# l& B/ I0 N; ^1 Z# |7 o5 n
4 j7 C" d2 W: v# J- w 一般地,还有下列类型定义:* ?4 g4 f5 C" C* w5 K8 \
% _1 U/ h! p7 W9 z& W
#ifdef UNICODE " o5 s# K% A' Q5 n
typedef LPWSTR LPTSTR;
0 o- x4 J# U: O$ z/ l! K8 t typedef LPCWSTR LPCTSTR; 4 |/ d' l, V) ~: ]. A* s5 M! ?' b2 @
#else 9 K7 m1 h5 ^( c: [" } k
typedef LPSTR LPTSTR; z9 }; f% A( f# ?7 g( }3 _
typedef LPCSTR LPCTSTR; * w' m- l" j1 M* d, Y8 Q9 L! r
#endif + R2 M( R# p6 M( p9 _, j" M% m# N
5 ~2 a/ W* T* z1 U5 x& `
二、CString、CStringA 和 CStringW' R1 P) A p2 b! p# t
- a( W7 V6 `$ w; M9 |
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应用程序中经常用到,这里不再重复。6 C p' @6 k, e: o: y8 m
1 Z; Z6 N) Z' j+ J2 u5 ?
三、VARIANT、COleVariant 和_variant_t
; V- U5 p) f! d, B+ }2 j# b/ h3 x1 l
在OLE、ActiveX和COM中,VARIANT数据类型提供了一种非常有效的机制,由于它既包含了数据本身,也包含了数据的类型,因而它可以实现各种不同的自动化数据的传输。下面让我们来看看OAIDL.H文件中VARIANT定义的一个简化版:0 y3 l% J8 r! G* F2 G1 a
' F0 O3 i) Z. v" ~4 \) l' istruct tagVARIANT {+ Q5 ]. g' t& ~4 ]* {! w2 V; Q
VARTYPE vt;/ z( p* Q' r* [/ J4 Y) g* ]( `! U: w3 [; H
union {
; e1 r& l/ `8 U* O short iVal; // VT_I2.
" E4 s0 i, o* L' l$ l) V long lVal; // VT_I4.! p& H8 H: X; @* m( k
float fltVal; // VT_R4.
; U! _/ |; V9 L: o5 r double dblVal; // VT_R8.
* B* w% C" b8 n# P! \ DATE date; // VT_DATE.2 F4 a* X1 a4 N$ u9 j2 w, e2 y
BSTR bstrVal; // VT_BSTR.* w' x/ @/ A$ C! v' C; P
…6 t7 a' u9 V* [5 a3 g, N
short * piVal; // VT_BYREF|VT_I2.
8 f9 N5 ?9 Z h# m! M3 y long * plVal; // VT_BYREF|VT_I4.+ ~+ R' a( X3 t& F7 B7 u, n
float * pfltVal; // VT_BYREF|VT_R4.5 {7 W( [ }3 h+ R
double * pdblVal; // VT_BYREF|VT_R8.
# T; a( v0 y, Q9 W3 _% g$ L DATE * pdate; // VT_BYREF|VT_DATE.' O9 t2 w5 K* y8 L2 Y
BSTR * pbstrVal; // VT_BYREF|VT_BSTR.& w, b" Z- y6 b1 J( u( g2 i: o
};
8 L" V @/ ?4 B) H+ E- h1 t8 {}; ; S- T; b4 {5 |8 M
2 h m U/ Y3 D: p' L 显然,VARIANT类型是一个C结构,它包含了一个类型成员vt、一些保留字节以及一个大的union类型。例如,如果vt为VT_I2,那么我们可以从iVal中读出VARIANT的值。同样,当给一个VARIANT变量赋值时,也要先指明其类型。例如:3 ~4 {1 h& M' C$ r
# W" Q! m& E$ g! w
VARIANT va;( }# d3 O3 E9 i) ?* @2 Z. u& d6 i
:: VariantInit(&va); // 初始化5 P) F- v6 ~" C0 `7 v) F
int a = 2002;
f0 h* K9 X' v( l5 v7 c9 u& s8 |va.vt = VT_I4; // 指明long数据类型1 W* v$ [5 j% b6 y" y
va.lVal = a; // 赋值 3 y- m: G4 x, p9 S
/ t2 ~+ ^4 a) C0 Q# i
为了方便处理VARIANT类型的变量,Windows还提供了这样一些非常有用的函数:
# b7 P6 n" t8 D6 M9 c
8 q7 }8 E( s+ B VariantInit —— 将变量初始化为VT_EMPTY;& B9 @- X( y) d
% R$ u/ m$ S' N; W0 k
VariantClear —— 消除并初始化VARIANT;
) f( Q9 {7 d0 W: _% o4 p, w4 V# n$ t2 w- k
VariantChangeType —— 改变VARIANT的类型;
; Z; |2 R' @( T+ Y
: E, I# a7 ^. ^ @# \6 u VariantCopy —— 释放与目标VARIANT相连的内存并复制源VARIANT。
3 n+ x0 ]- M6 b5 x* `& P% d1 ?
V' f3 P4 Z _$ O: i( u' M0 d COleVariant类是对VARIANT结构的封装。它的构造函数具有极为强大大的功能,当对象构造时首先调用VariantInit进行初始化,然后根据参数中的标准类型调用相应的构造函数,并使用VariantCopy进行转换赋值操作,当VARIANT对象不在有效范围时,它的析构函数就会被自动调用,由于析构函数调用了VariantClear,因而相应的内存就会被自动清除。除此之外,COleVariant的赋值操作符在与VARIANT类型转换中为我们提供极大的方便。例如下面的代码:
1 d0 z" J1 V, _1 A. N& p
4 P: A/ F E# G# ^ o2 z) qCOleVariant v1("This is a test"); // 直接构造: N/ u; N/ Z+ j7 h- [- Z+ `
COleVariant v2 = "This is a test"; ( }/ ^2 f* n/ d# d9 _0 R0 G
// 结果是VT_BSTR类型,值为"This is a test"
7 i: `+ P, x% t- Q1 z$ l: ICOleVariant v3((long)2002);
, v4 O# A& }1 _7 z( {COleVariant v4 = (long)2002;
) ?! n6 ^" X5 h5 F$ L, V) p// 结果是VT_I4类型,值为2002 6 a: }, U3 r8 f% L% C' F' `
( ?. u3 A/ H# u' f3 S5 s
_variant_t是一个用于COM的VARIANT类,它的功能与COleVariant相似。不过在Visual C++.NET的MFC应用程序中使用时需要在代码文件前面添加下列两句:6 D5 k$ }# F1 J+ C; a; D) p
o. B! }& n0 u3 M" }: A
#include "comutil.h") r( [! b l& _
0 t$ X$ @+ h2 r, K5 j
#pragma comment( lib, "comsupp.lib" )
* b/ I" X, [) _( x _9 ?) _/ K
; {% F$ M! m/ s$ f) W 四、CComBSTR和_bstr_t; U1 F5 |6 H5 U( c! a
3 [. [8 W8 X' d CComBSTR是对BSTR数据类型封装的一个ATL类,它的操作比较方便。例如:
/ b* E& l% |5 a' g! x! A3 J0 i2 F+ f6 q
CComBSTR bstr1; C3 h2 S& K3 E
bstr1 = "Bye"; // 直接赋值! J. J& H. }# _( c1 N2 e5 c' n9 c
OLECHAR* str = OLESTR("ta ta"); // 长度为5的宽字符
) v% G; v. E0 G6 B0 s) b- iCComBSTR bstr2(wcslen(str)); // 定义长度为5
# U% d2 x0 O, Owcscpy(bstr2.m_str, str); // 将宽字符串复制到BSTR中 H1 x' J+ ~2 C) e4 f
CComBSTR bstr3(5, OLESTR("Hello World")); - t8 y* e) y) p
CComBSTR bstr4(5, "Hello World");
" G& L4 p% ]8 f1 [; a( C6 K4 aCComBSTR bstr5(OLESTR("Hey there"));
# i: B) J3 z0 t# F& A5 W9 a6 G" ]CComBSTR bstr6("Hey there");
8 b" j6 Z4 O( F, L7 ?CComBSTR bstr7(bstr6);
. }0 P/ w& B0 \" t9 h3 i2 R// 构造时复制,内容为"Hey there"
5 d7 q; ~" q* @9 v# y
7 P% J- g8 Z& u _bstr_t是是C++对BSTR的封装,它的构造和析构函数分别调用SysAllocString和SysFreeString函数,其他操作是借用BSTR API函数。与_variant_t相似,使用时也要添加comutil.h和comsupp.lib。5 U1 G8 [% l8 y& h9 o0 g
' ?( W6 P' `! a2 j2 {
五、BSTR、char*和CString转换
) h5 K$ q; y. ?6 S1 ~9 T) b: ^( g( l! |8 W
(1) char*转换成CString1 E' p! V, V% B C3 e* o
& ?0 Y; A0 o- n& V& Z5 Q; y( K 若将char*转换成CString,除了直接赋值外,还可使用CString::Format进行。例如:
. Z D) W B h% L- m
. S" t7 S& ?4 u) Ychar chArray[] = "This is a test";
% b5 q1 t5 L# n5 O% r: z: ychar * p = "This is a test"; + P w3 V) @0 ^, B3 q. q* F L9 U
5 J1 W% r$ F- a D' U/ {* S
或
, a0 f. Y0 A7 ]7 D! Y6 k0 G- r: b4 v8 A& i
LPSTR p = "This is a test"; $ r" H! {: L( _0 P$ Q. a8 h9 V. E- Y9 `6 h
7 r! b! j0 t5 q# K 或在已定义Unicode应的用程序中, S. _/ L$ d2 S8 e$ @: [
* I1 o4 Q# I# K/ LTCHAR * p = _T("This is a test");
9 a5 V' a# J4 P, s
+ w4 h% j! A- R4 B& } 或
* ~. D! |* s& f# p5 x$ {4 O
+ o0 F# ?6 ~, k) \( I: ?- |+ hLPTSTR p = _T("This is a test");
- D; d$ ~' [& J# R4 nCString theString = chArray;
/ a, R x/ E& @1 N( X/ H. itheString.Format(_T("%s"), chArray);& v8 I8 h0 U+ n& J% U9 F+ ~
theString = p; 6 k! e+ C: b$ B
# N- [$ w0 o, C p7 T; G (2) CString转换成char*
4 @7 g9 h4 E. U1 s9 F
3 `' v* C+ B* j 若将CString类转换成char*(LPSTR)类型,常常使用下列三种方法:6 i3 V; M9 u1 F G }7 D' V" K' A
- O# ~* p' A/ Z% z
方法一,使用强制转换。例如:, c1 d8 K0 [) b4 a
% f) q9 F" t+ P9 d
CString theString( "This is a test" );. A$ o1 L& Q6 B4 }
LPTSTR lpsz =(LPTSTR)(LPCTSTR)theString;
: o0 i& w0 M& T- W; R W6 h# f
# t, p3 K% ]2 z) r! c# O# f( j 方法二,使用strcpy。例如:
4 T6 v+ {) \3 N6 P
% Y4 y) y* ^; f+ K$ RCString theString( "This is a test" );
' t* D* _# u( f. g% \LPTSTR lpsz = new TCHAR[theString.GetLength()+1];3 e ^ Z' m5 A4 L
_tcscpy(lpsz, theString); 1 x% p2 l( ~3 e
/ }/ Y3 s, P H2 Z- g5 i: G9 e 需要说明的是,strcpy(或可移值Unicode/MBCS的_tcscpy)的第二个参数是 const wchar_t* (Unicode)或const char* (ANSI),系统编译器将会自动对其进行转换。
# v: F1 Q" G9 P2 r6 L
, c* c; M: H8 K7 e# q# {) ` 方法三,使用CString::GetBuffer。例如:
; g+ ?1 m5 w4 x0 ~! q8 L( _# e. E# E! U. r8 u: N- K: i
CString s(_T("This is a test "));! ^$ i; a( x- ?
LPTSTR p = s.GetBuffer();% F& ~( h' o% v2 e
// 在这里添加使用p的代码' `% \8 x8 Z' G: x: C1 h
if(p != NULL) *p = _T('\0');1 Y$ y4 J4 `, ~/ m5 A
s.ReleaseBuffer();
0 N: N8 T7 A. z+ X// 使用完后及时释放,以便能使用其它的CString成员函数 3 O4 @5 }1 w3 k3 s. G
8 C2 |% Y& Z# l
(3) BSTR转换成char*+ j7 O0 s- R% v) o) |
& y* ]2 r! N6 d0 w; @+ u
方法一,使用ConvertBSTRToString。例如:
6 a. V& m' s# ]+ K0 R i, p7 Z1 ?. o3 [5 k" |
#include C \7 |9 x g
#pragma comment(lib, "comsupp.lib")
$ a+ u5 O* p9 E, X, O- Eint _tmain(int argc, _TCHAR* argv[]){; T1 a0 D E# d& a. s
BSTR bstrText = ::SysAllocString(L"Test");! H ^1 b- t/ |2 h0 w0 d
char* lpszText2 = _com_util::ConvertBSTRToString(bstrText);
7 p( u) @* P9 ]$ cSysFreeString(bstrText); // 用完释放, H2 F) X# H: C7 c6 q! s
delete[] lpszText2;
# f$ w' _- m/ c4 O4 f- ereturn 0;
4 ]: B- o3 Z# p( U}
( }$ S" M* S+ e ^/ V" B
9 M" ]' h1 J \$ J& b 方法二,使用_bstr_t的赋值运算符重载。例如:
5 i5 {) b1 u2 C- Q2 m4 V1 _
6 A) Y/ Z( Y9 f: v_bstr_t b = bstrText;
& e1 y7 n# j1 b G" ?0 ichar* lpszText2 = b; 8 a) M( Q, O& r A$ j3 f3 i
* C' Y: X3 ~$ i" h! f; F) n
(4) char*转换成BSTR8 Z# ?+ _" J% Y6 P
$ \# G/ C/ B" z8 U
方法一,使用SysAllocString等API函数。例如:6 r% N1 w0 J3 R2 q: p* F w6 Z6 d
* ~" W) r: f+ D9 m$ j; w0 kBSTR bstrText = ::SysAllocString(L"Test");4 y3 o2 I6 c4 t$ O
BSTR bstrText = ::SysAllocStringLen(L"Test",4);5 I @9 ~& Q+ i5 g! v
BSTR bstrText = ::SysAllocStringByteLen("Test",4); 9 y7 Q2 |2 B6 B" P6 M+ x
' Z- p3 X' P# M" Z& h6 L 方法二,使用COleVariant或_variant_t。例如:
9 A3 n% h ?; d3 d' H0 L2 N' r% H, j) B' k8 V2 i
//COleVariant strVar("This is a test");
, o9 F4 ^9 H) q# i1 M" z4 A_variant_t strVar("This is a test");
6 W1 n6 J1 p- c- \7 E& |. D$ ABSTR bstrText = strVar.bstrVal; 3 d* ]$ v/ T4 e& W5 e8 }; y- J
, G; s5 x, }6 j( |& c
方法三,使用_bstr_t,这是一种最简单的方法。例如:
6 k% W+ o/ x; u0 T5 r: O6 u I8 R2 E- T/ I% |0 ^
BSTR bstrText = _bstr_t("This is a test"); , F' E& x7 o7 }9 N! i+ g
8 F3 l/ o$ ~" m& p- P: C 方法四,使用CComBSTR。例如:! z+ {1 P+ b! B0 y' R7 J
, i) k1 H8 \7 t" @; l. N: {BSTR bstrText = CComBSTR("This is a test");
, C: z, o, D5 E* R4 G
: r8 l. S1 v2 R+ T! z 或
0 I1 |% H! _% s. @9 B1 @& _) I/ D: H8 d5 |5 r( u% R* E
CComBSTR bstr("This is a test");
+ Y0 Y7 o, H8 bBSTR bstrText = bstr.m_str; 3 ]' v N) g5 P5 C# U4 a
* [* {4 ]0 i6 v" L& u# m9 m" H+ H 方法五,使用ConvertStringToBSTR。例如:
' q8 _' m" o x, o1 p* \9 t0 U1 m: x5 X5 C8 Z5 \: ]: i+ l
char* lpszText = "Test";
% r- O) }- S5 }BSTR bstrText = _com_util::ConvertStringToBSTR(lpszText);
& x7 c: B! t3 B, V* q7 e+ g" A/ ~+ I0 m) _7 B1 P) i0 K% l
(5) CString转换成BSTR
+ `: K0 P- k( s, ]
) E& d, D) f4 C" v 通常是通过使用CStringT::AllocSysString来实现。例如: A* x2 k( T! @0 } M# {& b- G
- B: V- w6 _; D5 [
CString str("This is a test");
7 D7 F7 W/ \1 R( U: EBSTR bstrText = str.AllocSysString();0 a. T" J# Q7 S
…6 S% F4 j; X) `" e9 v9 h
SysFreeString(bstrText); // 用完释放 a( P3 ~" Z! D# p
2 V2 H+ _& i( _, f5 N3 a
(6) BSTR转换成CString
* i3 s, y6 U( T
7 {9 x9 H4 V0 g- I 一般可按下列方法进行:8 o, h$ t9 U. M" Z8 k) x
! w; j/ b+ Y5 e0 K) g. x3 H/ [BSTR bstrText = ::SysAllocString(L"Test");
; z# j5 x6 l. y" N8 I* f l1 d5 `CStringA str;
6 q6 s7 g3 r6 d- ^$ u& ~7 ]1 Ystr.Empty();! s- J, G/ n5 W: U$ H
str = bstrText; : }0 {& m S- l. |
5 C) M! g: p, p' [ 或
4 f5 C! w/ K( e3 C) j, R" x" ~8 u6 f$ v
CStringA str(bstrText);
0 m7 m# Q* A( \5 U5 j
- O9 P+ U9 s3 I+ c7 N7 l; N (7) ANSI、Unicode和宽字符之间的转换
Y# x5 g: \& X$ ^# x
& y+ E5 T, W* D5 W6 p 方法一,使用MultiByteToWideChar将ANSI字符转换成Unicode字符,使用WideCharToMultiByte将Unicode字符转换成ANSI字符。
) l( U. D9 `) N9 `+ A1 U- g8 P3 ^4 E4 P" } L; J) y' z. _
方法二,使用“_T”将ANSI转换成“一般”类型字符串,使用“L”将ANSI转换成Unicode,而在托管C++环境中还可使用S将ANSI字符串转换成String*对象。例如:. W* m; U: n" I% ?7 W, A( ]' p
- d% m( a9 Q( W/ }TCHAR tstr[] = _T("this is a test");* o9 M' F i" f8 ?1 o3 l" o
wchar_t wszStr[] = L"This is a test";
1 ~1 j/ ]* U5 B1 XString* str = S”This is a test”;
/ j y' F1 ]8 A* o. {& Q- l. R8 L; Z# Q8 W5 T6 e" ?1 {! W3 U
方法三,使用ATL 7.0的转换宏和类。ATL7.0在原有3.0基础上完善和增加了许多字符串转换宏以及提供相应的类,它具有如图3所示的统一形式:; V* v1 ^- N0 ~' d" k: e! y" ^, L
0 r% @3 _9 ~6 j) ~0 v* i' A- {
其中,第一个C表示“类”,以便于ATL 3.0宏相区别,第二个C表示常量,2表示“to”,EX表示要开辟一定大小的缓冲。SourceType和DestinationType可以是A、T、W和OLE,其含义分别是ANSI、Unicode、“一般”类型和OLE字符串。例如,CA2CT就是将ANSI转换成一般类型的字符串常量。下面是一些示例代码:
3 r+ h- O* `- j8 t4 b3 q1 S% M$ \" ^, g% q
LPTSTR tstr= CA2TEX<16>("this is a test");- ~. l# R) J( f0 C4 v: O; x" i" \
LPCTSTR tcstr= CA2CT("this is a test");( d) C0 j& b) B% T
wchar_t wszStr[] = L"This is a test";
/ O( M3 b- W. D' S5 \) b" fchar* chstr = CW2A(wszStr);
( [8 Y' d3 n* p5 L
7 O P/ v4 d6 w$ f4 G- R 六、结语
4 E, x8 f* D2 e7 u/ ?8 w9 B* y5 s: ]& Y- R. N
几乎所有的程序都要用到字符串,而Visual C++.NET由于功能强大、应用广泛,因而字符串之间的转换更为频繁。本文几乎涉及到目前的所有转换方法。当然对于.NET框架来说,还可使用Convert和Text类进行不同数据类型以及字符编码之间的相互转换。 |
|