|
|
2002-12-05· ·丁有和··yesky
* @+ U+ P6 u5 P* d ; K# e# h0 |! D! V
: C" v! R5 e# e0 u C# M. A# g) W
Visual C++.NET涉及到ATL/ATL Server、MFC和托管C++等多种编程方式,不仅功能强大而且应用广泛。在编程中,我们常常会遇到ANSI、Unicode以及BSTR不同编码类型的字符串转换操作。本文先介绍基本字符串类型,然后说明相关的类,如CComBSTR、_bstr_t、CStringT等,最后讨论它们的转换方法,其中还包括使用最新ATL7.0的转换类和宏,如CA2CT、CA2TEX等。+ _0 o4 s, i, B- w
$ H9 d/ G# J: ^
一、BSTR、LPSTR和LPWSTR1 f' M9 `' n. }4 L* {
. l8 X l2 ? Y
在Visual C++.NET的所有编程方式中,我们常常要用到这样的一些基本字符串类型,如BSTR、LPSTR和LPWSTR等。之所以出现类似上述的这些数据类型,是因为不同编程语言之间的数据交换以及对ANSI、Unicode和多字节字符集(MBCS)的支持。: V8 Y$ {* ]; ^: ~( c5 J
; m0 S; n% ]* _" C0 |3 g+ G
那么什么是BSTR、LPSTR以及LPWSTR呢?) Q% J* @6 d9 n, Y9 b4 y. F' |
' w. \7 u; z- n% q1 C. w BSTR(Basic STRing,Basic字符串)是一个OLECHAR*类型的Unicode字符串。它被描述成一个与自动化相兼容的类型。由于操作系统提供相应的API函数(如SysAllocString)来管理它以及一些默认的调度代码,因此BSTR实际上就是一个COM字符串,但它却在自动化技术以外的多种场合下得到广泛使用。图1描述了BSTR的结构,其中DWORD值是字符串中实际所占用的字节数,且它的值是字符串中Unicode字符的两倍。+ I& F3 F9 i0 Z1 U
7 j! g8 c$ v4 b5 P d2 `, m- y LPSTR和LPWSTR是Win32和VC++所使用的一种字符串数据类型。LPSTR被定义成是一个指向以NULL(‘\0’)结尾的8位ANSI字符数组指针,而LPWSTR是一个指向以NULL结尾的16位双字节字符数组指针。在VC++中,还有类似的字符串类型,如LPTSTR、LPCTSTR等,它们的含义如图2所示。
8 K& W) n" z. w# w* q5 s6 {7 Q; n. S: U4 z" [* I- k
例如,LPCTSTR是指“long pointer to a constant generic string”,表示“一个指向一般字符串常量的长指针类型”,与C/C++的const char*相映射,而LPTSTR映射为 char*。2 j0 R4 O' w% r9 Q; m6 `5 h! X
: M+ ^* b' S3 E! v 一般地,还有下列类型定义:
' o$ {4 ?, o9 I1 _- S& [% _
% @. n/ f3 b2 O" R+ `#ifdef UNICODE
0 U( u( N2 E7 y+ l; p( @4 H typedef LPWSTR LPTSTR;) q6 p/ T! k7 n2 @8 {7 L
typedef LPCWSTR LPCTSTR;
% N" {# W- [, h2 n8 R) _' L#else ! a2 ]$ w: g# g/ Q2 Q" c( [+ g
typedef LPSTR LPTSTR; 6 q) t7 u* }# V! s6 P
typedef LPCSTR LPCTSTR;
" ^% {' J! G/ f" }% j( |#endif ! E4 O' t8 n/ O! D7 h& L- t$ I
/ G9 V" d: n- ]3 T" D% w) X- h# P
二、CString、CStringA 和 CStringW$ m! h, ]$ z0 G
( D% v1 c% _5 L c 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应用程序中经常用到,这里不再重复。5 V8 J' w5 C9 S, ` o4 h
c' ~8 m" n6 m; R" y) F. @! m
三、VARIANT、COleVariant 和_variant_t
" ~/ B. ]1 c4 F0 [( @
- u- t- E/ [1 j# ` 在OLE、ActiveX和COM中,VARIANT数据类型提供了一种非常有效的机制,由于它既包含了数据本身,也包含了数据的类型,因而它可以实现各种不同的自动化数据的传输。下面让我们来看看OAIDL.H文件中VARIANT定义的一个简化版:9 S) t- `; K+ y( @$ c
" W, h$ z$ `: {
struct tagVARIANT {) v6 p2 _3 q4 X; q: c
VARTYPE vt;2 y) t! E$ k( z! O4 b8 n
union {1 A. x- p' b* e2 c
short iVal; // VT_I2.
6 s7 ]/ w2 j( h: y" _5 @! v5 o( J long lVal; // VT_I4.- r" d1 x. c6 e$ X: N5 `0 |* @& Z
float fltVal; // VT_R4.
& }0 y. w" g. j& D$ v4 z. Z double dblVal; // VT_R8.
) P/ P7 O& Y( A DATE date; // VT_DATE.7 \; `, f, b- o* T1 B4 S
BSTR bstrVal; // VT_BSTR.
R% p' Z0 K |5 ^/ H" Y1 p/ [+ A …
) w J1 s6 @) M( J0 U1 E1 Y) a short * piVal; // VT_BYREF|VT_I2.5 P% J h" r2 ?, @; V P! B
long * plVal; // VT_BYREF|VT_I4.
{ X: M/ d# [ float * pfltVal; // VT_BYREF|VT_R4.
$ B( e: _! ?) C5 h; n double * pdblVal; // VT_BYREF|VT_R8.
" y& Y% {2 V. a z" B DATE * pdate; // VT_BYREF|VT_DATE.
% S; U1 i4 ?' Q BSTR * pbstrVal; // VT_BYREF|VT_BSTR.6 ~7 e, R6 a# r3 K: F# q
};
. C9 h$ v8 o# y+ }" `};
h9 |/ g( n6 |& g6 I% h% q0 k
0 F+ b8 Y' f9 e9 |: ~, Y1 L 显然,VARIANT类型是一个C结构,它包含了一个类型成员vt、一些保留字节以及一个大的union类型。例如,如果vt为VT_I2,那么我们可以从iVal中读出VARIANT的值。同样,当给一个VARIANT变量赋值时,也要先指明其类型。例如:
6 t9 c1 ?; H# k2 u9 |) J6 P8 t% N9 C0 u
# {: m( X, F4 z- x8 z% BVARIANT va;
$ C4 ^0 A1 i1 T9 c2 F:: VariantInit(&va); // 初始化
2 y1 e$ F+ z0 O% v7 Yint a = 2002;' X, D/ {, U1 E" b8 X6 y. F! v
va.vt = VT_I4; // 指明long数据类型& r4 @9 n) G8 A5 @
va.lVal = a; // 赋值
6 Q) R# c. @; O J' _# y
, r) T- ?/ p% t8 n; \9 P 为了方便处理VARIANT类型的变量,Windows还提供了这样一些非常有用的函数:, i6 ]8 [- x( D# w* [0 g
8 g1 s, z- g$ \4 S
VariantInit —— 将变量初始化为VT_EMPTY;
* O0 I* @% ~: I2 h1 |! q F, E* c/ k8 [! t# R5 D4 r
VariantClear —— 消除并初始化VARIANT;3 q- r9 \( ~) X
" u: ?* m& Y* k) C+ C6 s& s VariantChangeType —— 改变VARIANT的类型;6 o' ]7 H3 b2 P
9 {. O* d7 s3 \" H& D- U. _ VariantCopy —— 释放与目标VARIANT相连的内存并复制源VARIANT。
+ \( K w, F' c. v7 T7 ~! W; C" \ S1 [/ Z
COleVariant类是对VARIANT结构的封装。它的构造函数具有极为强大大的功能,当对象构造时首先调用VariantInit进行初始化,然后根据参数中的标准类型调用相应的构造函数,并使用VariantCopy进行转换赋值操作,当VARIANT对象不在有效范围时,它的析构函数就会被自动调用,由于析构函数调用了VariantClear,因而相应的内存就会被自动清除。除此之外,COleVariant的赋值操作符在与VARIANT类型转换中为我们提供极大的方便。例如下面的代码:
- L$ R& J# V" R k! G2 |8 k8 e: k6 K$ S+ ]& m
COleVariant v1("This is a test"); // 直接构造
9 X; J$ Z0 G! m& G9 pCOleVariant v2 = "This is a test"; 3 n3 r! f( e K/ B5 H/ i6 `/ o
// 结果是VT_BSTR类型,值为"This is a test"
4 D0 N' |# w" K. T# [, o3 z ~COleVariant v3((long)2002);
: x* U4 l- w5 i1 {- w9 ?COleVariant v4 = (long)2002;
8 J) `8 X w' T8 X7 P. V$ u+ i// 结果是VT_I4类型,值为2002 ; J) K: M$ G( h5 @! D
3 {7 `) `9 i. p' T% x: ]. w
_variant_t是一个用于COM的VARIANT类,它的功能与COleVariant相似。不过在Visual C++.NET的MFC应用程序中使用时需要在代码文件前面添加下列两句:7 n+ z( {- D U$ k( z* w, W: e
. @- G: y1 k% A& M
#include "comutil.h"
! @6 W3 i+ ~: \: E! k3 Q3 F" u( f* ~) M( P. J8 t0 t! N
#pragma comment( lib, "comsupp.lib" )) w" L# b+ X, j+ V
; W5 M, Q/ Z" y- B/ `
四、CComBSTR和_bstr_t, j5 [3 Q% ^+ B, X; S. v
{% n; Q( L+ i; B d6 ^+ H CComBSTR是对BSTR数据类型封装的一个ATL类,它的操作比较方便。例如:
7 ^. g- e9 o: D: q: V- W/ N. C% t ~+ o% y2 H& ^. l0 N4 u
CComBSTR bstr1; 6 B: E. E3 J3 j: t M; C8 V
bstr1 = "Bye"; // 直接赋值' |) j4 R) S4 b' D9 Z$ X6 u
OLECHAR* str = OLESTR("ta ta"); // 长度为5的宽字符
. P; Z5 R. Q ^" JCComBSTR bstr2(wcslen(str)); // 定义长度为54 }4 [0 U3 F$ j) Y6 [
wcscpy(bstr2.m_str, str); // 将宽字符串复制到BSTR中6 W1 _1 R! k H& B+ a! _
CComBSTR bstr3(5, OLESTR("Hello World")); 7 q4 ?1 s! b+ D# f' e1 D9 _
CComBSTR bstr4(5, "Hello World"); ; i2 @+ n2 K" N
CComBSTR bstr5(OLESTR("Hey there")); 2 Y) ^( h; z0 ` ~
CComBSTR bstr6("Hey there"); 8 @, l6 g0 A, H
CComBSTR bstr7(bstr6); 5 \/ z& P# G5 m/ c9 O4 s+ R
// 构造时复制,内容为"Hey there" 1 L% W, O" |0 x, }& p, e0 B1 n
1 x' D, m7 I4 A
_bstr_t是是C++对BSTR的封装,它的构造和析构函数分别调用SysAllocString和SysFreeString函数,其他操作是借用BSTR API函数。与_variant_t相似,使用时也要添加comutil.h和comsupp.lib。. p7 K( r' {. F2 S, v3 y, c5 M
/ p B- |4 o: z4 X
五、BSTR、char*和CString转换
+ K& N$ a F% C7 G- J) T0 T+ R6 ^
" P- b, V. b7 T, [) b (1) char*转换成CString
& A% C3 q6 \$ q, j" g
! v0 G+ @0 D: ~! Q d 若将char*转换成CString,除了直接赋值外,还可使用CString::Format进行。例如:& ^' d) W/ B7 ]# q x( C1 e; p
6 H4 \7 [. Y' `' h; Z
char chArray[] = "This is a test";8 |: M! q5 ]0 {9 H0 n T
char * p = "This is a test";
9 U% m/ }. v9 u1 b' u. H, J, \1 d5 y, z+ y3 v+ q
或
9 \' R5 h9 c2 i4 K7 ~1 {# d! n# E+ G/ q( X+ } d
LPSTR p = "This is a test"; ; P8 d, I5 W6 D( C+ x+ U
/ }- o) b& }6 ~7 M5 { 或在已定义Unicode应的用程序中3 {# `8 |% t' b# M. A$ F6 [
. x8 v* C5 Q1 V& g
TCHAR * p = _T("This is a test");
5 A* c; K9 n4 i$ g5 U" x/ Z$ I0 L3 P& X9 M
或9 u; \2 h8 \$ S8 n
" x5 y8 o- |* z7 I* K5 ~6 Z; {LPTSTR p = _T("This is a test");
' q1 C. J( g9 m& w" j `CString theString = chArray;
& r" P0 W0 f, y- H! P; TtheString.Format(_T("%s"), chArray);# h% S9 B0 \5 j$ u
theString = p; 3 M: t% u+ ?" V/ L" _
, z2 v$ \' c8 q6 n0 B3 o P (2) CString转换成char* u" F1 E0 I1 k4 i9 y# S
6 V3 ~5 [4 d7 r: ^/ g7 v
若将CString类转换成char*(LPSTR)类型,常常使用下列三种方法:
- ^) H0 Z* A1 _ C2 O1 ~8 Y9 ^! ` b9 L+ Q& d% u/ k& }
方法一,使用强制转换。例如:. \! m6 ] F+ z0 }# b3 f9 x
5 [+ R" `* q& v
CString theString( "This is a test" );1 E D. N6 t2 \. a
LPTSTR lpsz =(LPTSTR)(LPCTSTR)theString;
& q6 `& z# Q/ n4 f
/ v/ |9 y3 P* b 方法二,使用strcpy。例如:
. P, c; a# s$ Z3 a! c$ ]1 O. b
& j# E' t5 i4 }+ o4 B uCString theString( "This is a test" );* n) i' g3 a, H+ x8 G5 Z: f# U% N
LPTSTR lpsz = new TCHAR[theString.GetLength()+1];
# w; [. q: K W_tcscpy(lpsz, theString);
; r. t) y% d5 h, Q# E+ K: X0 T4 N2 D. ]0 r$ `
需要说明的是,strcpy(或可移值Unicode/MBCS的_tcscpy)的第二个参数是 const wchar_t* (Unicode)或const char* (ANSI),系统编译器将会自动对其进行转换。
7 u' @' q8 {4 `3 I$ y& [7 l" X% g. `6 i" E% d7 U# V
方法三,使用CString::GetBuffer。例如:
. j: A- E& e/ ~) I! [1 F7 F1 t) Q7 r, L% a0 ^( C
CString s(_T("This is a test "));
# H1 q) Z" V& V* K6 f- OLPTSTR p = s.GetBuffer();- a; L5 H% Z+ C, w! e. J
// 在这里添加使用p的代码4 l6 V: O% D' J! c7 N" n, w, C2 P
if(p != NULL) *p = _T('\0');3 o0 A( N" ^5 K6 |: \' H
s.ReleaseBuffer();
9 t: W" g% n& |$ }* @3 L& V7 _+ J// 使用完后及时释放,以便能使用其它的CString成员函数
: x `* R W& u+ v. G' j* s/ W" V* \* K+ ~9 k0 l- }4 O4 [
(3) BSTR转换成char*! h4 Z# u' R" Y) o: T/ \; w7 s; o
4 D/ ~) l- @, j9 H' ^, ]; B) q 方法一,使用ConvertBSTRToString。例如:( {( E. L) D1 Q' g$ ?0 A
' H" H" w0 }4 B0 C) j#include K9 s* u% U' c0 A& {; N2 f8 d
#pragma comment(lib, "comsupp.lib")4 f/ N+ `5 `9 b6 w2 ^- B
int _tmain(int argc, _TCHAR* argv[]){; }' N* C+ `( i6 ?5 N! c& ~
BSTR bstrText = ::SysAllocString(L"Test");
. i$ N4 n7 v5 U$ P( O; o, p+ G ~char* lpszText2 = _com_util::ConvertBSTRToString(bstrText);- {( a a. A/ Y+ b* E2 \
SysFreeString(bstrText); // 用完释放. h8 e8 W1 o4 N& c* T5 y1 z" V2 U
delete[] lpszText2;
) e- N8 |: ^$ y7 \2 Y r% jreturn 0;
# g- x3 ^ e4 y; y* e9 C* I- L}
|( x1 [8 K! R1 _. i8 |4 j" b; f
/ b3 \8 I2 ~" G$ l) L 方法二,使用_bstr_t的赋值运算符重载。例如:
, r9 d& l: e9 Y
. _3 p2 `: X0 `& D) I; v_bstr_t b = bstrText;$ ?+ H. _* V4 c# l+ ^
char* lpszText2 = b; - \ b' d0 C3 x& I0 l
6 H- f1 Y1 I7 V# I( U9 X (4) char*转换成BSTR2 ^- d- Y0 [" R/ Y) c. x/ y% C" B
4 D/ X5 J) Q, |! M 方法一,使用SysAllocString等API函数。例如:: f( r0 e+ e" ]
, b- ?: ], x( I/ W/ R9 ^" C! W
BSTR bstrText = ::SysAllocString(L"Test");
. y: E# D3 p3 Z s4 M3 m% M2 aBSTR bstrText = ::SysAllocStringLen(L"Test",4);: v) S+ F" ]/ e) Z: B+ D& ]
BSTR bstrText = ::SysAllocStringByteLen("Test",4);
4 R# w. n, `( ~- b3 S% K5 w6 Q- V
方法二,使用COleVariant或_variant_t。例如:
3 t0 _; m, I6 b% w
, O" \" w) R9 v8 L2 J8 T+ x4 f//COleVariant strVar("This is a test");+ Y- h1 Z! t+ s' _
_variant_t strVar("This is a test");
8 _# `( M# F, _. c @; y, O) {BSTR bstrText = strVar.bstrVal;
8 p- ?, Q5 b% J; l4 |
) B0 v" w# ]8 i 方法三,使用_bstr_t,这是一种最简单的方法。例如:
. S. z+ [& P V# I' K [ C
, \" g! Q* D0 _. D% JBSTR bstrText = _bstr_t("This is a test");
2 F5 ]7 Y8 g' [3 }, y4 }' W7 o; q8 f- Y% p
方法四,使用CComBSTR。例如: Y {7 K! e5 D& J5 d' w1 l+ k
% p9 r' @: r7 z9 O5 [! ^# q1 D
BSTR bstrText = CComBSTR("This is a test"); , r8 s4 O. `: E& x/ d0 @( Q4 l
- |' ?, I$ ?+ C1 X. j) z 或
3 W2 Z! A: o; V# t& m) i9 Z- D! I* i, G% p% }4 I
CComBSTR bstr("This is a test");% h( ^! R! D, g4 }, ^& T
BSTR bstrText = bstr.m_str;
9 |) t: K$ J! \- P7 G! @% } G% w( k! f; w9 D- t4 U) S; I! ~
方法五,使用ConvertStringToBSTR。例如:& A: c, y+ }+ b3 m; t* k) m
# @/ J+ |! K8 Z# T7 e7 fchar* lpszText = "Test";' h- {3 v! {% j! c6 ]0 `! I. a
BSTR bstrText = _com_util::ConvertStringToBSTR(lpszText);
: H9 }" u$ B$ X* J3 W
! v" ]* b5 P3 g' `6 x4 X (5) CString转换成BSTR
/ _) ` n$ y1 P' u) l" ~" x2 V
* n# e. d: @! Z: I* [. b X/ N0 m8 L 通常是通过使用CStringT::AllocSysString来实现。例如:
: l3 s. k# U; m
8 z$ d1 [) G# c, i RCString str("This is a test");
/ D5 H+ T- ^: c$ d6 hBSTR bstrText = str.AllocSysString();9 V6 q, E3 c% x# J' O
…
$ v/ h/ a e5 o, @9 U7 Q z5 [+ y- gSysFreeString(bstrText); // 用完释放
( {9 y: h; e; R0 M
x' {+ t2 W4 X* ]5 y8 l2 p' j* Z& t (6) BSTR转换成CString# R$ q' b% [3 R3 k G& L% K: A5 x
7 K* R( ?4 }7 g4 p- ?5 @ 一般可按下列方法进行:
1 D% e+ v- ^! l$ F! ~- t9 @" l: X D' j6 l; I+ _% r; R1 [
BSTR bstrText = ::SysAllocString(L"Test");
" K, j5 z. t6 K) ]' c4 X2 }CStringA str;" i# e7 w- m9 W5 {2 i
str.Empty();
7 h( y2 C$ `$ W* A( pstr = bstrText; 9 e2 H/ s/ \. I6 N
4 c' B }( a5 l' f0 x& [2 s 或: ?) K5 }; ]* e4 E" P. u3 E
/ v" |/ u- H) o; K3 jCStringA str(bstrText); + u' |. {* c, X" e& d: {
8 m3 F7 B4 c# E5 R3 q (7) ANSI、Unicode和宽字符之间的转换
9 C9 c% X/ \+ z5 w2 c# p2 V1 S2 t- [. u
方法一,使用MultiByteToWideChar将ANSI字符转换成Unicode字符,使用WideCharToMultiByte将Unicode字符转换成ANSI字符。, b4 }+ V& s' ^6 c
* ?) n3 ]' T2 k4 [0 M( \ 方法二,使用“_T”将ANSI转换成“一般”类型字符串,使用“L”将ANSI转换成Unicode,而在托管C++环境中还可使用S将ANSI字符串转换成String*对象。例如: d9 n7 C j. }7 N
& e0 ?8 S" p4 o- T( X
TCHAR tstr[] = _T("this is a test");& \4 u- i9 G2 N! I- W
wchar_t wszStr[] = L"This is a test";; ~6 P& A& N ?$ m; F9 _$ L; Z
String* str = S”This is a test”; , G( L' r8 p8 ?9 ~0 f; q& u
* V& z s, y6 I9 X5 r
方法三,使用ATL 7.0的转换宏和类。ATL7.0在原有3.0基础上完善和增加了许多字符串转换宏以及提供相应的类,它具有如图3所示的统一形式:9 K7 V. E3 f$ O
1 p/ I {4 {) Z) N! u 其中,第一个C表示“类”,以便于ATL 3.0宏相区别,第二个C表示常量,2表示“to”,EX表示要开辟一定大小的缓冲。SourceType和DestinationType可以是A、T、W和OLE,其含义分别是ANSI、Unicode、“一般”类型和OLE字符串。例如,CA2CT就是将ANSI转换成一般类型的字符串常量。下面是一些示例代码:
. `8 V% l+ @# C- ~. p8 c. y1 n4 Q/ r& p/ [- J
LPTSTR tstr= CA2TEX<16>("this is a test");
3 Z; o9 y6 `. U& U+ w4 qLPCTSTR tcstr= CA2CT("this is a test");% _- A7 p1 Q' I% U
wchar_t wszStr[] = L"This is a test";
7 Z! ~* x- d ~, \char* chstr = CW2A(wszStr); 0 J0 D7 r$ A4 W% e* |
" w4 ]) H+ Z0 G% E, n 六、结语
! Z) M0 u' c3 c5 G! b3 C9 X
9 z6 ~) m7 k$ O K- z 几乎所有的程序都要用到字符串,而Visual C++.NET由于功能强大、应用广泛,因而字符串之间的转换更为频繁。本文几乎涉及到目前的所有转换方法。当然对于.NET框架来说,还可使用Convert和Text类进行不同数据类型以及字符编码之间的相互转换。 |
|