" a! [$ |. A. j9 A/ b9 b // Read install dir from registry... we''ll assume it succeeds.3 ~7 H# Y" x5 C
: b# u9 l" {/ i. G7 r+ T& z // Add on a backslash if it wasn''t present in the registry value., N% H) w. f! K& {/ T
// First, get a pointer to the terminating zero. / S) x8 E2 ~0 {2 p char* pLastChar = strchr ( szConfigFilename, ''\0'' );( G* I6 |4 u% U# `
* U5 ~8 c# e2 I& G* D! |! Q7 W
// Now move it back one character.0 b; j7 P S# C
pLastChar--; j) x$ B T7 l, k4 f
4 O9 ^# a$ K Q4 c/ F3 M" ^* p if ( *pLastChar != ''\\'' )$ q# S9 \2 F; @7 F
strcat ( szConfigFilename, "\\" ); * ~: z3 u8 G' K! y% _+ Z! x" I - w$ f' e/ U( L. X7 q- {7 P6 A6 r // Add on the name of the config file. * S/ C# B8 x# ^2 f7 q strcat ( szConfigFilename, "config.bin" ); * Z4 {: P# h5 J% K( _# e# b) U . ^ E, \, A, u1 W$ d
// If the caller''s buffer is big enough, return the filename. ( U* W) _+ q* l2 r2 b0 j6 v. A if ( strlen ( szConfigFilename ) >= nBuffSize )* g8 j( J# H/ }) L, `( y
return false;9 v1 S; Z. `2 I; r
else* i$ c$ a# r! S# H
{ - d$ d2 f: ^: h! q+ w) c { strcpy ( pszName, szConfigFilename ); % O* Z: W6 n! J- v return true; 2 {* l6 l9 Q, e0 m/ `7 h7 d& n } ( `& z- V& Z% o: v/ ^# G2 ^} </PRE> 这是一段很健壮的代码,然而在遇到 DBCS 字符时它将会出错。让我们来看看为什么。假设一个日本用户使用了你的程序,把它安装在 C:\<IMG src="http://www.vckbase.com/document/journal/vckbase30/images/youkoso.gif" border=0>。下面是这个名字在内存中的存储形式:3 x* N* k" I, X# M0 @
2 C( r m9 h5 g" \% E7 z% `3 B8 Z
<TABLE>- T$ P, _, D" v6 b }
; y$ e8 @9 ~' w! X // Read install dir from registry... we''ll assume it succeeds.- i: K- |4 W( p
+ d* ?; b7 C% V' w8 ? // Add on a backslash if it wasn''t present in the registry value., {: S+ Y" `; d# }2 B0 w3 G
// First, get a pointer to the terminating zero.# Y5 t# ]5 c% a- B
char* pLastChar = _mbschr ( szConfigFilename, ''\0'' );( L' Q; l# c: U9 Y, q! y7 o8 I/ d
Z) d" ~5 C( T5 \" a9 |2 \ // Now move it back one double-byte character.) A% h6 P3 I) \% N
<FONT color=#ff0000> pLastChar = CharPrev ( szConfigFilename, pLastChar );</FONT>8 g) _8 k/ B7 _5 M; |
' j& B- `$ ^. l# X7 E! \ if ( *pLastChar != ''\\'' )2 m% z5 P# }0 G
_mbscat ( szConfigFilename, "\\" );/ l2 f" C3 S: Q$ n
% H! {9 m; B* d- b
// Add on the name of the config file. 2 A2 y7 o) y9 y9 Z _mbscat ( szConfigFilename, "config.bin" ); 2 I4 F' Y7 T: g! O0 Z4 a0 j* W. M5 v' j6 _9 i4 L( g5 F2 b1 O
// If the caller''s buffer is big enough, return the filename.4 L* n% E/ V O L% x% x+ A
if ( _mbslen ( szInstallDir ) >= nBuffSize )$ e) N4 z; A% ~2 E; k- ~" {" ]
return false;" v( e" f; D( t1 v1 X/ H
else + r* I! W/ r. B9 F P" j5 K3 T {$ O3 f& F1 v7 C' i$ H9 V3 j9 \8 S
_mbscpy ( pszName, szConfigFilename );7 g" I+ M8 p- H ~7 W
return true; ( h/ H6 M+ `# k# _4 `7 a } ' ^6 u3 g* I9 f, y! ?} 5 ~: |+ \, F, U' w+ q" r</PRE> 上面的函数使用CharPrev() API使pLastChar向后移动一个字符,这个字符可能是两个字节长。在这个版本里,if条件正常工作,因为lead byte永远不会等于0x5c。. b; d! V" q% e5 p0 h4 R
让我们来想象一个违背规则1的场合。例如,你可能要检测一个用户输入的文件名是否多次出现了'':''。如果,你使用++操作来遍历字符串,而不是使用CharNext(),你可能会发出不正确的错误警告如果恰巧有一个trail byte它的值的等于'':''的值。 : V$ S5 J" ]. c e8 M3 M6 L与规则2相关的关于字符串索引的规则:<RE>2a. 永远不要使用减法去得到一个字符串的索引。</PRE>% ~: t; D; B& N5 Z/ z; j. V& e
<>违背这条规则的代码和违背规则2的代码很相似。例如,</P><RE>char* pLastChar = &szConfigFilename [strlen(szConfigFilename) - 1];</PRE> , I1 Q+ N- ~& J2 t' s$ M<>这和向后移动一个指针是同样的效果。 g, q, y1 z( Y7 a k, f
8 \+ G# R# K& [: Q' I$ ]
<IMG src="http://www.vckbase.com/document/image/paragraph.gif"><B> 回到关于str***()和_mbs***()的区别</B>& L& B" e% ^& Z( h0 X
( _# E9 i, u* ^) L8 A: c 现在,我们应该很清楚为什么_mbs***()函数是必需的。Str***()函数根本不考虑DBCS字符,而_mbs***()考虑。如果,你调用strrchr("C:\\ ", ''\\''),返回结果可能是错误的,然而_mbsrchr()将会认出最后的双字节字符,返回一个指向真的''\\''的指针。 }! g0 l! o" a" a' D% I v
关于字符串函数的最后一点:str***()和_mbs***()函数认为字符串的长度都是以char来计算的。所以,如果一个字符串包含3个双字节字符,_mbslen()将会返回6。Unicode函数返回的长度是按wchar_t来计算的。例如,wcslen(L"Bob")返回3。# @6 Z$ J; s' a1 {- m' C8 A) e; t5 z
/ H# M# S0 p$ p V
<IMG src="http://www.vckbase.com/document/image/paragraph.gif"><B> Win32 API中的MBCS和Unicode</B> . m' e4 F* B7 g) o. M5 K: C6 L# W9 y4 [- E; O% k1 F9 b* m6 v
两组 APIs: 4 v; p5 W1 X; I+ G) Y5 |0 @
尽管你也许从来没有注意过,Win32中的每个与字符串相关的API和message都有两个版本。一个版本接受MBCS字符串,另一个接受Unicode字符串。例如,根本没有SetWindowText()这个API,相反,有SetWindowTextA()和SetWindowTextW()。后缀A表明这是MBCS函数,后缀W表示这是Unicode版本的函数。 $ A7 }1 S& N- q' V, P1 f 当你 build 一个 Windows 程序,你可以选择是用 MBCS 或者 Unicode APIs。如果,你曾经用过VC向导并且没有改过预处理的设置,那表明你用的是MBCS版本。那么,既然没有 SetWindowText() API,我们为什么可以使用它呢?winuser.h头文件包含了一些宏,例如: </P><RE>BOOL WINAPI SetWindowTextA ( HWND hWnd, LPCSTR lpString ); " {9 \5 o7 k, V) R; Z; ABOOL WINAPI SetWindowTextW ( HWND hWnd, LPCWSTR lpString ); / u& l6 z: B5 g$ q" x & W- p8 F# [: a( T! y8 [#ifdef UNICODE % B3 t/ C9 g7 I$ T#define SetWindowText SetWindowTextW8 p$ ^* C% J4 ?* j
#else $ U1 T: m' Q+ u2 U, N#define SetWindowText SetWindowTextA , v7 Z0 c# i2 ?#endif </PRE>当使用MBCS APIs来build程序时,UNICODE没有被定义,所以预处理器看到:<RE>#define SetWindowText SetWindowTextA</PRE> $ C- S4 S5 K7 c: h<> 这个宏定义把所有对SetWindowText的调用都转换成真正的API函数SetWindowTextA。(当然,你可以直接调用SetWindowTextA() 或者 SetWindowTextW(),虽然你不必那么做。) $ e- q7 s$ `# ?8 g l 所以,如果你想把默认使用的API函数变成Unicode版的,你可以在预处理器设置中,把_MBCS从预定义的宏列表中删除,然后添加UNICODE和_UNICODE。(你需要两个都定义,因为不同的头文件可能使用不同的宏。) 然而,如果你用char来定义你的字符串,你将会陷入一个尴尬的境地。考虑下面的代码:</P><RE>HWND hwnd = GetSomeWindowHandle();& ^* E$ V4 l( f: p s
char szNewText[] = "we love Bob!"; 6 P) Y/ o! B, a6 D+ o8 I, G. fSetWindowText ( hwnd, szNewText );</PRE> # |6 V0 K' v' d. s# O! w, p. W' L: H8 r: R<>在预处理器把SetWindowText用SetWindowTextW来替换后,代码变成:</P><RE>HWND hwnd = GetSomeWindowHandle(); / O1 Y3 j2 C( N' P8 J# x# hchar szNewText[] = "we love Bob!";/ w6 y6 D" e1 T* V+ v+ a
SetWindowTextW ( hwnd, szNewText );</PRE> 0 z* Q" y' l2 m a$ x<> 看到问题了吗?我们把单字节字符串传给了一个以Unicode字符串做参数的函数。解决这个问题的第一个方案是使用 #ifdef 来包含字符串变量的定义:</P><RE>HWND hwnd = GetSomeWindowHandle();! H5 a. b8 D* k: \, Y( K/ G
#ifdef UNICODE 1 y) k( A0 x' n. S/ Y2 F1 ~) hwchar_t szNewText[] = L"we love Bob!";# c6 d) @# Z* \* n! R
#else M4 [7 i& ?' X+ G! cchar szNewText[] = "we love Bob!"; ) ^& o. X' s5 C8 x: C* D: {$ {#endif 3 ], X* R2 L5 W# Q3 NSetWindowText ( hwnd, szNewText );</PRE> ; R0 r5 b5 a4 O w<>你可能已经感受到了这样做将会使你多么的头疼。完美的解决方案是使用TCHAR. 6 V' e) G; {7 j, x! z / { y) D+ N- ]: c! L. H5 ~<IMG src="http://www.vckbase.com/document/image/paragraph.gif"><B> 使用TCHAR</B>4 w4 @' H6 O4 L6 n7 N6 j
; [% A$ E/ z$ Y0 A
TCHAR是一种字符串类型,它让你在以MBCS和UNNICODE来build程序时可以使用同样的代码,不需要使用繁琐的宏定义来包含你的代码。TCHAR的定义如下:</P><RE>#ifdef UNICODE - b' b2 S6 J4 D$ ?' J8 ~typedef wchar_t TCHAR;* {1 Z' ~& J! ~& M. A% ]
#else 8 c0 q) z& [( ytypedef char TCHAR; + U9 G; L {1 X: L+ ]0 A) C8 ~#endif</PRE>4 F$ Q( b% p9 K/ k$ V& |! Z
<>所以用MBCS来build时,TCHAR是char,使用UNICODE时,TCHAR是wchar_t。还有一个宏来处理定义Unicode字符串常量时所需的L前缀。</P><RE>#ifdef UNICODE S8 x& O& ?7 T9 Y' }$ O#define _T(x) L##x , b4 D0 G) N# X8 e& f#else( @0 `: q/ b9 k* R l4 [4 o% ]
#define _T(x) x Z6 e( J8 Z5 X$ k- H1 b9 C( B#endif</PRE>, g7 I- r" B9 i7 Z9 Y0 f5 A! V! c
<P> ##是一个预处理操作符,它可以把两个参数连在一起。如果你的代码中需要字符串常量,在它前面加上_T宏。如果你使用Unicode来build,它会在字符串常量前加上L前缀。</P><PRE>TCHAR szNewText[] = _T("we love Bob!");</PRE> ( S4 i; s$ Y/ v- z( T<P> 像是用宏来隐藏SetWindowTextA/W的细节一样,还有很多可以供你使用的宏来实现str***()和_mbs***()等字符串函数。例如,你可以使用_tcsrchr宏来替换strrchr()、_mbsrchr()和wcsrchr()。_tcsrchr根据你预定义的宏是_MBCS还是UNICODE来扩展成正确的函数,就像SetWindowText所作的一样。+ q& P( Z1 N Z
不仅str***()函数有TCHAR宏。其他的函数如, _stprintf(代替sprinft()和swprintf()),_tfopen(代替fopen()和_wfopen())。 MSDN中"Generic-Text Routine Mappings."标题下有完整的宏列表。 0 I7 V4 G+ `4 F+ G2 a 9 q% G- L8 v9 }0 V# S2 K<IMG src="http://www.vckbase.com/document/image/paragraph.gif"><B> 字符串和TCHAR typedefs</B>$ Z+ e% \& | A& n, G
, |% ]! m" X: ^* `9 `7 F
由于Win32 API文档的函数列表使用函数的常用名字(例如,"SetWindowText"),所有的字符串都是用TCHAR来定义的。(除了XP中引入的只适用于Unicode的API)。下面列出一些常用的typedefs,你可以在msdn中看到他们。</P> 5 q1 L! M' c8 v8 z+ _<TABLE>0 j6 W, f* |3 f: ]! }8 N2 [2 X" r
8 t5 i/ g3 o# x( E. S<TR>3 {/ I) ^: U% S; w4 B5 g0 R/ B6 c
<TD align=middle width="16%"><B>type </B></TD>7 D* b2 K/ e; s z( `/ M
<TD align=middle width="42%"><B>Meaning in MBCS builds </B></TD>. \* |) E3 C) R" B9 J/ l, M) Q
<TD align=middle width="42%"><B>Meaning in Unicode builds</B></TD></TR> B, T% `1 z$ m& k. R! R
<TR> ( r8 I) p! F8 r3 G<TD width="16%">WCHAR</TD>" \' a9 i+ c } Q" l" F' O
<TD width="42%">wchar_t</TD> # m, X. t9 @$ E1 C/ _+ K<TD width="42%">wchar_t</TD></TR> . n0 A6 p. Z$ e* j; d<TR> 5 M' A3 R6 M3 o5 z$ N<TD width="16%">LPSTR </TD> 9 U( S( U! R5 X2 K<TD width="42%">zero-terminated string of char (char*)</TD> ; p( m( A# r& c% X. \/ V<TD width="42%">zero-terminated string of char (char*)</TD></TR>1 w9 L$ X6 |! A7 H# _! p
<TR>* M9 H8 t1 D+ J7 l4 H2 _9 I
<TD width="16%">LPCSTR </TD> 8 q( m0 i5 q" b+ u) H, L<TD width="42%">constant zero-terminated string of char (const char*)</TD> - h3 |8 `! M9 Y1 S ~5 F6 `8 Q+ B<TD width="42%">constant zero-terminated string of char (const char*)</TD></TR> 6 d6 r R$ A8 L! U; Q/ c W6 w: q: ^<TR>4 c" `+ H' y/ Z L0 u6 |) W; B
<TD width="16%">LPWSTR</TD>0 Y5 A5 N! b3 X# j
<TD width="42%">zero-terminated Unicode string (wchar_t*) </TD>- X. |( ^5 V# D0 s% u5 Z0 V. L0 [
<TD width="42%">zero-terminated Unicode string (wchar_t*)</TD></TR>! e4 V9 E4 h; w+ E0 S( f
<TR> + D. o% L v/ G* W<TD width="16%">LPCWSTR</TD>; a7 b" S. u( X8 G7 N% h
<TD width="42%">constant zero-terminated Unicode string (const wchar_t*)</TD> r$ r8 y( s, Q! e ^& S/ r<TD width="42%">constant zero-terminated Unicode string (const wchar_t*) </TD></TR>: U# i0 _+ @4 [, ~8 `4 p# m9 X
<TR>1 a( A9 s' L# G6 Q& Y& @4 L: C8 t9 b
<TD width="16%"><XXXXIME xime="7">TCHAR</XXXXIME></TD> + A% m0 c; @0 q3 y% ~6 W( e! y6 Z c5 B<TD width="42%"><XXXXIME xime="7">char</XXXXIME></TD>, b, D' V, W# W0 j2 i4 `+ d
<TD width="42%"><XXXXIME xime="7">wchar_t</XXXXIME></TD></TR> " B; F9 h- T! o! `4 U6 m7 y' Q4 w: P8 x<TR>/ `) {! p: a' g J$ y
<TD width="16%">LPTSTR</TD> + {- ], N: M2 c c<TD width="42%">zero-terminated string of TCHAR (TCHAR*) </TD> $ r4 x5 E1 e, B& S* k<TD width="42%">zero-terminated string of TCHAR (TCHAR*)</TD></TR> ! N2 K6 U) Q Y# d<TR> " Q: }" V' h I) q- u% u* d! a- V$ M<TD width="16%">LPCTSTR </TD> ; i/ }; ?0 e" d0 ] a9 d, s<TD width="42%">constant zero-terminated string of TCHAR (const TCHAR*)</TD>3 w. x3 I5 Q* k; ]% d& L4 C5 h
<TD width="42%">constant zero-terminated string of TCHAR (const TCHAR*)</TD></TR></TABLE>/ |* M" e5 J$ ~& e( \
<P><IMG src="http://www.vckbase.com/document/image/paragraph.gif"><B> 何时使用 TCHAR 和 Unicode</B> p% s; ~( L" _; R5 t * j2 U9 M2 s; x" k, K 到现在,你可能会问,我们为什么要使用Unicode。我已经用了很多年的char。下列3种情况下,使用Unicode将会使你受益:</P> 0 t# K/ Y- s0 R3 Z! l; L" h Y<DIR>2 n/ ^1 |* Q7 I
<LI>1.你的程序只运行在Windows NT系统中。 / V% H! I: x2 `) r0 g4 Z
<LI>2. 你的程序需要处理超过MAX_PATH个字符长的文件名。 , A& A! E! v+ ?; V5 S6 ]% J- t" c7 b<LI>3. 你的程序需要使用XP中引入的只有Unicode版本的API. </LI></DIR>, f6 f7 l/ g. M
<P> Windows 9x 中大多数的 API 没有实现 Unicode 版本。所以,如果你的程序要在windows 9x中运行,你必须使用MBCS APIs。然而,由于NT系统内部都使用Unicode,所以使用Unicode APIs将会加快你的程序的运行速度。每次,你传递一个字符串调用MBCS API,操作系统会把这个字符串转换成Unicode字符串,然后调用对应的Unicode API。如果一个字符串被返回,操作系统还要把它转变回去。尽管这个转换过程被高度优化了,但它对速度造成的损失是无法避免的。- U; w+ G) b) R P+ o! O$ Q
只要你使用Unicode API,NT系统允许使用非常长的文件名(突破了MAX_PATH的限制,MAX_PATH=260)。使用Unicode API的另一个优点是你的程序会自动处理用户输入的各种语言。所以一个用户可以输入英文,中文或者日文,而你不需要额外编写代码去处理它们。5 l# c3 J" V: v: x1 P* r1 F$ u
最后,随着windows 9x产品的淡出,微软似乎正在抛弃MBCS APIs。例如,包含两个字符串参数的SetWindowTheme() API只有Unicode版本的。使用Unicode来build你的程序将会简化字符串的处理,你不必在MBCS和Unicdoe之间相互转换。 9 y& l V+ p" Y 即使你现在不使用Unicode来build你的程序,你也应该使用TCHAR及其相关的宏。这样做不仅可以的代码可以很好地处理DBCS,而且如果将来你想用Unicode来build你的程序,你只需要改变一下预处理器中的设置就可以实现了。 2 m, B& z. l2 n : H8 a" b- k. U# S9 w1 w</P> $ z) j2 Q; s8 d 3 ^7 S8 R6 _( L0 Y4 j. j: ~) J+ F0 A. `% |/ j' d6 c
<p>5 a3 H8 \; N! y
<p></TD></TR>) `0 @% P2 s6 g% d
<TR>$ K+ T& A! K' C
<TD><IMG src="http://www.vckbase.com/document/image/paragraph.gif"><B> 作者简介</B> & T3 N4 y# v K5 w Michael Dunn:居住在阳光城市洛杉矶。他是如此的喜欢这里的天气以致于想一生都住在这里。他在4年级时开始编程,那时用的电脑是Apple //e。1995年,在 UCLA 获得数学学士学位,随后在Symantec 公司做 QA 工程师,在 Norton AntiVirus 组工作。他自学了 Windows 和 MFC 编程。1999-2000年,他设计并实现了 Norton AntiVirus 的新界面。 ! k; [+ f( ]" ^" I5 j& ^ Michael 现在在 Napster(一个提供在线订阅音乐服务的公司)做开发工作,他还开发了UltraBar,一个IE工具栏插件,它可以使网络搜索更加容易,给了 googlebar 以沉重打击;他还开发了 CodeProject SearchBar;与人共同创建了 Zabersoft 公司,该公司在洛杉矶和丹麦的 Odense 都设有办事处。 & U) a! a" b, T4 r) l 他喜欢玩游戏。爱玩的游戏有 pinball, bike riding,偶尔还玩 PS, Dreamcasth 和 MAME 游戏。他因忘了自己曾经学过的语言:法语、汉语、日语而感到悲哀。</TD></TR></TABLE></P>