: Z% s. [- j. e( b- E4 H' O. y // Add on the name of the config file. 5 M! s4 |% ?) @0 N! }3 t8 j strcat ( szConfigFilename, "config.bin" ); ; {* o4 T5 b3 K" I; ?( j # f2 Y% x0 c9 m" m, M6 Y' k // If the caller''s buffer is big enough, return the filename.& ^) z5 t9 w" M6 L1 J# o. ^
if ( strlen ( szConfigFilename ) >= nBuffSize ) % k+ D, y6 L+ F2 N1 Y; U; k return false;( c/ K$ b n. i- d3 J. E$ e' f
else , g; m3 d1 `/ i; z4 a- x/ M { 6 Z6 S# K- _3 o8 o. M8 G' |# G. F strcpy ( pszName, szConfigFilename ); 3 C8 |! v# ?1 K8 w0 N: o return true; * a# T# s( p2 g$ x }! Y8 l0 y& s* W5 a2 n* K
} </PRE> 这是一段很健壮的代码,然而在遇到 DBCS 字符时它将会出错。让我们来看看为什么。假设一个日本用户使用了你的程序,把它安装在 C:\<IMG src="http://www.vckbase.com/document/journal/vckbase30/images/youkoso.gif" border=0>。下面是这个名字在内存中的存储形式:- B6 F |% H* \; _5 ~" U
9 N2 h% a/ s) L3 v9 R" ]1 H
<TABLE> : W$ w3 Y6 X; `! Z- l8 M# `. V$ [5 D2 J8 R; @0 l4 G/ M( A- f. t
<TR> - i W3 m |4 s! a) J) |) r<TD align=middle width="12%">43</TD>' z7 V5 u6 K4 D+ c
<TD align=middle width="12%"><FONT color=#990000>3A</FONT></TD> 9 g( U7 Z4 ]. p7 T<TD align=middle width="12%"><FONT color=#0000ff>5C</FONT></TD> ) T+ S" U; V, f5 v5 `<TD align=middle width="12%">83 88</TD>8 g. }6 A$ i9 u9 O
<TD align=middle width="13%">83 45</TD> 4 l7 Z, ?* T' v# ?) v( [6 X% F<TD align=middle width="13%">83 52</TD> n( p d- ?# K% R8 I G2 Z8 G, G<TD align=middle width="13%">83 <FONT color=#0000ff>5C</FONT></TD>% v- x* d- J# x; Y7 [7 ?
<TD align=middle width="13%">00</TD></TR> + t- N. n; {& Z; f) V<TR>) K' M7 ^% \5 t! y
<TD align=middle width="12%"> </TD> * w1 m! B" T/ V4 v" s/ b<TD align=middle width="12%"> </TD> . q, M8 l7 h h- O5 W% C- S: }<TD align=middle width="12%"> </TD>2 _7 S$ H) H4 s2 X5 D
<TD align=middle width="12%"><FONT color=#990000>LB TB </FONT></TD> ( E: q; i% Q a) M J, p<TD align=middle width="13%"><FONT color=#990000>LB TB </FONT></TD> H3 z0 D- C( q' N<TD align=middle width="13%"><FONT color=#990000>LB TB </FONT></TD>3 J- F3 c% {9 \" Y* I3 o. v9 p
<TD align=middle width="13%"><FONT color=#990000>LB TB </FONT></TD>$ Z |2 u C1 M3 |& B* F
<TD align=middle width="13%"> </TD></TR> % p' u X) B& W, i" f# ]<TR>5 @" q8 o* O& D7 ~' I
<TD align=middle width="12%">C</TD>: u2 q5 R3 A4 `* } w
<TD align=middle width="12%">:</TD> 3 t- o, A/ _2 f6 J% d<TD align=middle width="12%">\</TD> 0 `+ K, J) Y" B W( N o<TD align=middle width="12%"><IMG src="http://www.vckbase.com/document/journal/vckbase30/images/yo.gif" border=0></TD> 0 u% X" _8 Z8 r* y1 ?1 V<TD align=middle width="13%"><IMG src="http://www.vckbase.com/document/journal/vckbase30/images/u.gif" border=0></TD> 7 \) Y# x' B, }<TD align=middle width="13%"><IMG src="http://www.vckbase.com/document/journal/vckbase30/images/ko.gif" border=0></TD>4 {0 x5 O' E( k# L6 g; ] _$ E
<TD align=middle width="13%"><IMG src="http://www.vckbase.com/document/journal/vckbase30/images/so.gif" border=0></TD>: s& Y* F1 S5 i$ z* \' }
<TD align=middle width="13%">EOS</TD></TR></TABLE>) v ~! H$ I8 Y* v+ _
<> 当使用 GetConfigFileName() 检查尾部的''\\''时,它寻找安装目录名中最后的非0字节,看它是等于''\\''的,所以没有重新增加一个''\\''。结果是代码返回了错误的文件名。5 l# m S) @9 V$ S( d$ M
哪里出错了呢?看看上面两个被用蓝色高量显示的字节。斜杠''\\''的值是0x5c。'' ''的值是83 5c。上面的代码错误的读取了一个 trail byte,把它当作了一个字符。 1 K, F8 y, }5 \; m 正确的后向遍历方法是使用能够识别DBCS字符的函数,使指针移动正确的字节数。下面是正确的代码。(指针移动的地方用红色标明) </P><RE>bool FixedGetConfigFileName ( char* pszName, size_t nBuffSize )! a; D8 S, g6 E: w9 F" H) K8 o, Z5 e
{0 g+ M2 r7 M" l' Q0 I
char szConfigFilename[MAX_PATH];7 m: Y7 Z& O+ A4 s7 R
' j3 q$ l% p- m; T // Read install dir from registry... we''ll assume it succeeds. ) J& n4 C3 @' r' N ) W) ~$ L1 _9 A9 I
// Add on a backslash if it wasn''t present in the registry value.# ]$ b9 s1 s8 r6 j% U
// First, get a pointer to the terminating zero. * k8 l! \/ W! g char* pLastChar = _mbschr ( szConfigFilename, ''\0'' );3 H" j8 g3 t6 q' t) j
3 q4 m5 B. Z' m: K% A, Y+ o5 u // Now move it back one double-byte character.9 n1 ` x1 L3 `- D% V7 X( G
<FONT color=#ff0000> pLastChar = CharPrev ( szConfigFilename, pLastChar );</FONT> 0 U9 D' U: J8 b3 B; I : Q3 B$ A" Z; a9 B- E/ v, X. ~" w
if ( *pLastChar != ''\\'' )' R/ T& r' B5 _ f, G! \
_mbscat ( szConfigFilename, "\\" );8 {) {$ s( J: K x8 S+ @' _: A! B6 Z. t
3 y$ I2 _- ~8 j4 _) L7 z
// Add on the name of the config file.$ h6 s8 W9 \! `& S- ~- R$ W2 W R* v+ v. s
_mbscat ( szConfigFilename, "config.bin" ); 7 x2 }9 H7 M4 o5 W; j0 L$ b( l% u7 u6 E4 y& N- K" ]7 w
// If the caller''s buffer is big enough, return the filename.5 T6 r1 u/ R% X/ Y4 D+ B
if ( _mbslen ( szInstallDir ) >= nBuffSize )$ W9 A# f/ b7 l; N" K4 S& l
return false; * t9 H: o1 V: d* J* r else 8 d k) ~2 T, i n; L { p" _$ Z7 e& U& w$ f5 a
_mbscpy ( pszName, szConfigFilename ); & v2 q% M, \* A; r: k6 r; l return true; ' j# W: P; K0 D7 }' w: K' x3 X' T } , j6 n6 {+ W. E- h2 Z}0 z, X% G |4 f9 H
</PRE> 上面的函数使用CharPrev() API使pLastChar向后移动一个字符,这个字符可能是两个字节长。在这个版本里,if条件正常工作,因为lead byte永远不会等于0x5c。* f4 t6 S) I& G3 O! v
让我们来想象一个违背规则1的场合。例如,你可能要检测一个用户输入的文件名是否多次出现了'':''。如果,你使用++操作来遍历字符串,而不是使用CharNext(),你可能会发出不正确的错误警告如果恰巧有一个trail byte它的值的等于'':''的值。; c' Q8 \6 o Y3 j% R% }; y. f
与规则2相关的关于字符串索引的规则:<RE>2a. 永远不要使用减法去得到一个字符串的索引。</PRE>9 F( f; o0 \9 J4 [1 P- ?
<>违背这条规则的代码和违背规则2的代码很相似。例如,</P><RE>char* pLastChar = &szConfigFilename [strlen(szConfigFilename) - 1];</PRE> / F" q: f; n" k( H- A<>这和向后移动一个指针是同样的效果。 9 V& m+ G0 Y7 t7 o; }+ |* X" d! O- O0 J" Q `! k
<IMG src="http://www.vckbase.com/document/image/paragraph.gif"><B> 回到关于str***()和_mbs***()的区别</B>9 F5 S" v, Q1 `3 U- r: j2 |
" I& Z) k. t. ]) w% @ 现在,我们应该很清楚为什么_mbs***()函数是必需的。Str***()函数根本不考虑DBCS字符,而_mbs***()考虑。如果,你调用strrchr("C:\\ ", ''\\''),返回结果可能是错误的,然而_mbsrchr()将会认出最后的双字节字符,返回一个指向真的''\\''的指针。% s" @% z+ M' u e2 [4 q4 G
关于字符串函数的最后一点:str***()和_mbs***()函数认为字符串的长度都是以char来计算的。所以,如果一个字符串包含3个双字节字符,_mbslen()将会返回6。Unicode函数返回的长度是按wchar_t来计算的。例如,wcslen(L"Bob")返回3。) t, K& J/ P! E2 f! w
( M) J% _! m3 G% o) e9 W, }0 g- W% q: {$ y
<IMG src="http://www.vckbase.com/document/image/paragraph.gif"><B> Win32 API中的MBCS和Unicode</B> |9 W% \' O7 Z& s; Y+ V! i0 y$ s% w2 ~0 Q( N# n1 F
两组 APIs: - P2 {+ T. s3 r4 A4 | 尽管你也许从来没有注意过,Win32中的每个与字符串相关的API和message都有两个版本。一个版本接受MBCS字符串,另一个接受Unicode字符串。例如,根本没有SetWindowText()这个API,相反,有SetWindowTextA()和SetWindowTextW()。后缀A表明这是MBCS函数,后缀W表示这是Unicode版本的函数。% N- k. C2 v) Z4 E" ^0 T* w4 z
当你 build 一个 Windows 程序,你可以选择是用 MBCS 或者 Unicode APIs。如果,你曾经用过VC向导并且没有改过预处理的设置,那表明你用的是MBCS版本。那么,既然没有 SetWindowText() API,我们为什么可以使用它呢?winuser.h头文件包含了一些宏,例如: </P><RE>BOOL WINAPI SetWindowTextA ( HWND hWnd, LPCSTR lpString ); 7 \7 T) V! e1 h+ q& |- BBOOL WINAPI SetWindowTextW ( HWND hWnd, LPCWSTR lpString ); 4 a, r8 M0 m- t: s; O' O3 | $ U! R* `& u; }' B8 f; {
#ifdef UNICODE * V$ E- i4 ^/ v1 L$ ~#define SetWindowText SetWindowTextW6 p3 Y; c' d- k# ~9 m) Z0 H; N
#else " G$ f+ u, V' U) v' C( Q2 m/ C. e#define SetWindowText SetWindowTextA & Z+ k4 `. F* f1 C, {#endif </PRE>当使用MBCS APIs来build程序时,UNICODE没有被定义,所以预处理器看到:<RE>#define SetWindowText SetWindowTextA</PRE> " h4 m. A, n, G5 F<> 这个宏定义把所有对SetWindowText的调用都转换成真正的API函数SetWindowTextA。(当然,你可以直接调用SetWindowTextA() 或者 SetWindowTextW(),虽然你不必那么做。) & n1 ~% o% V: U9 \# D0 \5 g 所以,如果你想把默认使用的API函数变成Unicode版的,你可以在预处理器设置中,把_MBCS从预定义的宏列表中删除,然后添加UNICODE和_UNICODE。(你需要两个都定义,因为不同的头文件可能使用不同的宏。) 然而,如果你用char来定义你的字符串,你将会陷入一个尴尬的境地。考虑下面的代码:</P><RE>HWND hwnd = GetSomeWindowHandle(); N+ i P& @4 v# k& H$ y, {0 kchar szNewText[] = "we love Bob!";/ @. T7 a( Y, F- Y8 _
SetWindowText ( hwnd, szNewText );</PRE>3 n: S# |! b, s* l5 X9 r
<>在预处理器把SetWindowText用SetWindowTextW来替换后,代码变成:</P><RE>HWND hwnd = GetSomeWindowHandle();% ?+ {; {4 a; Y% ]
char szNewText[] = "we love Bob!"; ) ^, Z2 W- c+ }+ D4 _3 t8 W' USetWindowTextW ( hwnd, szNewText );</PRE> " w1 w% n0 a& X0 `) c, T% `<> 看到问题了吗?我们把单字节字符串传给了一个以Unicode字符串做参数的函数。解决这个问题的第一个方案是使用 #ifdef 来包含字符串变量的定义:</P><RE>HWND hwnd = GetSomeWindowHandle();3 m! q+ ]0 O" I& O
#ifdef UNICODE : v' M. \; H2 ]/ uwchar_t szNewText[] = L"we love Bob!";5 l' \/ s0 _; r: N, u& j
#else : w: e% | a: T1 `# T, fchar szNewText[] = "we love Bob!";% L. n- }0 V) J; }& M
#endif3 p9 n! s8 N3 L4 a2 o( w0 f1 ^
SetWindowText ( hwnd, szNewText );</PRE>0 \9 `7 h9 [- r+ R8 T) F" B' u
<>你可能已经感受到了这样做将会使你多么的头疼。完美的解决方案是使用TCHAR. # }$ z* q7 R7 u6 j; H/ s9 H, t; }/ T# r2 y
<IMG src="http://www.vckbase.com/document/image/paragraph.gif"><B> 使用TCHAR</B>0 I9 o# Z8 Q( Z8 E; K O
7 }2 K- V; f9 a! X M& A
TCHAR是一种字符串类型,它让你在以MBCS和UNNICODE来build程序时可以使用同样的代码,不需要使用繁琐的宏定义来包含你的代码。TCHAR的定义如下:</P><RE>#ifdef UNICODE * w. u) i6 p# Y' R6 H. z0 n" H ~) btypedef wchar_t TCHAR; 3 o3 P* G' t0 Z J' b6 L#else ?7 g1 `: N& d! ^* S" p
typedef char TCHAR;5 r$ ~; |4 V0 P7 C! V8 N4 {
#endif</PRE>) t, Q8 d" l" E9 V/ F( A$ @) G
<>所以用MBCS来build时,TCHAR是char,使用UNICODE时,TCHAR是wchar_t。还有一个宏来处理定义Unicode字符串常量时所需的L前缀。</P><RE>#ifdef UNICODE % d7 K# N# a& D) j$ |0 T! }#define _T(x) L##x $ J$ C1 N: }% w5 ^3 i: }( H: _#else 2 S7 L; _! Y7 x0 `; Q#define _T(x) x 5 `1 |: `9 H1 A; W! j' ?9 [#endif</PRE> ) m8 Q2 J' \$ F6 k6 y6 a<P> ##是一个预处理操作符,它可以把两个参数连在一起。如果你的代码中需要字符串常量,在它前面加上_T宏。如果你使用Unicode来build,它会在字符串常量前加上L前缀。</P><PRE>TCHAR szNewText[] = _T("we love Bob!");</PRE> 1 a) e* a0 H$ Y" n' O<P> 像是用宏来隐藏SetWindowTextA/W的细节一样,还有很多可以供你使用的宏来实现str***()和_mbs***()等字符串函数。例如,你可以使用_tcsrchr宏来替换strrchr()、_mbsrchr()和wcsrchr()。_tcsrchr根据你预定义的宏是_MBCS还是UNICODE来扩展成正确的函数,就像SetWindowText所作的一样。 U6 m v! W. V8 l 不仅str***()函数有TCHAR宏。其他的函数如, _stprintf(代替sprinft()和swprintf()),_tfopen(代替fopen()和_wfopen())。 MSDN中"Generic-Text Routine Mappings."标题下有完整的宏列表。 - [. q8 T: H. S2 d7 L' e 8 ]$ L# t+ y# {<IMG src="http://www.vckbase.com/document/image/paragraph.gif"><B> 字符串和TCHAR typedefs</B>' L. |' k$ ~* D+ a. M1 g2 h; ?
" A. \0 K/ f0 R+ F
由于Win32 API文档的函数列表使用函数的常用名字(例如,"SetWindowText"),所有的字符串都是用TCHAR来定义的。(除了XP中引入的只适用于Unicode的API)。下面列出一些常用的typedefs,你可以在msdn中看到他们。</P> 9 W$ H- A- M2 }9 s<TABLE>& G6 L+ a, H" l: Y$ ~ q
( g. k3 W/ C$ f) @: I
<TR> 1 ?! m' v' L6 ]* w4 e/ n4 u<TD align=middle width="16%"><B>type </B></TD> ' o5 K6 u% H/ q/ ? c) Z4 y$ D<TD align=middle width="42%"><B>Meaning in MBCS builds </B></TD> 9 [8 s( H0 u/ v7 R& N<TD align=middle width="42%"><B>Meaning in Unicode builds</B></TD></TR>' X% k! Q0 N8 _; S
<TR> $ A0 S- V/ F4 O( H# O! \5 t1 X b<TD width="16%">WCHAR</TD> ) h% k4 c: S4 B<TD width="42%">wchar_t</TD>% F8 g! ~, l0 o
<TD width="42%">wchar_t</TD></TR>" d1 |1 h+ {4 |4 ~3 G+ l
<TR> ( V$ E5 y4 X7 E R: h<TD width="16%">LPSTR </TD>/ m& X# M+ h% M Y1 \) @% ?4 K' K
<TD width="42%">zero-terminated string of char (char*)</TD> w* W: {6 [! s8 m% I1 S- ]0 B<TD width="42%">zero-terminated string of char (char*)</TD></TR>7 c9 {$ d4 `6 d6 L, h1 d) o
<TR> ! C0 r2 u! X8 C2 |& c. K<TD width="16%">LPCSTR </TD> 8 Z/ G; M7 s4 y7 G& W<TD width="42%">constant zero-terminated string of char (const char*)</TD>) F% w8 ^/ L. A, q1 q9 [
<TD width="42%">constant zero-terminated string of char (const char*)</TD></TR>; W- _0 E$ F! u6 r" @' O! m* ^
<TR> # [$ V$ W: S. t' R<TD width="16%">LPWSTR</TD> ! Q+ H! n. M9 T' ?9 O/ }. [& ^<TD width="42%">zero-terminated Unicode string (wchar_t*) </TD>3 K \" f a- j% O. H
<TD width="42%">zero-terminated Unicode string (wchar_t*)</TD></TR>% k) y: R0 K3 b% _
<TR>/ ^ l& Y6 v2 Z% B$ ~; Z& i
<TD width="16%">LPCWSTR</TD>) Y4 o+ p- R1 m; M( E+ M' Q& w! {
<TD width="42%">constant zero-terminated Unicode string (const wchar_t*)</TD>0 _' V+ L4 R" H# C" ~$ l) D
<TD width="42%">constant zero-terminated Unicode string (const wchar_t*) </TD></TR> 2 c; h( I1 h- U: R* B; p/ y3 O7 ?0 T<TR>8 M6 f' _& D. P+ J3 l2 t0 [
<TD width="16%"><XXXXIME xime="7">TCHAR</XXXXIME></TD>, F+ c9 i0 l0 S: F) h
<TD width="42%"><XXXXIME xime="7">char</XXXXIME></TD>; r5 @0 d5 |( q8 d9 g
<TD width="42%"><XXXXIME xime="7">wchar_t</XXXXIME></TD></TR> % s4 R/ r; E( x/ r9 Y7 w( z<TR> / x( u7 U9 I1 l<TD width="16%">LPTSTR</TD> + p, I' y+ C5 Q<TD width="42%">zero-terminated string of TCHAR (TCHAR*) </TD> H% P( `5 g/ }; |2 e1 u" b) x. c<TD width="42%">zero-terminated string of TCHAR (TCHAR*)</TD></TR> " Q- b& G3 H) C& a5 O<TR> + e; g3 [1 A% B, D# O7 h: H<TD width="16%">LPCTSTR </TD> J/ R( {+ I- R$ _- ~; _<TD width="42%">constant zero-terminated string of TCHAR (const TCHAR*)</TD> 5 ?9 s6 c, c2 J/ x, L5 f<TD width="42%">constant zero-terminated string of TCHAR (const TCHAR*)</TD></TR></TABLE> # D8 Y6 k8 q) r# K<P><IMG src="http://www.vckbase.com/document/image/paragraph.gif"><B> 何时使用 TCHAR 和 Unicode</B>& B, C, e; k- \7 l6 j4 y
6 p( ^4 Y1 o7 n' Q; N. q
到现在,你可能会问,我们为什么要使用Unicode。我已经用了很多年的char。下列3种情况下,使用Unicode将会使你受益:</P>- E/ M7 q) M9 l/ y
<DIR> }( }% S( V$ X" t1 e<LI>1.你的程序只运行在Windows NT系统中。 . l2 F! G3 o% J% _<LI>2. 你的程序需要处理超过MAX_PATH个字符长的文件名。 ) ~8 J6 h; J7 A1 s8 b5 {
<LI>3. 你的程序需要使用XP中引入的只有Unicode版本的API. </LI></DIR>+ a5 n( `! ?; L* H/ ^
<P> Windows 9x 中大多数的 API 没有实现 Unicode 版本。所以,如果你的程序要在windows 9x中运行,你必须使用MBCS APIs。然而,由于NT系统内部都使用Unicode,所以使用Unicode APIs将会加快你的程序的运行速度。每次,你传递一个字符串调用MBCS API,操作系统会把这个字符串转换成Unicode字符串,然后调用对应的Unicode API。如果一个字符串被返回,操作系统还要把它转变回去。尽管这个转换过程被高度优化了,但它对速度造成的损失是无法避免的。 2 C+ o, L7 p- X9 \ 只要你使用Unicode API,NT系统允许使用非常长的文件名(突破了MAX_PATH的限制,MAX_PATH=260)。使用Unicode API的另一个优点是你的程序会自动处理用户输入的各种语言。所以一个用户可以输入英文,中文或者日文,而你不需要额外编写代码去处理它们。% R) h# _8 F* Q: Q$ m
最后,随着windows 9x产品的淡出,微软似乎正在抛弃MBCS APIs。例如,包含两个字符串参数的SetWindowTheme() API只有Unicode版本的。使用Unicode来build你的程序将会简化字符串的处理,你不必在MBCS和Unicdoe之间相互转换。$ P/ w1 k# c' q) n) j5 c
即使你现在不使用Unicode来build你的程序,你也应该使用TCHAR及其相关的宏。这样做不仅可以的代码可以很好地处理DBCS,而且如果将来你想用Unicode来build你的程序,你只需要改变一下预处理器中的设置就可以实现了。 " `; {0 D0 E: T- ~3 \6 W3 e9 q( i) F8 j. b1 U2 m% G
</P> 0 j( ^% |6 o" g k' ` : D) n2 p' q; [) p" {" N# x% }* L* g7 {2 W7 [ ?
<p> + V1 j& L4 V! ^<p></TD></TR> 6 X+ o+ E3 L8 W; q1 m<TR> 9 x; x$ }7 d0 @) V6 p4 C$ z<TD><IMG src="http://www.vckbase.com/document/image/paragraph.gif"><B> 作者简介</B>8 P4 o# t9 a+ b8 t0 A! _$ s f( M
Michael Dunn:居住在阳光城市洛杉矶。他是如此的喜欢这里的天气以致于想一生都住在这里。他在4年级时开始编程,那时用的电脑是Apple //e。1995年,在 UCLA 获得数学学士学位,随后在Symantec 公司做 QA 工程师,在 Norton AntiVirus 组工作。他自学了 Windows 和 MFC 编程。1999-2000年,他设计并实现了 Norton AntiVirus 的新界面。 2 U; a: p# p8 `7 x Michael 现在在 Napster(一个提供在线订阅音乐服务的公司)做开发工作,他还开发了UltraBar,一个IE工具栏插件,它可以使网络搜索更加容易,给了 googlebar 以沉重打击;他还开发了 CodeProject SearchBar;与人共同创建了 Zabersoft 公司,该公司在洛杉矶和丹麦的 Odense 都设有办事处。$ b# M/ q; ^2 `
他喜欢玩游戏。爱玩的游戏有 pinball, bike riding,偶尔还玩 PS, Dreamcasth 和 MAME 游戏。他因忘了自己曾经学过的语言:法语、汉语、日语而感到悲哀。</TD></TR></TABLE></P>