# u/ A# } | n% h! z% `7 [+ t1 p // Read install dir from registry... we''ll assume it succeeds.8 ]8 l+ @$ D3 o( k4 g
5 s& d/ J7 R1 d- L% L- m% W
// Add on a backslash if it wasn''t present in the registry value.3 L5 o/ [" F' k$ t ~
// First, get a pointer to the terminating zero. 8 N# r2 r+ Z: x" Q9 S+ k+ k char* pLastChar = strchr ( szConfigFilename, ''\0'' ); * e0 h: \3 V+ c6 {: n" F d & H) c1 P1 y2 u9 L, c1 ]/ N
// Now move it back one character. - ?% g5 m( a; P1 }) i. {( v( I0 D4 c pLastChar--; . m; x3 ]9 J) b0 ^( k: h $ @7 ?# L5 Y5 X" H, ~" s( z0 E if ( *pLastChar != ''\\'' ); U& q) `0 C) F7 q) J4 |
strcat ( szConfigFilename, "\\" );3 b/ I$ }9 z0 a- |- T* }& B, r
5 B- P$ @" I6 R7 i7 p
// Add on the name of the config file., \1 [: ^$ o0 v. W" s$ C8 C
strcat ( szConfigFilename, "config.bin" ); 7 J& B: G+ \ e % w4 ]) E; v; f: l6 R* W& @* ? // If the caller''s buffer is big enough, return the filename. 7 a4 a7 U4 D% s' m8 d# g; G if ( strlen ( szConfigFilename ) >= nBuffSize ) $ N7 Z2 Q3 t' I% H return false; h6 Q7 i% D! h9 Q1 Q# K: J
else 0 k1 {2 n0 d$ x$ e$ T: J {% \% A, t1 ~, ^7 J. R2 A
strcpy ( pszName, szConfigFilename ); 8 k1 t; y5 E; u# s return true; - `" [; V( Z: A1 C0 J0 @; m } ) G7 J& a% B; U} </PRE> 这是一段很健壮的代码,然而在遇到 DBCS 字符时它将会出错。让我们来看看为什么。假设一个日本用户使用了你的程序,把它安装在 C:\<IMG src="http://www.vckbase.com/document/journal/vckbase30/images/youkoso.gif" border=0>。下面是这个名字在内存中的存储形式: ! Q. O* y7 P+ r; P " O2 ^( Z, O7 j; Q6 Z
<TABLE> 0 r5 _6 q: {! U6 ~! t3 v" U u ) d n& c( w! c1 e3 i1 N<TR> 3 T+ x! h" u& b' U4 U+ Z4 s0 X<TD align=middle width="12%">43</TD> 8 r) w; r+ g: S" f7 r: X<TD align=middle width="12%"><FONT color=#990000>3A</FONT></TD>8 R. F6 g' r) N' t q4 H5 j) g
<TD align=middle width="12%"><FONT color=#0000ff>5C</FONT></TD>& M; h+ R1 O( G% a
<TD align=middle width="12%">83 88</TD>% S' z. _: s; C5 z M1 K2 [& M
<TD align=middle width="13%">83 45</TD>. @( d/ c2 N: f _5 h3 T+ E6 n
<TD align=middle width="13%">83 52</TD> 5 S" Y0 r, T0 k9 M<TD align=middle width="13%">83 <FONT color=#0000ff>5C</FONT></TD> ^2 L, `, C. q; B3 p+ U
<TD align=middle width="13%">00</TD></TR> 0 e' M6 Q- C* o% ~- U0 b; v+ _<TR> % w7 a# Q+ R5 [/ k$ S4 e' x% u<TD align=middle width="12%"> </TD>" a5 B7 B( m7 j: ]. O
<TD align=middle width="12%"> </TD> 5 V# p" i6 z; R" S5 d6 r<TD align=middle width="12%"> </TD>1 D$ Q _# C* L% H- H) [
<TD align=middle width="12%"><FONT color=#990000>LB TB </FONT></TD> 9 C4 t; z J$ U' I4 o9 v1 g# @' y<TD align=middle width="13%"><FONT color=#990000>LB TB </FONT></TD> 7 b" U2 k2 E' A<TD align=middle width="13%"><FONT color=#990000>LB TB </FONT></TD> 5 [6 S1 r# h4 W9 @9 `<TD align=middle width="13%"><FONT color=#990000>LB TB </FONT></TD> 5 w P6 Y# `% ]. k5 |" S3 _<TD align=middle width="13%"> </TD></TR>5 Z3 A/ J- F+ _$ _$ Y+ u+ H
<TR>3 s5 c: A. J* J
<TD align=middle width="12%">C</TD> - e: {3 U# p# a9 ^) E) |% B<TD align=middle width="12%">:</TD> : q# s7 v1 J) l<TD align=middle width="12%">\</TD>. h6 k( R; {6 J H
<TD align=middle width="12%"><IMG src="http://www.vckbase.com/document/journal/vckbase30/images/yo.gif" border=0></TD> ' p4 ~3 v( o; V7 _% ^<TD align=middle width="13%"><IMG src="http://www.vckbase.com/document/journal/vckbase30/images/u.gif" border=0></TD>/ I" e1 Z6 Q) k* w8 b* P, O
<TD align=middle width="13%"><IMG src="http://www.vckbase.com/document/journal/vckbase30/images/ko.gif" border=0></TD>0 m0 N- Z) ~! o% v/ r1 d
<TD align=middle width="13%"><IMG src="http://www.vckbase.com/document/journal/vckbase30/images/so.gif" border=0></TD>3 M# U. b2 Q! o* D! Y3 W
<TD align=middle width="13%">EOS</TD></TR></TABLE> 0 ]# z: @ R% c0 j2 I/ i& K& d<> 当使用 GetConfigFileName() 检查尾部的''\\''时,它寻找安装目录名中最后的非0字节,看它是等于''\\''的,所以没有重新增加一个''\\''。结果是代码返回了错误的文件名。 ! {) R* P: s7 g" U* H 哪里出错了呢?看看上面两个被用蓝色高量显示的字节。斜杠''\\''的值是0x5c。'' ''的值是83 5c。上面的代码错误的读取了一个 trail byte,把它当作了一个字符。- x7 k5 W# z$ r# Y+ ?) j/ c
正确的后向遍历方法是使用能够识别DBCS字符的函数,使指针移动正确的字节数。下面是正确的代码。(指针移动的地方用红色标明) </P><RE>bool FixedGetConfigFileName ( char* pszName, size_t nBuffSize ): Z0 O" n @% s1 W6 j9 h% ?
{6 x. f- q2 `; Z: i% M% y) T
char szConfigFilename[MAX_PATH];! H3 y2 p5 h' i! L. y/ ?* M
# c# q( X6 {& \9 N2 a // Read install dir from registry... we''ll assume it succeeds. . y4 O* E' Q! B+ O. W7 F( u( R# v! ^ ' I' a. H4 {! ]: I
// Add on a backslash if it wasn''t present in the registry value." P% t6 Y, X" t/ r
// First, get a pointer to the terminating zero. 4 c0 m( ]8 U: [# P& v1 ^ char* pLastChar = _mbschr ( szConfigFilename, ''\0'' );9 N0 g1 d7 N$ T' o7 w+ R
1 D9 f5 \3 ~+ x0 L6 J
// Now move it back one double-byte character. ; c5 o5 l: U/ h5 D! }1 ~, I4 [ <FONT color=#ff0000> pLastChar = CharPrev ( szConfigFilename, pLastChar );</FONT> [* L8 i) ] ]+ u$ k2 R
7 m3 b, ?6 P: X- `6 z A1 u if ( *pLastChar != ''\\'' ) 3 s" I" b# n" T- W _mbscat ( szConfigFilename, "\\" ); 2 i( e8 w1 G! _$ q 7 q6 X7 o2 K; S( x
// Add on the name of the config file.) q/ w& E" Q* A
_mbscat ( szConfigFilename, "config.bin" );. l* M4 O' Q, J C4 L( d
. a7 E: n; H" V9 v // If the caller''s buffer is big enough, return the filename. * I3 i7 u9 c( Z2 X6 k if ( _mbslen ( szInstallDir ) >= nBuffSize ) * M9 K( D& S; k/ U' R9 ^ return false;% H! B6 x. ~- o9 h- F/ D2 t
else 4 P( a8 h' R% @& `1 u( M+ { {; C& m/ a8 {, ~' p; D
_mbscpy ( pszName, szConfigFilename ); * }* a& l6 s# T7 V* H6 j return true;. Q: o% \0 R5 W% v/ C6 I
}; C* V+ r" n! J( Y
}$ E; d4 Q0 k# v7 m
</PRE> 上面的函数使用CharPrev() API使pLastChar向后移动一个字符,这个字符可能是两个字节长。在这个版本里,if条件正常工作,因为lead byte永远不会等于0x5c。7 K# \; @& {% g4 k' E" B
让我们来想象一个违背规则1的场合。例如,你可能要检测一个用户输入的文件名是否多次出现了'':''。如果,你使用++操作来遍历字符串,而不是使用CharNext(),你可能会发出不正确的错误警告如果恰巧有一个trail byte它的值的等于'':''的值。 # S! s4 S; a1 e与规则2相关的关于字符串索引的规则:<RE>2a. 永远不要使用减法去得到一个字符串的索引。</PRE>6 R2 N- w8 g7 ^0 Z: H! t3 D% j `
<>违背这条规则的代码和违背规则2的代码很相似。例如,</P><RE>char* pLastChar = &szConfigFilename [strlen(szConfigFilename) - 1];</PRE> 8 H) i0 d* d Z6 ~<>这和向后移动一个指针是同样的效果。: ~2 ]( H4 D$ L R% F
% o* O4 p v2 y' R1 i/ S: y4 |: C<IMG src="http://www.vckbase.com/document/image/paragraph.gif"><B> 回到关于str***()和_mbs***()的区别</B> k8 ?- j' o" Y. w7 o' z) F7 K& F# ^ & Y- f f* M" p) p 现在,我们应该很清楚为什么_mbs***()函数是必需的。Str***()函数根本不考虑DBCS字符,而_mbs***()考虑。如果,你调用strrchr("C:\\ ", ''\\''),返回结果可能是错误的,然而_mbsrchr()将会认出最后的双字节字符,返回一个指向真的''\\''的指针。 ; G0 m4 h/ p9 x/ D# x4 _ 关于字符串函数的最后一点:str***()和_mbs***()函数认为字符串的长度都是以char来计算的。所以,如果一个字符串包含3个双字节字符,_mbslen()将会返回6。Unicode函数返回的长度是按wchar_t来计算的。例如,wcslen(L"Bob")返回3。 % k, W9 c. F9 j0 c9 H 8 f. T2 M& D8 l; D+ D<IMG src="http://www.vckbase.com/document/image/paragraph.gif"><B> Win32 API中的MBCS和Unicode</B>) ^- K* e W: a& W ]