5 m7 `$ ]3 D9 n/ [ // Read install dir from registry... we''ll assume it succeeds. $ v; j( H" d$ V( j+ R - l! f0 U6 z1 V* `# A // Add on a backslash if it wasn''t present in the registry value. 1 d \. V* X) m# n // First, get a pointer to the terminating zero.: R' r5 `2 D9 p( Q+ l5 m5 w
char* pLastChar = strchr ( szConfigFilename, ''\0'' ); 6 X) a* [* |1 C# z. T 6 o; C6 Z0 h2 \2 R. p* @& g% I) e // Now move it back one character.2 u- x! e7 l! @3 R( r8 y
pLastChar--; : }$ `# R/ y/ Z, x: b ~- A* T, Z V! [8 K# [
if ( *pLastChar != ''\\'' )8 y" l* @1 V9 ]7 J' U- O
strcat ( szConfigFilename, "\\" );8 ~" ^$ y4 b g$ L- Z! N
1 a$ E; n. z3 X4 _ // Add on the name of the config file. , I& u F# R' X& e! S# `9 S strcat ( szConfigFilename, "config.bin" );2 s$ v* \2 r9 ]. J; r+ C
7 Y$ L2 ^# U# O6 y! u; B
// If the caller''s buffer is big enough, return the filename. - X' J9 j4 }$ l) O* q if ( strlen ( szConfigFilename ) >= nBuffSize ) 4 U, E/ d* W' G( r% F return false;3 T4 [! o$ J. \# T
else . F# W M" J- }7 V- ~ { 4 e7 d$ t+ i" ^0 {. V- T3 B+ g strcpy ( pszName, szConfigFilename );* X' ~. |+ H7 e9 \
return true;$ p/ g' V) p9 A
}7 `+ {4 y8 X: w
} </PRE> 这是一段很健壮的代码,然而在遇到 DBCS 字符时它将会出错。让我们来看看为什么。假设一个日本用户使用了你的程序,把它安装在 C:\<IMG src="http://www.vckbase.com/document/journal/vckbase30/images/youkoso.gif" border=0>。下面是这个名字在内存中的存储形式:7 u$ W& ~2 f. P! I# `
1 `$ n, P8 ~6 E
<TABLE> : a# f; `5 l0 x2 {3 r* `- [ 4 F% V% T$ ^& l6 k) h+ |0 |$ q<TR> % g7 B9 z8 k$ [3 g1 R2 t; C3 F7 U+ j<TD align=middle width="12%">43</TD>( k: U0 M# u& J3 }- s
<TD align=middle width="12%"><FONT color=#990000>3A</FONT></TD>! }/ z0 O) U- |) w
<TD align=middle width="12%"><FONT color=#0000ff>5C</FONT></TD> : e* `4 q5 G, X4 r<TD align=middle width="12%">83 88</TD>4 @2 k; B# z% a$ k
<TD align=middle width="13%">83 45</TD> 3 P' q- `$ ` b; X" I% o<TD align=middle width="13%">83 52</TD>& |% B+ o( w% w2 f: y9 {
<TD align=middle width="13%">83 <FONT color=#0000ff>5C</FONT></TD> 7 ?5 J! ]9 `$ G X6 j' l& T3 A<TD align=middle width="13%">00</TD></TR> 5 d# e$ O, ^+ n9 ^. ?9 P2 ^& k<TR> - O9 _. s! h0 h) i6 J<TD align=middle width="12%"> </TD> 6 k* L+ E9 x8 M Z) b<TD align=middle width="12%"> </TD>& @: q3 X. h4 D) e* N
<TD align=middle width="12%"> </TD>, F) }! M( {/ Q& }2 L
<TD align=middle width="12%"><FONT color=#990000>LB TB </FONT></TD> , k! e1 b, a& K: {! J: {<TD align=middle width="13%"><FONT color=#990000>LB TB </FONT></TD> % U4 p+ ]/ j! r! }<TD align=middle width="13%"><FONT color=#990000>LB TB </FONT></TD>- \3 m7 e8 F: P' X. s7 k; Z
<TD align=middle width="13%"><FONT color=#990000>LB TB </FONT></TD> ; ?6 k, v/ X }7 |. m# X<TD align=middle width="13%"> </TD></TR> ! s7 l7 ~+ n% j! A<TR> 8 v8 ^ w. O/ O& k) x9 O+ q8 Y% g<TD align=middle width="12%">C</TD> $ V! n% o. a* G<TD align=middle width="12%">:</TD>1 n$ B# w) y9 l3 J
<TD align=middle width="12%">\</TD> Z# p# d. W+ ~; U B v<TD align=middle width="12%"><IMG src="http://www.vckbase.com/document/journal/vckbase30/images/yo.gif" border=0></TD> % l- h' E! r4 ?<TD align=middle width="13%"><IMG src="http://www.vckbase.com/document/journal/vckbase30/images/u.gif" border=0></TD>* d- m3 F0 u, V* U
<TD align=middle width="13%"><IMG src="http://www.vckbase.com/document/journal/vckbase30/images/ko.gif" border=0></TD> " ~1 \9 P0 ^! P; m0 @<TD align=middle width="13%"><IMG src="http://www.vckbase.com/document/journal/vckbase30/images/so.gif" border=0></TD> 6 d v8 \) U" e0 d* D0 g2 w- M h3 m<TD align=middle width="13%">EOS</TD></TR></TABLE>% f/ p5 {' T. F* T% J
<> 当使用 GetConfigFileName() 检查尾部的''\\''时,它寻找安装目录名中最后的非0字节,看它是等于''\\''的,所以没有重新增加一个''\\''。结果是代码返回了错误的文件名。" w9 {0 V0 d" F! R
哪里出错了呢?看看上面两个被用蓝色高量显示的字节。斜杠''\\''的值是0x5c。'' ''的值是83 5c。上面的代码错误的读取了一个 trail byte,把它当作了一个字符。 5 c2 j( S2 |; f6 M: R! g F) p6 K 正确的后向遍历方法是使用能够识别DBCS字符的函数,使指针移动正确的字节数。下面是正确的代码。(指针移动的地方用红色标明) </P><RE>bool FixedGetConfigFileName ( char* pszName, size_t nBuffSize ) + u, p! E( h$ L( T& q4 J5 M{ 9 P7 Q$ ], A9 g7 V8 A" d7 l char szConfigFilename[MAX_PATH];9 I1 j& {. R% W8 X4 ^- |& y% t
+ `6 `7 D T6 s
// Read install dir from registry... we''ll assume it succeeds. , V5 e, G1 [+ D! F6 \ " d# `8 D. \' y4 `' U3 L# D. t // Add on a backslash if it wasn''t present in the registry value. ; k) A$ y, `. ` // First, get a pointer to the terminating zero.8 _! k% X5 W* B' }) |( q' |
char* pLastChar = _mbschr ( szConfigFilename, ''\0'' );+ _8 l! \; h9 [6 \) @
+ y# k# l: A& R3 m9 P3 F // Now move it back one double-byte character.- f4 R2 O2 V( D3 c2 q7 s1 {3 T
<FONT color=#ff0000> pLastChar = CharPrev ( szConfigFilename, pLastChar );</FONT> % {! e7 R" k; t* O" ` . x* a( U' N( i" b4 P
if ( *pLastChar != ''\\'' ) " w) d. Q# }3 m! f& _ _mbscat ( szConfigFilename, "\\" );5 h( G! y) ?! x% a
, r. u$ e3 h0 T8 g! i // Add on the name of the config file. * z) a) V; F$ P! m) Y* ? _mbscat ( szConfigFilename, "config.bin" );. l8 |8 O5 {1 B9 ^1 ] e4 R
1 a. m8 C7 O6 b8 r+ e# Q // If the caller''s buffer is big enough, return the filename. + o, d1 n0 I2 |% }# y; Z9 V7 t+ r9 e if ( _mbslen ( szInstallDir ) >= nBuffSize )" g2 ^- _3 S3 n- |! R/ O% ~# q
return false;0 D' w% _6 ^9 a! T& c4 m
else# @; ]& O! R- `' h5 B* C4 p
{8 b$ s& |% D# z4 H# b+ n0 B
_mbscpy ( pszName, szConfigFilename );+ d6 D; a8 c/ p! y
return true; - Y+ g* o. ~7 R5 s }& D- H9 u" |# S/ P$ V* m0 N
} : L/ B, b, |) m, H/ B/ l0 W</PRE> 上面的函数使用CharPrev() API使pLastChar向后移动一个字符,这个字符可能是两个字节长。在这个版本里,if条件正常工作,因为lead byte永远不会等于0x5c。 ; g; u5 a( e9 }- [) S* [- `8 A1 O 让我们来想象一个违背规则1的场合。例如,你可能要检测一个用户输入的文件名是否多次出现了'':''。如果,你使用++操作来遍历字符串,而不是使用CharNext(),你可能会发出不正确的错误警告如果恰巧有一个trail byte它的值的等于'':''的值。. ]) s8 t# z; L# ]' A: r
与规则2相关的关于字符串索引的规则:<RE>2a. 永远不要使用减法去得到一个字符串的索引。</PRE> " ]. |4 N8 R: q1 ^6 ~ j+ }<>违背这条规则的代码和违背规则2的代码很相似。例如,</P><RE>char* pLastChar = &szConfigFilename [strlen(szConfigFilename) - 1];</PRE> : `% p% ~3 y4 G0 [8 e<>这和向后移动一个指针是同样的效果。6 x0 }7 O* E$ q: {$ s5 ]
# Z: b6 }9 G$ p, A. W
<IMG src="http://www.vckbase.com/document/image/paragraph.gif"><B> 回到关于str***()和_mbs***()的区别</B>8 n" F) x7 z4 j
* A% U0 [/ l% S8 _. ?$ @' u1 |6 P7 ]
现在,我们应该很清楚为什么_mbs***()函数是必需的。Str***()函数根本不考虑DBCS字符,而_mbs***()考虑。如果,你调用strrchr("C:\\ ", ''\\''),返回结果可能是错误的,然而_mbsrchr()将会认出最后的双字节字符,返回一个指向真的''\\''的指针。' j: B! L: d7 w# e7 g$ D
关于字符串函数的最后一点:str***()和_mbs***()函数认为字符串的长度都是以char来计算的。所以,如果一个字符串包含3个双字节字符,_mbslen()将会返回6。Unicode函数返回的长度是按wchar_t来计算的。例如,wcslen(L"Bob")返回3。 & S5 I( p) m$ ?3 N$ N' Z3 g 0 X* Y7 ^9 _& ?3 }9 x9 B<IMG src="http://www.vckbase.com/document/image/paragraph.gif"><B> Win32 API中的MBCS和Unicode</B> ; N( H9 b/ x7 P. q$ Y |. C ( E* H! }# v6 G两组 APIs: 3 }' @( t$ Z. K
尽管你也许从来没有注意过,Win32中的每个与字符串相关的API和message都有两个版本。一个版本接受MBCS字符串,另一个接受Unicode字符串。例如,根本没有SetWindowText()这个API,相反,有SetWindowTextA()和SetWindowTextW()。后缀A表明这是MBCS函数,后缀W表示这是Unicode版本的函数。 . ^3 W- J, ?/ L 当你 build 一个 Windows 程序,你可以选择是用 MBCS 或者 Unicode APIs。如果,你曾经用过VC向导并且没有改过预处理的设置,那表明你用的是MBCS版本。那么,既然没有 SetWindowText() API,我们为什么可以使用它呢?winuser.h头文件包含了一些宏,例如: </P><RE>BOOL WINAPI SetWindowTextA ( HWND hWnd, LPCSTR lpString );- y3 R. z6 Z/ Z8 ]7 s* v
BOOL WINAPI SetWindowTextW ( HWND hWnd, LPCWSTR lpString ); & c( Q1 O+ [; J b% D % b$ g3 p" L" n. }5 E
#ifdef UNICODE2 ?6 _2 H, G* b3 n
#define SetWindowText SetWindowTextW) o& E+ J# y3 v$ P# s. d
#else & ]6 i& I- r" _: Q- d& J#define SetWindowText SetWindowTextA ! r1 z' F7 M! \ @: v#endif </PRE>当使用MBCS APIs来build程序时,UNICODE没有被定义,所以预处理器看到:<RE>#define SetWindowText SetWindowTextA</PRE>; e$ W, l& d* \% I$ b7 f& J& v% e
<> 这个宏定义把所有对SetWindowText的调用都转换成真正的API函数SetWindowTextA。(当然,你可以直接调用SetWindowTextA() 或者 SetWindowTextW(),虽然你不必那么做。) 2 N& m" L3 N8 b3 l3 P7 { 所以,如果你想把默认使用的API函数变成Unicode版的,你可以在预处理器设置中,把_MBCS从预定义的宏列表中删除,然后添加UNICODE和_UNICODE。(你需要两个都定义,因为不同的头文件可能使用不同的宏。) 然而,如果你用char来定义你的字符串,你将会陷入一个尴尬的境地。考虑下面的代码:</P><RE>HWND hwnd = GetSomeWindowHandle();7 F9 r3 S0 n& V
char szNewText[] = "we love Bob!";- d8 w$ {, j' x' r: L3 Z1 [
SetWindowText ( hwnd, szNewText );</PRE>( y: X- S/ o' R+ T" w) z
<>在预处理器把SetWindowText用SetWindowTextW来替换后,代码变成:</P><RE>HWND hwnd = GetSomeWindowHandle(); 7 c. ^) k' M4 a" y. kchar szNewText[] = "we love Bob!";6 s3 T% P+ ?/ U1 S; b
SetWindowTextW ( hwnd, szNewText );</PRE> ' q( {2 W4 o& ]$ ?. @<> 看到问题了吗?我们把单字节字符串传给了一个以Unicode字符串做参数的函数。解决这个问题的第一个方案是使用 #ifdef 来包含字符串变量的定义:</P><RE>HWND hwnd = GetSomeWindowHandle();/ F$ G2 |) `( N1 O
#ifdef UNICODE# g3 P2 H; a5 B9 I* H
wchar_t szNewText[] = L"we love Bob!";+ y' S3 Q5 ^& z
#else ) ^! a/ A3 Y# B; Uchar szNewText[] = "we love Bob!";* V# f2 D' H1 _" Z1 ?, E" {
#endif; b3 n2 N; D! T; A
SetWindowText ( hwnd, szNewText );</PRE> : a8 I% v3 U. B! c* ]$ K( ]<>你可能已经感受到了这样做将会使你多么的头疼。完美的解决方案是使用TCHAR. . x' l8 b, M: G! a: N# c: Q% h! N9 m) a3 j
<IMG src="http://www.vckbase.com/document/image/paragraph.gif"><B> 使用TCHAR</B> g) L, I# R' L 4 D- A0 @) r. c+ j TCHAR是一种字符串类型,它让你在以MBCS和UNNICODE来build程序时可以使用同样的代码,不需要使用繁琐的宏定义来包含你的代码。TCHAR的定义如下:</P><RE>#ifdef UNICODE 0 I% F" Q8 \7 X$ p( _, l( Y8 _" E1 ptypedef wchar_t TCHAR;& j$ I1 D, t* F B- Y
#else7 V8 K! h) ~1 u+ j
typedef char TCHAR; . u( w8 F: ^& N! B+ v. l#endif</PRE> 0 M; T" _' Y2 @0 V5 s<>所以用MBCS来build时,TCHAR是char,使用UNICODE时,TCHAR是wchar_t。还有一个宏来处理定义Unicode字符串常量时所需的L前缀。</P><RE>#ifdef UNICODE 8 e7 \: V2 g7 ?: O& f% o#define _T(x) L##x B0 S: P( H2 c) o2 x
#else) _& W& ^) D+ r5 ~$ b1 ]
#define _T(x) x 8 A5 {% X* Y+ c8 o#endif</PRE> r5 d" q2 w) {
<P> ##是一个预处理操作符,它可以把两个参数连在一起。如果你的代码中需要字符串常量,在它前面加上_T宏。如果你使用Unicode来build,它会在字符串常量前加上L前缀。</P><PRE>TCHAR szNewText[] = _T("we love Bob!");</PRE>0 M$ M8 G: W9 S5 u& o/ e; x R. W2 u
<P> 像是用宏来隐藏SetWindowTextA/W的细节一样,还有很多可以供你使用的宏来实现str***()和_mbs***()等字符串函数。例如,你可以使用_tcsrchr宏来替换strrchr()、_mbsrchr()和wcsrchr()。_tcsrchr根据你预定义的宏是_MBCS还是UNICODE来扩展成正确的函数,就像SetWindowText所作的一样。 . @1 I+ \) j& U" I- l: ~ 不仅str***()函数有TCHAR宏。其他的函数如, _stprintf(代替sprinft()和swprintf()),_tfopen(代替fopen()和_wfopen())。 MSDN中"Generic-Text Routine Mappings."标题下有完整的宏列表。" b( Y/ m( p% D; E X! _, i
/ U7 h$ j/ c/ t0 t2 G' v7 Y
<IMG src="http://www.vckbase.com/document/image/paragraph.gif"><B> 字符串和TCHAR typedefs</B>5 @6 Z1 n7 F+ [/ G
( b: y& A$ Z& k. y) e7 p; o 由于Win32 API文档的函数列表使用函数的常用名字(例如,"SetWindowText"),所有的字符串都是用TCHAR来定义的。(除了XP中引入的只适用于Unicode的API)。下面列出一些常用的typedefs,你可以在msdn中看到他们。</P> 2 x: j+ \; C% ^<TABLE>! c5 I9 }8 l/ L9 Z
7 U" j9 L* l3 f, U, ~2 H4 \9 N<TR>% o$ F2 R: s5 E2 Y
<TD align=middle width="16%"><B>type </B></TD> 1 `% N* p* l: [4 {/ v. A+ |; i7 ^2 q. U6 y<TD align=middle width="42%"><B>Meaning in MBCS builds </B></TD> 2 \5 L% M% M( W' C9 F<TD align=middle width="42%"><B>Meaning in Unicode builds</B></TD></TR>! u8 x* R) Z/ m% V2 h
<TR> 8 }% s( O/ L [* A# m<TD width="16%">WCHAR</TD> 4 }, `( p5 |4 S. B/ Z6 m<TD width="42%">wchar_t</TD> y( K7 e$ x& y' B& V- h<TD width="42%">wchar_t</TD></TR>1 e2 {) }+ }8 Y3 b
<TR> 2 L" v% H+ i5 M: v<TD width="16%">LPSTR </TD>3 ^* @& [, [* |) E# x M$ s5 ]
<TD width="42%">zero-terminated string of char (char*)</TD> 7 F, Z" N5 W/ I" w4 K. R<TD width="42%">zero-terminated string of char (char*)</TD></TR>' c5 s# g5 L. }+ N
<TR> 6 b$ c: X) C; u/ p<TD width="16%">LPCSTR </TD>. W F- b8 K' B$ J; t: r
<TD width="42%">constant zero-terminated string of char (const char*)</TD> ( b4 M5 ]# ^' L3 i<TD width="42%">constant zero-terminated string of char (const char*)</TD></TR>% j! B' b! q8 w( Y4 ~; g% w' O; y
<TR>' n' z. D& f: Y, ?9 F8 W
<TD width="16%">LPWSTR</TD> 5 r- k9 x/ n, I9 W% |& c1 v& I<TD width="42%">zero-terminated Unicode string (wchar_t*) </TD> 4 K- E$ |6 `1 f" \<TD width="42%">zero-terminated Unicode string (wchar_t*)</TD></TR> $ B x4 C1 J2 V# E1 c, Z<TR>: c7 _4 b9 ^, _# l" t2 |3 X3 ?
<TD width="16%">LPCWSTR</TD>* N% C$ R7 _% Z
<TD width="42%">constant zero-terminated Unicode string (const wchar_t*)</TD>1 _* i) O, | q6 Z6 g+ q# R' Y
<TD width="42%">constant zero-terminated Unicode string (const wchar_t*) </TD></TR> * m+ w# X6 ~0 t4 i) J% c7 i<TR> + S- ^1 e" x: Y! W<TD width="16%"><XXXXIME xime="7">TCHAR</XXXXIME></TD> & T$ {1 J' p: t3 M/ W/ \1 U: M* }7 T<TD width="42%"><XXXXIME xime="7">char</XXXXIME></TD>) j6 z# f; C* A/ J* a
<TD width="42%"><XXXXIME xime="7">wchar_t</XXXXIME></TD></TR>6 D/ u" ^5 \& g; Y! D
<TR> 7 [2 H6 N6 J: I" v<TD width="16%">LPTSTR</TD>0 k& C) {/ `9 _( U: V
<TD width="42%">zero-terminated string of TCHAR (TCHAR*) </TD> K: I. @7 e# f+ I7 U) o
<TD width="42%">zero-terminated string of TCHAR (TCHAR*)</TD></TR>4 u. f2 M) h8 O# l; O
<TR>: u! a8 h; ]& F0 D, @
<TD width="16%">LPCTSTR </TD>+ P5 K; n- ?# r: Q' a$ X% U. U$ [
<TD width="42%">constant zero-terminated string of TCHAR (const TCHAR*)</TD> + W, I" h! e# `0 N" C9 n7 {<TD width="42%">constant zero-terminated string of TCHAR (const TCHAR*)</TD></TR></TABLE>- g; u( p/ C% M2 P, e( `& r
<P><IMG src="http://www.vckbase.com/document/image/paragraph.gif"><B> 何时使用 TCHAR 和 Unicode</B>( f7 c& v. u3 g7 u0 n6 n# w
+ b4 ^6 B8 q/ r1 s9 Y9 Z# D4 R
到现在,你可能会问,我们为什么要使用Unicode。我已经用了很多年的char。下列3种情况下,使用Unicode将会使你受益:</P> ; ?6 R" N& P3 m<DIR> 0 B5 f- r, P# g8 B5 L<LI>1.你的程序只运行在Windows NT系统中。 " Q$ N$ g3 _9 q2 _( V<LI>2. 你的程序需要处理超过MAX_PATH个字符长的文件名。 q8 A2 W3 j. {. ^, l/ d4 u1 S
<LI>3. 你的程序需要使用XP中引入的只有Unicode版本的API. </LI></DIR> 5 K @2 Q) n. R8 L" N' @<P> Windows 9x 中大多数的 API 没有实现 Unicode 版本。所以,如果你的程序要在windows 9x中运行,你必须使用MBCS APIs。然而,由于NT系统内部都使用Unicode,所以使用Unicode APIs将会加快你的程序的运行速度。每次,你传递一个字符串调用MBCS API,操作系统会把这个字符串转换成Unicode字符串,然后调用对应的Unicode API。如果一个字符串被返回,操作系统还要把它转变回去。尽管这个转换过程被高度优化了,但它对速度造成的损失是无法避免的。: t/ @6 [: k7 e! |! w. U2 v- W' L
只要你使用Unicode API,NT系统允许使用非常长的文件名(突破了MAX_PATH的限制,MAX_PATH=260)。使用Unicode API的另一个优点是你的程序会自动处理用户输入的各种语言。所以一个用户可以输入英文,中文或者日文,而你不需要额外编写代码去处理它们。4 Z! G& W; ?% G' k7 P. l
最后,随着windows 9x产品的淡出,微软似乎正在抛弃MBCS APIs。例如,包含两个字符串参数的SetWindowTheme() API只有Unicode版本的。使用Unicode来build你的程序将会简化字符串的处理,你不必在MBCS和Unicdoe之间相互转换。 4 a* J2 X; ~( z P) O2 q! t. A 即使你现在不使用Unicode来build你的程序,你也应该使用TCHAR及其相关的宏。这样做不仅可以的代码可以很好地处理DBCS,而且如果将来你想用Unicode来build你的程序,你只需要改变一下预处理器中的设置就可以实现了。. |1 w3 W, | b
9 y& I6 M* D: w% e) ?- F. Z- {+ |
</P> % s& b- J# P5 F8 j& R( K: a ( T) u7 R! |" R- N6 |) Y. Q/ i0 D7 h8 `. ]. X& U
<p>2 Z9 V& t; d! y9 {* \ V" a
<p></TD></TR> # F% `0 P- u/ Z5 H; ]4 n<TR> + \3 f4 {5 v3 s4 O) {4 | b<TD><IMG src="http://www.vckbase.com/document/image/paragraph.gif"><B> 作者简介</B>& h& i, N5 [+ J U8 j4 B
Michael Dunn:居住在阳光城市洛杉矶。他是如此的喜欢这里的天气以致于想一生都住在这里。他在4年级时开始编程,那时用的电脑是Apple //e。1995年,在 UCLA 获得数学学士学位,随后在Symantec 公司做 QA 工程师,在 Norton AntiVirus 组工作。他自学了 Windows 和 MFC 编程。1999-2000年,他设计并实现了 Norton AntiVirus 的新界面。 - s' C8 H) J- R4 a
Michael 现在在 Napster(一个提供在线订阅音乐服务的公司)做开发工作,他还开发了UltraBar,一个IE工具栏插件,它可以使网络搜索更加容易,给了 googlebar 以沉重打击;他还开发了 CodeProject SearchBar;与人共同创建了 Zabersoft 公司,该公司在洛杉矶和丹麦的 Odense 都设有办事处。2 W1 _' U; `# l. _' K
他喜欢玩游戏。爱玩的游戏有 pinball, bike riding,偶尔还玩 PS, Dreamcasth 和 MAME 游戏。他因忘了自己曾经学过的语言:法语、汉语、日语而感到悲哀。</TD></TR></TABLE></P>