9 Y5 O P5 ?! c6 s, _9 g l! n // Add on a backslash if it wasn''t present in the registry value.) N9 r$ R% R4 A" R$ a. `
// First, get a pointer to the terminating zero.# b4 Y# C1 A+ Y/ y; b, N3 _/ v
char* pLastChar = strchr ( szConfigFilename, ''\0'' );' R, B) s0 B& W a& p7 Q. I. L* }; S
+ i; S* R: r+ Q2 D6 ]4 c
// Now move it back one character. % x) m/ @5 ^8 ]: W pLastChar--; ; Q( h# F9 ~2 v5 k + E; _7 R5 k+ k# T- K
if ( *pLastChar != ''\\'' ) . W& h9 X5 x/ z4 ?% F% [ strcat ( szConfigFilename, "\\" );6 m. [0 B# `& D+ `7 ]7 c3 q
$ t. w, ]9 `6 X5 ]% M' u // Add on the name of the config file.1 ], T7 P3 ]" ~/ P# `
strcat ( szConfigFilename, "config.bin" );2 d1 z0 o" j% a' s4 u ^9 m# {) h
6 c: N6 o* K$ s/ Y
// If the caller''s buffer is big enough, return the filename.5 r2 H, A. J: o7 g8 ~
if ( strlen ( szConfigFilename ) >= nBuffSize )3 w% ~9 w8 i G1 @! v5 ?
return false;* r' J" ~( ?* v; f$ A/ R; }0 W4 h
else ( H6 g( v) y9 ^7 M {8 {: ~: Y6 o! S2 ]5 d
strcpy ( pszName, szConfigFilename );; b+ V* @$ S0 J5 A: I5 ?7 p, R* A/ O
return true; : E7 s5 |( S; R Y5 j4 Y' u } % b/ g% N% x0 {5 r& ?) S; }. j6 U) O} </PRE> 这是一段很健壮的代码,然而在遇到 DBCS 字符时它将会出错。让我们来看看为什么。假设一个日本用户使用了你的程序,把它安装在 C:\<IMG src="http://www.vckbase.com/document/journal/vckbase30/images/youkoso.gif" border=0>。下面是这个名字在内存中的存储形式: 4 H: Y! B: o) ^" Z0 S ' Z: P$ t# t! H f7 k* B( ~- ]0 X4 K
<TABLE> - X W. o7 B- c z! O8 r7 O" H; x1 s- J+ G i
<TR>5 T: g7 w8 N# B7 v: u- b6 d
<TD align=middle width="12%">43</TD> 3 w. D2 u( E- L' c5 b- l5 F<TD align=middle width="12%"><FONT color=#990000>3A</FONT></TD>+ o5 T* m9 T" r' G6 |9 ?. a
<TD align=middle width="12%"><FONT color=#0000ff>5C</FONT></TD> ' k) N/ p/ C l/ o<TD align=middle width="12%">83 88</TD># y7 I) ~( ]% d0 X- p
<TD align=middle width="13%">83 45</TD>! O: _/ i1 e' C' r
<TD align=middle width="13%">83 52</TD> * ~) y. s# N6 H. k+ q5 [1 ]! R1 P7 P<TD align=middle width="13%">83 <FONT color=#0000ff>5C</FONT></TD> * v/ P9 C6 |6 ]0 O o ^<TD align=middle width="13%">00</TD></TR> ]5 H! a2 W3 h' L; w<TR> & u. K t5 p- L! Z5 X<TD align=middle width="12%"> </TD>5 G4 m8 @" \$ R( m) }6 h
<TD align=middle width="12%"> </TD>) N3 h1 n; G! _/ f& ?' S
<TD align=middle width="12%"> </TD>3 {7 J% ~) ~$ G( f6 W0 m
<TD align=middle width="12%"><FONT color=#990000>LB TB </FONT></TD> * _0 l& p- ^6 ?% x<TD align=middle width="13%"><FONT color=#990000>LB TB </FONT></TD> 1 d$ W6 r) I4 Z7 ], j& V<TD align=middle width="13%"><FONT color=#990000>LB TB </FONT></TD> 1 H! r4 F2 G. T% F3 O* V$ Y/ v<TD align=middle width="13%"><FONT color=#990000>LB TB </FONT></TD>, G% e q* }/ S3 Z7 D+ \1 b% r& f
<TD align=middle width="13%"> </TD></TR> 8 |7 z2 W" G4 W" T# u/ p9 F0 u<TR>5 A2 _2 y2 B ~# T" |- |
<TD align=middle width="12%">C</TD> % _ F. h! c, P/ ?* a<TD align=middle width="12%">:</TD> ) ?0 }3 \9 g2 t+ ?- N/ C<TD align=middle width="12%">\</TD> # e3 O4 m. d% ^1 z2 e$ e<TD align=middle width="12%"><IMG src="http://www.vckbase.com/document/journal/vckbase30/images/yo.gif" border=0></TD> ' K# B, o7 g0 P( C0 z) \( y; k+ @<TD align=middle width="13%"><IMG src="http://www.vckbase.com/document/journal/vckbase30/images/u.gif" border=0></TD> S0 ]: q, I3 B3 T1 v r8 w<TD align=middle width="13%"><IMG src="http://www.vckbase.com/document/journal/vckbase30/images/ko.gif" border=0></TD> . }) [; r- F) A, h. Z9 x( x' e<TD align=middle width="13%"><IMG src="http://www.vckbase.com/document/journal/vckbase30/images/so.gif" border=0></TD> - G8 d1 {& {3 }9 r<TD align=middle width="13%">EOS</TD></TR></TABLE> % m5 J% n8 F7 N+ |3 k<> 当使用 GetConfigFileName() 检查尾部的''\\''时,它寻找安装目录名中最后的非0字节,看它是等于''\\''的,所以没有重新增加一个''\\''。结果是代码返回了错误的文件名。( }6 X4 b, m7 ]" I. N9 b( Q
哪里出错了呢?看看上面两个被用蓝色高量显示的字节。斜杠''\\''的值是0x5c。'' ''的值是83 5c。上面的代码错误的读取了一个 trail byte,把它当作了一个字符。6 t; p; Z( @0 X8 p9 `
正确的后向遍历方法是使用能够识别DBCS字符的函数,使指针移动正确的字节数。下面是正确的代码。(指针移动的地方用红色标明) </P><RE>bool FixedGetConfigFileName ( char* pszName, size_t nBuffSize )+ ^2 K" k+ T3 A) k! C6 P, b
{ * F3 g, p8 A+ U1 }: q char szConfigFilename[MAX_PATH]; - S) S) K2 \* R( X* |/ M8 ^ 7 J& b8 [! T" C // Read install dir from registry... we''ll assume it succeeds. J c. c }3 Z2 \- A3 t
7 M2 I: N! e4 a/ Y# R+ W // Add on a backslash if it wasn''t present in the registry value. # h3 s8 X4 Q8 q7 |, j& |( a // First, get a pointer to the terminating zero.9 f( E# d1 X9 m, x- v; L2 C
char* pLastChar = _mbschr ( szConfigFilename, ''\0'' );: b) Z* w9 p0 b
+ q3 k; g/ C% k2 J; o
// Now move it back one double-byte character. - M: x8 r4 N' t- r0 L3 U" Q <FONT color=#ff0000> pLastChar = CharPrev ( szConfigFilename, pLastChar );</FONT> 5 Q# x% D( h6 J5 q- o; a) Q ) ?; C$ X9 i; c) G3 B if ( *pLastChar != ''\\'' )7 f0 t3 I! r! j, T, B9 m( Z" \
_mbscat ( szConfigFilename, "\\" ); : A8 b; q' Q/ D 1 E+ V0 B8 {2 _8 q! z
// Add on the name of the config file. ; x8 j( f' N( ~8 f1 g _mbscat ( szConfigFilename, "config.bin" ); ! e( a! d- C% Z0 ]; f ! L0 N. S: V9 d* Q+ l) D n) N3 j // If the caller''s buffer is big enough, return the filename. 4 K l, ]0 N; M) B if ( _mbslen ( szInstallDir ) >= nBuffSize ) & A% G# e( A1 Z: s return false;' ]9 p0 \% @$ e( c. k2 b9 d
else' L( C% ]9 F, _1 X4 K n
{# l: P. Z& n. H' x+ `. a
_mbscpy ( pszName, szConfigFilename );5 J) Y- U) k: _: D9 Y |
return true;' C/ k6 _, e7 N6 P4 l5 Y7 ~
}4 J; t1 l( q! Z" s2 G1 J
} % }# U. @6 y$ w" t1 V) {</PRE> 上面的函数使用CharPrev() API使pLastChar向后移动一个字符,这个字符可能是两个字节长。在这个版本里,if条件正常工作,因为lead byte永远不会等于0x5c。8 Y9 P2 ^! S7 ^0 ~/ q+ [8 s
让我们来想象一个违背规则1的场合。例如,你可能要检测一个用户输入的文件名是否多次出现了'':''。如果,你使用++操作来遍历字符串,而不是使用CharNext(),你可能会发出不正确的错误警告如果恰巧有一个trail byte它的值的等于'':''的值。 2 R6 h: i3 h; |, e与规则2相关的关于字符串索引的规则:<RE>2a. 永远不要使用减法去得到一个字符串的索引。</PRE> & O7 b% C6 C* r' S; \- J: |5 f<>违背这条规则的代码和违背规则2的代码很相似。例如,</P><RE>char* pLastChar = &szConfigFilename [strlen(szConfigFilename) - 1];</PRE> & j* |& [% u0 x* z<>这和向后移动一个指针是同样的效果。; i% A- _! P* y% r, ^
2 t, I$ {# v* f! {4 C$ i; a" t# v% B<IMG src="http://www.vckbase.com/document/image/paragraph.gif"><B> 回到关于str***()和_mbs***()的区别</B> , G4 A1 x2 D/ [& w, w; o* I6 ^( j4 P" m) S$ C* a1 A& e0 z; B
现在,我们应该很清楚为什么_mbs***()函数是必需的。Str***()函数根本不考虑DBCS字符,而_mbs***()考虑。如果,你调用strrchr("C:\\ ", ''\\''),返回结果可能是错误的,然而_mbsrchr()将会认出最后的双字节字符,返回一个指向真的''\\''的指针。 ! P5 `. c4 G, W 关于字符串函数的最后一点:str***()和_mbs***()函数认为字符串的长度都是以char来计算的。所以,如果一个字符串包含3个双字节字符,_mbslen()将会返回6。Unicode函数返回的长度是按wchar_t来计算的。例如,wcslen(L"Bob")返回3。% ^5 X; {! H( L% _
! G* j' K9 y3 V/ A3 `8 y% T. f$ y
<IMG src="http://www.vckbase.com/document/image/paragraph.gif"><B> Win32 API中的MBCS和Unicode</B> ) k" M+ d; z) m# O# x7 a$ n J1 _5 j2 j ~8 s, `4 ]# [. Y
两组 APIs: : b+ y0 i1 D* O+ H) ]* X 尽管你也许从来没有注意过,Win32中的每个与字符串相关的API和message都有两个版本。一个版本接受MBCS字符串,另一个接受Unicode字符串。例如,根本没有SetWindowText()这个API,相反,有SetWindowTextA()和SetWindowTextW()。后缀A表明这是MBCS函数,后缀W表示这是Unicode版本的函数。3 {+ t$ \" N, d3 @1 A; U
当你 build 一个 Windows 程序,你可以选择是用 MBCS 或者 Unicode APIs。如果,你曾经用过VC向导并且没有改过预处理的设置,那表明你用的是MBCS版本。那么,既然没有 SetWindowText() API,我们为什么可以使用它呢?winuser.h头文件包含了一些宏,例如: </P><RE>BOOL WINAPI SetWindowTextA ( HWND hWnd, LPCSTR lpString );3 F; X( R. H$ X- Y( X
BOOL WINAPI SetWindowTextW ( HWND hWnd, LPCWSTR lpString );+ [" d! R+ X: l) t3 e, P2 G) x5 r- w
0 ^/ v+ B n) H' x' V
#ifdef UNICODE 0 d' ~8 V! C) Q: ?4 C#define SetWindowText SetWindowTextW ) O2 ?5 f1 J# A8 {. [0 x) c2 b#else6 ~7 N( {/ Y# E/ a" `
#define SetWindowText SetWindowTextA3 e! U X4 l. W; r
#endif </PRE>当使用MBCS APIs来build程序时,UNICODE没有被定义,所以预处理器看到:<RE>#define SetWindowText SetWindowTextA</PRE>+ ~2 G+ L+ g H. Q! B
<> 这个宏定义把所有对SetWindowText的调用都转换成真正的API函数SetWindowTextA。(当然,你可以直接调用SetWindowTextA() 或者 SetWindowTextW(),虽然你不必那么做。) ! Q3 d0 H/ k0 b$ R" H 所以,如果你想把默认使用的API函数变成Unicode版的,你可以在预处理器设置中,把_MBCS从预定义的宏列表中删除,然后添加UNICODE和_UNICODE。(你需要两个都定义,因为不同的头文件可能使用不同的宏。) 然而,如果你用char来定义你的字符串,你将会陷入一个尴尬的境地。考虑下面的代码:</P><RE>HWND hwnd = GetSomeWindowHandle();( H7 |0 k, y* w6 h# L% m
char szNewText[] = "we love Bob!"; 9 G* T0 x. a- k$ E1 s5 MSetWindowText ( hwnd, szNewText );</PRE>: N. T# d6 D8 R' N) Q* X
<>在预处理器把SetWindowText用SetWindowTextW来替换后,代码变成:</P><RE>HWND hwnd = GetSomeWindowHandle();' c, I$ ^. @) k) H) `2 i" h
char szNewText[] = "we love Bob!";2 E' L4 B% x( d, d
SetWindowTextW ( hwnd, szNewText );</PRE>, F- A% [4 o7 W. i8 d
<> 看到问题了吗?我们把单字节字符串传给了一个以Unicode字符串做参数的函数。解决这个问题的第一个方案是使用 #ifdef 来包含字符串变量的定义:</P><RE>HWND hwnd = GetSomeWindowHandle(); 7 I' l8 P7 P1 }8 [( Z#ifdef UNICODE , c0 c' k/ ?3 Z0 q2 @2 }wchar_t szNewText[] = L"we love Bob!"; ; H" J A/ ~# w |% Z; y7 L#else0 N% Z' m' i% V/ J% u
char szNewText[] = "we love Bob!";8 ?( w. |. R4 [
#endif, `/ u" ~6 S$ v; ]0 E0 r
SetWindowText ( hwnd, szNewText );</PRE>% w/ J7 m( U3 b+ G; \
<>你可能已经感受到了这样做将会使你多么的头疼。完美的解决方案是使用TCHAR. . F: Z" j7 |5 E8 i2 b' u1 A8 O& G+ v- M% Q
<IMG src="http://www.vckbase.com/document/image/paragraph.gif"><B> 使用TCHAR</B>8 Z( |$ L5 p3 J
& Q" n5 s7 @# a& z
TCHAR是一种字符串类型,它让你在以MBCS和UNNICODE来build程序时可以使用同样的代码,不需要使用繁琐的宏定义来包含你的代码。TCHAR的定义如下:</P><RE>#ifdef UNICODE 2 t( t2 E, j5 j/ k: d9 g, F; @typedef wchar_t TCHAR;9 K" w+ l+ `( w. K0 \
#else3 E6 v U2 Z+ t' {: {7 S; L. u. v2 M
typedef char TCHAR; & o6 { e3 R% G#endif</PRE>4 M* V) `% ^) `: t3 z* O
<>所以用MBCS来build时,TCHAR是char,使用UNICODE时,TCHAR是wchar_t。还有一个宏来处理定义Unicode字符串常量时所需的L前缀。</P><RE>#ifdef UNICODE0 V, S9 V9 C+ [# \+ u
#define _T(x) L##x " R7 T8 V' A% y- t#else8 Z( G2 k" z7 p; P* L' n
#define _T(x) x 2 ^. Y# {8 d8 X#endif</PRE>0 `, ?, [9 q- ]- A
<P> ##是一个预处理操作符,它可以把两个参数连在一起。如果你的代码中需要字符串常量,在它前面加上_T宏。如果你使用Unicode来build,它会在字符串常量前加上L前缀。</P><PRE>TCHAR szNewText[] = _T("we love Bob!");</PRE>1 A4 [* {. I& _- r
<P> 像是用宏来隐藏SetWindowTextA/W的细节一样,还有很多可以供你使用的宏来实现str***()和_mbs***()等字符串函数。例如,你可以使用_tcsrchr宏来替换strrchr()、_mbsrchr()和wcsrchr()。_tcsrchr根据你预定义的宏是_MBCS还是UNICODE来扩展成正确的函数,就像SetWindowText所作的一样。 ' ]5 ^( W6 X, F( c 不仅str***()函数有TCHAR宏。其他的函数如, _stprintf(代替sprinft()和swprintf()),_tfopen(代替fopen()和_wfopen())。 MSDN中"Generic-Text Routine Mappings."标题下有完整的宏列表。 . d6 K( O7 G: N+ g ! @ E3 C0 e: B# `7 N<IMG src="http://www.vckbase.com/document/image/paragraph.gif"><B> 字符串和TCHAR typedefs</B>+ N: M" P% a2 D, ^5 ?. `$ r: Y
! q; q; x; w0 J4 T, b+ J
由于Win32 API文档的函数列表使用函数的常用名字(例如,"SetWindowText"),所有的字符串都是用TCHAR来定义的。(除了XP中引入的只适用于Unicode的API)。下面列出一些常用的typedefs,你可以在msdn中看到他们。</P> , m6 w( {6 G- H% ~2 s2 t) C. E<TABLE> % F6 g5 _0 S }9 H- ]) r % `; b! H( p! \% e- _; B<TR> K" B0 K2 F( {! e2 Z<TD align=middle width="16%"><B>type </B></TD>3 ^5 H/ M' T. o6 s0 S
<TD align=middle width="42%"><B>Meaning in MBCS builds </B></TD> ) v- @) z/ h& f" p3 A1 n' e<TD align=middle width="42%"><B>Meaning in Unicode builds</B></TD></TR> 0 v: s8 F0 s% @+ c/ G7 E<TR>( {0 E. Y) u5 x8 d: Z# j+ V1 k0 N
<TD width="16%">WCHAR</TD> e8 [. U/ }* I. s# `
<TD width="42%">wchar_t</TD>. X5 q+ |" g. N: L% Q% _$ J! m) y! N
<TD width="42%">wchar_t</TD></TR> 5 Q. Z) \1 e4 c! s3 d<TR> 8 U8 X: x& X' |4 c3 C {5 J<TD width="16%">LPSTR </TD> 8 j" Q' p( s7 a/ \" ~<TD width="42%">zero-terminated string of char (char*)</TD>7 d% y5 M0 A6 v6 h
<TD width="42%">zero-terminated string of char (char*)</TD></TR>! Y1 g' O1 w$ t- ]# |) o
<TR> - |8 ~2 n6 T# K- D! H<TD width="16%">LPCSTR </TD> 3 m1 D# D2 e0 ]. L4 t; w<TD width="42%">constant zero-terminated string of char (const char*)</TD> 3 Z) W2 [& ?) u$ f! I0 e' Y<TD width="42%">constant zero-terminated string of char (const char*)</TD></TR>% N# `/ W# a, D% ]7 D4 W% N
<TR> ( W# J$ E& Q) e+ |<TD width="16%">LPWSTR</TD> ) W3 m1 u; Y) B5 z: a: D& q e<TD width="42%">zero-terminated Unicode string (wchar_t*) </TD> + K' j2 x% E2 q/ p<TD width="42%">zero-terminated Unicode string (wchar_t*)</TD></TR>( s% Z7 l8 f& V( R( k5 a
<TR> - P& i3 D9 s p9 f4 V' S' ~" }<TD width="16%">LPCWSTR</TD>5 a" z v+ M- y1 g& A9 Y# T' ?1 q$ g
<TD width="42%">constant zero-terminated Unicode string (const wchar_t*)</TD> : R/ l T4 C' w5 k<TD width="42%">constant zero-terminated Unicode string (const wchar_t*) </TD></TR>; o C* |' V9 V( H3 a0 s1 G' D- n
<TR> / e- F. o$ N3 I1 c( v2 j; r# R<TD width="16%"><XXXXIME xime="7">TCHAR</XXXXIME></TD>% O' b* i. T$ a; H- S
<TD width="42%"><XXXXIME xime="7">char</XXXXIME></TD> 1 U3 o! i; _( r% O<TD width="42%"><XXXXIME xime="7">wchar_t</XXXXIME></TD></TR>& x) K# {% {' F
<TR> ( T7 }( M/ z1 W3 S- U- o' b5 n<TD width="16%">LPTSTR</TD> " H# H+ n$ M" {<TD width="42%">zero-terminated string of TCHAR (TCHAR*) </TD>* [* u$ ]6 y# W9 }% z9 c2 ?
<TD width="42%">zero-terminated string of TCHAR (TCHAR*)</TD></TR> 1 F/ k3 z* U/ t, { o<TR> ; ~# b c; d8 M8 l; N5 G<TD width="16%">LPCTSTR </TD> / v" |1 T0 d8 a8 w& { h: S) d( T<TD width="42%">constant zero-terminated string of TCHAR (const TCHAR*)</TD> 6 @5 l" N* ^2 M& @<TD width="42%">constant zero-terminated string of TCHAR (const TCHAR*)</TD></TR></TABLE> , d! r5 h4 j: C" n) G8 ~<P><IMG src="http://www.vckbase.com/document/image/paragraph.gif"><B> 何时使用 TCHAR 和 Unicode</B>' |8 `0 T. i- d- R( X& m