9 a+ S! \5 T3 s3 v& x! \3 F* i // Add on a backslash if it wasn''t present in the registry value. 0 Q8 e/ c" b& c% A/ F // First, get a pointer to the terminating zero.2 u9 t" c6 `; R1 A% L& D
char* pLastChar = strchr ( szConfigFilename, ''\0'' ); / n/ i5 ^* P0 } 8 {2 [9 I. F7 h2 \
// Now move it back one character.- I# Y. b- L, |% _7 E: v
pLastChar--; ( y6 L D# S. F: q0 @ 5 D2 X# U& D' | F9 V if ( *pLastChar != ''\\'' ) , e4 C+ h6 k$ O" ~0 p% p; U strcat ( szConfigFilename, "\\" );! o% Q0 V" k, |9 k# d+ a
7 Z Y% @0 r7 f2 A2 T$ x6 c // Add on the name of the config file.* Z! ]% ]7 H7 z$ v! K' D0 C
strcat ( szConfigFilename, "config.bin" ); / U4 {6 |) q0 x9 [4 t$ ^ W" A q1 ^3 t: b, X, B, {: i- `
// If the caller''s buffer is big enough, return the filename.& s: M% o$ r5 X+ I1 U0 q1 k! c3 F
if ( strlen ( szConfigFilename ) >= nBuffSize )) Y' u" o; w% B( r
return false;0 j: V/ d6 y M3 \7 P8 L) n2 }9 Y3 p
else8 _2 q, K8 V# w3 n% ]5 W( _5 v& G+ X
{ 0 s0 U& v+ Q) @6 g2 b strcpy ( pszName, szConfigFilename ); ( c7 e7 i5 I6 e, I2 Y8 p7 J+ L1 ? return true; . V) a/ s7 }- I- y0 z }, \3 m; W. z8 I R! ]' b1 T- L. p% @
} </PRE> 这是一段很健壮的代码,然而在遇到 DBCS 字符时它将会出错。让我们来看看为什么。假设一个日本用户使用了你的程序,把它安装在 C:\<IMG src="http://www.vckbase.com/document/journal/vckbase30/images/youkoso.gif" border=0>。下面是这个名字在内存中的存储形式: 3 U9 l; I. \9 b" U. i+ X9 f ( A+ F" n3 z0 F+ Q, c
<TABLE> 3 F8 f6 L' ?3 Z- y" ]3 R0 W5 }6 t1 o5 j3 s$ x& @# ]% J( R: m5 P8 p
<TR> ; h4 r& F" @* ]<TD align=middle width="12%">43</TD>2 T5 |4 f1 u' d$ K9 U5 ~8 [# p
<TD align=middle width="12%"><FONT color=#990000>3A</FONT></TD>: b) P& W" q; p- _% p% e1 j
<TD align=middle width="12%"><FONT color=#0000ff>5C</FONT></TD>. ^8 Y" \$ |8 T' K$ f4 a2 n" W
<TD align=middle width="12%">83 88</TD> , Q7 {3 z0 S, J) y<TD align=middle width="13%">83 45</TD> 4 r9 B1 g/ \5 n9 ^, |<TD align=middle width="13%">83 52</TD>( U& \: T7 o: m
<TD align=middle width="13%">83 <FONT color=#0000ff>5C</FONT></TD> ! g' |% y/ s5 B, e* Z2 c7 J<TD align=middle width="13%">00</TD></TR> }; {* f9 c! x8 @# {
<TR>( z' \# Z! Z X! t& I2 g l$ s3 d
<TD align=middle width="12%"> </TD>" C4 m' j/ _7 I, t. Y9 A
<TD align=middle width="12%"> </TD> + u A0 v- Z* @# E; F<TD align=middle width="12%"> </TD>: C& i; T/ L* w) V) O# e4 ^! F
<TD align=middle width="12%"><FONT color=#990000>LB TB </FONT></TD>6 ^+ t j, r& s8 `. D7 I
<TD align=middle width="13%"><FONT color=#990000>LB TB </FONT></TD> * O' H$ l/ p! Q<TD align=middle width="13%"><FONT color=#990000>LB TB </FONT></TD>& L6 O5 P; @+ |+ ?) F) a
<TD align=middle width="13%"><FONT color=#990000>LB TB </FONT></TD>( l& L5 O8 K8 p% m" z6 z) y
<TD align=middle width="13%"> </TD></TR> + z7 B/ y; S+ D; L! i<TR> - I! w/ H4 i2 {0 @8 c1 k4 }3 L<TD align=middle width="12%">C</TD>1 e5 V7 o. \* M' l3 S: E+ Q
<TD align=middle width="12%">:</TD>1 O2 @% s: i" W8 \& q! C
<TD align=middle width="12%">\</TD>7 ` R; h) @5 d
<TD align=middle width="12%"><IMG src="http://www.vckbase.com/document/journal/vckbase30/images/yo.gif" border=0></TD>2 e, K3 N* n3 e$ p& G, y1 i" W# M
<TD align=middle width="13%"><IMG src="http://www.vckbase.com/document/journal/vckbase30/images/u.gif" border=0></TD> 7 n( W7 E2 i" c<TD align=middle width="13%"><IMG src="http://www.vckbase.com/document/journal/vckbase30/images/ko.gif" border=0></TD>& _5 B' F+ _5 l. |6 i' ?
<TD align=middle width="13%"><IMG src="http://www.vckbase.com/document/journal/vckbase30/images/so.gif" border=0></TD> " K7 V# i/ i( o3 B% ?' K& r<TD align=middle width="13%">EOS</TD></TR></TABLE> 0 q# e7 R; }6 C) S<> 当使用 GetConfigFileName() 检查尾部的''\\''时,它寻找安装目录名中最后的非0字节,看它是等于''\\''的,所以没有重新增加一个''\\''。结果是代码返回了错误的文件名。4 g- e/ }( U# P
哪里出错了呢?看看上面两个被用蓝色高量显示的字节。斜杠''\\''的值是0x5c。'' ''的值是83 5c。上面的代码错误的读取了一个 trail byte,把它当作了一个字符。 1 \ z1 l6 I6 S }0 d 正确的后向遍历方法是使用能够识别DBCS字符的函数,使指针移动正确的字节数。下面是正确的代码。(指针移动的地方用红色标明) </P><RE>bool FixedGetConfigFileName ( char* pszName, size_t nBuffSize )$ h- A6 O- v* M7 Y+ ^* W
{ ; ^2 n9 M) ?9 I& o char szConfigFilename[MAX_PATH]; & I4 ]& M& Q7 ~2 T8 _: i5 H , O& U" k; l9 H0 f2 T6 u // Read install dir from registry... we''ll assume it succeeds.. z' r6 w" k2 c; d
0 ^8 W9 |* x0 |0 h
// Add on a backslash if it wasn''t present in the registry value. x5 b- j/ m9 R& C# W0 f
// First, get a pointer to the terminating zero. + c- N& J1 f. t- r& D' D. }' r char* pLastChar = _mbschr ( szConfigFilename, ''\0'' ); 7 {! E `" _' Q6 L4 K B6 f1 U 6 h. ?) O2 P1 ?! I2 U) |+ ` // Now move it back one double-byte character. , P) @! t" I. U0 ~! H- m" N# M <FONT color=#ff0000> pLastChar = CharPrev ( szConfigFilename, pLastChar );</FONT> 7 i( X1 ~0 a! r1 r0 y9 x$ [: c # U( s! P( n) H7 i& X" e if ( *pLastChar != ''\\'' ) 3 F% e* Z. I% O' C. s2 |7 D _mbscat ( szConfigFilename, "\\" );0 a. J4 Z/ r& M6 v
& ]" P/ I# s( a, f) B+ p // Add on the name of the config file.+ W( i$ c3 Q5 G" u7 M4 z9 L
_mbscat ( szConfigFilename, "config.bin" );2 m) L' l+ f8 Q- ~$ q- t) z
! }, r: _5 y3 u6 T1 _) f0 }; Q2 x
// If the caller''s buffer is big enough, return the filename.! G( J4 R2 t- d3 |2 a& V6 `/ c
if ( _mbslen ( szInstallDir ) >= nBuffSize ) 5 B2 y" F! B2 @' U" z5 C return false; R0 A5 _* H3 U9 O# ]; m" Y else 6 n0 _1 d2 m0 F3 c {5 q l H# x/ c7 K/ y9 ]
_mbscpy ( pszName, szConfigFilename );$ z/ _- [/ q' }/ ~
return true; 3 S& s4 Y3 E6 H7 g! _! |3 ?9 h }( Y/ j5 p2 m$ a2 @7 G5 i
}: a) J' I# f9 n- ~1 Q- A3 C
</PRE> 上面的函数使用CharPrev() API使pLastChar向后移动一个字符,这个字符可能是两个字节长。在这个版本里,if条件正常工作,因为lead byte永远不会等于0x5c。; S7 i3 G. c, ?9 V% c
让我们来想象一个违背规则1的场合。例如,你可能要检测一个用户输入的文件名是否多次出现了'':''。如果,你使用++操作来遍历字符串,而不是使用CharNext(),你可能会发出不正确的错误警告如果恰巧有一个trail byte它的值的等于'':''的值。: b' J" s, y/ H- P
与规则2相关的关于字符串索引的规则:<RE>2a. 永远不要使用减法去得到一个字符串的索引。</PRE> ) p! b1 s) s3 C1 Z4 K. H5 t& P<>违背这条规则的代码和违背规则2的代码很相似。例如,</P><RE>char* pLastChar = &szConfigFilename [strlen(szConfigFilename) - 1];</PRE>( W3 x% T7 ^6 P' J8 X3 C& v" j2 @
<>这和向后移动一个指针是同样的效果。4 ] h" H* }' i( j \6 E, l
4 W. T( \5 @/ I( c<IMG src="http://www.vckbase.com/document/image/paragraph.gif"><B> 回到关于str***()和_mbs***()的区别</B>' D$ @' g: C' x$ ] b/ ^
& `: a8 G" M- \$ r7 u
现在,我们应该很清楚为什么_mbs***()函数是必需的。Str***()函数根本不考虑DBCS字符,而_mbs***()考虑。如果,你调用strrchr("C:\\ ", ''\\''),返回结果可能是错误的,然而_mbsrchr()将会认出最后的双字节字符,返回一个指向真的''\\''的指针。; m4 m. U4 J. I( r! M
关于字符串函数的最后一点:str***()和_mbs***()函数认为字符串的长度都是以char来计算的。所以,如果一个字符串包含3个双字节字符,_mbslen()将会返回6。Unicode函数返回的长度是按wchar_t来计算的。例如,wcslen(L"Bob")返回3。 + ~% b1 M% W% C1 Y7 R; X- t& R" N ( ?* p4 `5 c& E$ M5 _. o2 V9 [<IMG src="http://www.vckbase.com/document/image/paragraph.gif"><B> Win32 API中的MBCS和Unicode</B> 5 p* M( ~1 U r( W! P) |9 C& l' _, |, E# t* {
两组 APIs: $ `9 l4 P5 r6 U! @- g 尽管你也许从来没有注意过,Win32中的每个与字符串相关的API和message都有两个版本。一个版本接受MBCS字符串,另一个接受Unicode字符串。例如,根本没有SetWindowText()这个API,相反,有SetWindowTextA()和SetWindowTextW()。后缀A表明这是MBCS函数,后缀W表示这是Unicode版本的函数。, T# |3 S" m; w; ^
当你 build 一个 Windows 程序,你可以选择是用 MBCS 或者 Unicode APIs。如果,你曾经用过VC向导并且没有改过预处理的设置,那表明你用的是MBCS版本。那么,既然没有 SetWindowText() API,我们为什么可以使用它呢?winuser.h头文件包含了一些宏,例如: </P><RE>BOOL WINAPI SetWindowTextA ( HWND hWnd, LPCSTR lpString );/ Z7 H0 G3 a% f
BOOL WINAPI SetWindowTextW ( HWND hWnd, LPCWSTR lpString ); - z! [8 X2 i* \3 n" @ 3 c8 C2 Y$ i1 x2 E9 N4 d#ifdef UNICODE+ P) h7 ^, ^& ]2 j
#define SetWindowText SetWindowTextW ) f* `' {+ F, c8 |! s! b#else9 ` q9 E9 C" E: n
#define SetWindowText SetWindowTextA 8 N0 O1 L. e- J1 e6 q#endif </PRE>当使用MBCS APIs来build程序时,UNICODE没有被定义,所以预处理器看到:<RE>#define SetWindowText SetWindowTextA</PRE> # s' b5 U5 S# y* u<> 这个宏定义把所有对SetWindowText的调用都转换成真正的API函数SetWindowTextA。(当然,你可以直接调用SetWindowTextA() 或者 SetWindowTextW(),虽然你不必那么做。) / C! l* R$ k" G$ z- K4 J 所以,如果你想把默认使用的API函数变成Unicode版的,你可以在预处理器设置中,把_MBCS从预定义的宏列表中删除,然后添加UNICODE和_UNICODE。(你需要两个都定义,因为不同的头文件可能使用不同的宏。) 然而,如果你用char来定义你的字符串,你将会陷入一个尴尬的境地。考虑下面的代码:</P><RE>HWND hwnd = GetSomeWindowHandle();9 w6 d2 P# N$ G# q' K9 H
char szNewText[] = "we love Bob!"; 3 G* R3 E( c1 O5 s# F# hSetWindowText ( hwnd, szNewText );</PRE>! @6 c5 }/ M7 I ^! ]+ L
<>在预处理器把SetWindowText用SetWindowTextW来替换后,代码变成:</P><RE>HWND hwnd = GetSomeWindowHandle(); ; E5 m& [) c' s/ [3 k4 Echar szNewText[] = "we love Bob!";! k5 r9 Z/ b7 f! M( [
SetWindowTextW ( hwnd, szNewText );</PRE> $ s( c+ C W. q# c; q( N2 D1 y<> 看到问题了吗?我们把单字节字符串传给了一个以Unicode字符串做参数的函数。解决这个问题的第一个方案是使用 #ifdef 来包含字符串变量的定义:</P><RE>HWND hwnd = GetSomeWindowHandle();+ {7 s9 t3 O7 z+ u. R6 r
#ifdef UNICODE s0 H7 o& ~7 |, }" K% p- iwchar_t szNewText[] = L"we love Bob!";) D5 O6 V* n$ X, J8 A
#else" T4 E6 t' K) s' P
char szNewText[] = "we love Bob!"; 3 m6 ?3 M' M6 B# }#endif 6 M- d" t) U b# ]% e8 n! S# gSetWindowText ( hwnd, szNewText );</PRE># b) m# l# l0 o
<>你可能已经感受到了这样做将会使你多么的头疼。完美的解决方案是使用TCHAR.) m" n! [. ]2 c- P" H
' q# W* j0 l# |4 A. {7 j
<IMG src="http://www.vckbase.com/document/image/paragraph.gif"><B> 使用TCHAR</B> }9 h& l" s- i/ Z& s! }# Y M1 e3 Y# r" d6 Y% A9 @- E
TCHAR是一种字符串类型,它让你在以MBCS和UNNICODE来build程序时可以使用同样的代码,不需要使用繁琐的宏定义来包含你的代码。TCHAR的定义如下:</P><RE>#ifdef UNICODE% X0 c6 J" c7 H
typedef wchar_t TCHAR; 0 ^' Q. J- @& ]1 q+ z& F* R. \#else( r" w7 F9 J9 d& V& Y0 @
typedef char TCHAR; 5 R- ]7 D6 ^; ~" d. H#endif</PRE>: \% G/ R( t) x: g# v- M& i
<>所以用MBCS来build时,TCHAR是char,使用UNICODE时,TCHAR是wchar_t。还有一个宏来处理定义Unicode字符串常量时所需的L前缀。</P><RE>#ifdef UNICODE5 l9 f7 L) b \
#define _T(x) L##x, t" D. r" @3 |: ?* e0 X
#else 9 m5 w- ~) k9 A( T9 c#define _T(x) x. ~$ E/ w' ]/ B( ]6 E c3 ? K, ^
#endif</PRE> 2 s$ v8 P7 z* z7 h/ s! `<P> ##是一个预处理操作符,它可以把两个参数连在一起。如果你的代码中需要字符串常量,在它前面加上_T宏。如果你使用Unicode来build,它会在字符串常量前加上L前缀。</P><PRE>TCHAR szNewText[] = _T("we love Bob!");</PRE>8 A' p, C& ~! w% y1 u
<P> 像是用宏来隐藏SetWindowTextA/W的细节一样,还有很多可以供你使用的宏来实现str***()和_mbs***()等字符串函数。例如,你可以使用_tcsrchr宏来替换strrchr()、_mbsrchr()和wcsrchr()。_tcsrchr根据你预定义的宏是_MBCS还是UNICODE来扩展成正确的函数,就像SetWindowText所作的一样。. e% f4 _* _3 n: x
不仅str***()函数有TCHAR宏。其他的函数如, _stprintf(代替sprinft()和swprintf()),_tfopen(代替fopen()和_wfopen())。 MSDN中"Generic-Text Routine Mappings."标题下有完整的宏列表。 - K0 ]' w, R: ]( L6 E& M. ^+ T+ |" j9 O. v& U0 z
<IMG src="http://www.vckbase.com/document/image/paragraph.gif"><B> 字符串和TCHAR typedefs</B> ! m- S8 ?2 q8 F5 ]; ~ ; Z# E3 c1 T0 E F 由于Win32 API文档的函数列表使用函数的常用名字(例如,"SetWindowText"),所有的字符串都是用TCHAR来定义的。(除了XP中引入的只适用于Unicode的API)。下面列出一些常用的typedefs,你可以在msdn中看到他们。</P>- I2 T* I. w6 f+ v$ @3 _5 J
<TABLE>0 f" g: P3 E9 g5 t