in

SDT Community Server

SDT Forums, Blogs, Photos server.

Floating Heart

No description is bad.

August 2006 - Posts

  • ILMerge

    ILMerge is a utility that can be used to merge multiple .NET assemblies into a single assembly. ILMerge takes a set of input assemblies and merges them into one target assembly. The first assembly in the list of input assemblies is the primary assembly. When the primary assembly is an executable, then the target assembly is created as an executable with the same entry point as the primary assembly. Also, if the primary assembly has a strong name, and a .snk file is provided, then the target assembly is re-signed with the specified key so that it also has a strong name.

    ILMerge is packaged as a console application. But all of its functionality is also available programmatically. Note that Visual Studio 2005 does allow one to add an executable as a reference, so you can write a C# client that uses ILMerge as a library. If you are using Visual Studio 2003, you must use the v1.1 version of ILMerge and rename it to be a dll in order to use it as a reference.

    There are several options that control the behavior of ILMerge. See the documentation that comes with the tool for details.

    The v2.0 version of ILMerge runs in the v2.0 .NET Runtime, but it is also able to merge v1 or v1.1 assemblies. However it can merge PDB files only for v2 assemblies. The v1.1 version of ILMerge can only process assemblies built in the v1.1 runtime (but does merge PDB files for those assemblies).

    Currently, ILMerge works only on Windows-based platforms. It does not yet support Rotor or Mono. It runs in the v2.0 .NET Runtime, but is also able to merge v1 or v1.1 assemblies.

    If you have any problems using ILMerge please contact mbarnett _at_ microsoft _dot_ com. More details are available at the ILMerge web site.
     
  • Guest帐号和错误Access to the path 'Global\.net clr networking' is denied


    在Windows中用guest运行.net程序,在访问网络的时候会出现错误Access to the path 'Global\.net clr networking' is denied。

    .net clr networking实际上是.net的性能计数器,用来记录所有.net(非asp.net)程序的数据发送和接收情况。

    Access to the path 'Global\.net clr networking' is denied.
       at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
       at System.Threading.Mutex.<>c__DisplayClass3.<.ctor>b__0(Object userData)
       at System.Runtime.CompilerServices.RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup(TryCode code, CleanupCode backoutCode, Object userData)
       at System.Threading.Mutex..ctor(Boolean initiallyOwned, String name, Boolean& createdNew, MutexSecurity mutexSecurity)
       at System.Diagnostics.SharedUtils.EnterMutexWithoutGlobal(String mutexName, Mutex& mutex)
       at System.Diagnostics.SharedPerformanceCounter.Verify(CategoryEntry* currentCategoryPointer)
       at System.Diagnostics.SharedPerformanceCounter.FindCategory(CategoryEntry** returnCategoryPointerReference)
       at System.Diagnostics.SharedPerformanceCounter.GetCounter(String counterName, String instanceName, Boolean enableReuse, PerformanceCounterInstanceLifetime lifetime)
       at System.Diagnostics.SharedPerformanceCounter..ctor(String catName, String counterName, String instanceName, PerformanceCounterInstanceLifetime lifetime)
       at System.Diagnostics.PerformanceCounter.Initialize()
       at System.Diagnostics.PerformanceCounter.set_RawValue(Int64 value)
       at System.Net.NetworkingPerfCounters.Initialize()
       at System.Net.Configuration.SettingsSectionInternal..ctor(SettingsSection section)
       at System.Net.Configuration.SettingsSectionInternal.get_Section()
       at System.Net.HttpWebRequest.get_DefaultMaximumResponseHeadersLength()
       at System.Net.HttpWebRequest..ctor(Uri uri, ServicePoint servicePoint)
       at System.Net.HttpRequestCreator.Create(Uri Uri)
       at System.Net.WebRequest.Create(Uri requestUri, Boolean useUriBase)
       at System.Net.WebRequest.Create(String requestUriString)
       at SimpleControls.Win.SimpleAppUpdater.UpdateConfig.GetConfig() in D:\Work\VS2005\SimpleControls.Win\SimpleControls.Win\SimpleAppUpdater\UpdateConfig.cs:line 148
       at SimpleControls.Win.SimpleAppUpdater.UpdateConfig.GetConfig(String url, String user, String password) in D:\Work\VS2005\SimpleControls.Win\SimpleControls.Win\SimpleAppUpdater\UpdateConfig.cs:line 157
       at SimpleControls.Win.SimpleAppUpdater.UpdateConfig..ctor(String url, String user, String password) in D:\Work\VS2005\SimpleControls.Win\SimpleControls.Win\SimpleAppUpdater\UpdateConfig.cs:line 36
       at SimpleControls.Win.SimpleAppUpdater.Updater.updateThread() in D:\Work\VS2005\SimpleControls.Win\SimpleControls.Win\SimpleAppUpdater\Updater.cs:line 123

    通过StackTrace可以分析实际上是在CreateMutex的时候产生了错误。

    目前的解决方案是创建一个属于Users的账户执行该应用程序。

    Posted Aug 27 2006, 01:35 PM by wicky with no comments
    Filed under:
  • .Net执行带参数的Oracle命令产生的问题

    使用OracleClient,用Command执行带参数的Sql语句时,往往会产生古怪的问题,特别是参数是针对Varchar2类型的字段并且更新Unicode字符串的时候(如中文)。

    如:

    ORA-01461: can bind a LONG value only for insert into a LONG column

    如果根据错误信息,误以为设置DbType可以解决问题的话,可能会发生其他错误,如:

    ORA-12571: TNS:packet writer failure

    甚至进程死掉。

    这个问题由来已久,但是网上找不到很好的解决方案。

    错误可能出现也可能不出现。比如在IDE下启动就OK,在IE里面启动就出错。

    临时的解决方法是对Unicode字符串只有不用参数形式传递。

     

    Posted Aug 25 2006, 05:48 PM by wicky with no comments
    Filed under: ,
  • Oracle关于时间/日期的操作

    在oracle中有很多关于日期的函数,如:

      1、add_months()用于从一个日期值增加或减少一些月份

      date_value:=add_months(date_value,number_of_months)

      例:

      SQL> select add_months(sysdate,12) "Next Year" from dual;

      Next Year

      ----------

      13-11月-04

      SQL> select add_months(sysdate,112) "Last Year" from dual;

      Last Year

      ----------

      13-3月 -13

      SQL>

      2、current_date()返回当前会放时区中的当前日期

      date_value:=current_date

      SQL> column sessiontimezone for a15

      SQL> select sessiontimezone,current_date from dual;

      SESSIONTIMEZONE CURRENT_DA

      --------------- ----------

      +08:00 13-11月-03

      SQL> alter session set time_zone='-11:00'

      2 /

      会话已更改。

      SQL> select sessiontimezone,current_timestamp from dual;

      SESSIONTIMEZONE CURRENT_TIMESTAMP

      --------------- ------------------------------------

      -11:00 12-11月-03 04.59.13.668000 下午 -11:

      00

      SQL>

      3、current_timestamp()以timestamp with time zone数据类型返回当前会放时区中的当前日期

      timestamp_with_time_zone_value:=current_timestamp([timestamp_precision])

      SQL> column sessiontimezone for a15

      SQL> column current_timestamp format a36

      SQL> select sessiontimezone,current_timestamp from dual;

      SESSIONTIMEZONE CURRENT_TIMESTAMP

      --------------- ------------------------------------

      +08:00 13-11月-03 11.56.28.160000 上午 +08:

      00

      SQL> alter session set time_zone='-11:00'

      2 /

      会话已更改。

      SQL> select sessiontimezone,current_timestamp from dual;

      SESSIONTIMEZONE CURRENT_TIMESTAMP

      --------------- ------------------------------------

      -11:00 12-11月-03 04.58.00.243000 下午 -11:

      00

      SQL>

      4、dbtimezone()返回时区

      varchar_value:=dbtimezone

      SQL> select dbtimezone from dual;

      DBTIME

      ------

      -07:00

      SQL>

      5、extract()找出日期或间隔值的字段值

      date_value:=extract(date_field from [datetime_value|interval_value])

      SQL> select extract(month from sysdate) "This Month" from dual;

      This Month

      ----------

      11

      SQL> select extract(year from add_months(sysdate,36)) "3 Years Out" from dual;

      3 Years Out

      -----------

      2006

      SQL>

      6、last_day()返回包含了日期参数的月份的最后一天的日期

      date_value:=last_day(date_value)

      SQL> select last_day(date'2000-02-01') "Leap Yr?" from dual;

      Leap Yr?

      ----------

      29-2月 -00

      SQL> select last_day(sysdate) "Last day of this month" from dual;

      Last day o

      ----------

      30-11月-03

      SQL>

      7、localtimestamp()返回会话中的日期和时间

      timestamp_value:=localtimestamp

      SQL> column localtimestamp format a28

      SQL> select localtimestamp from dual;

      LOCALTIMESTAMP

      ----------------------------

      13-11月-03 12.09.15.433000

      下午

      SQL> select localtimestamp,current_timestamp from dual;

      LOCALTIMESTAMP CURRENT_TIMESTAMP

      ---------------------------- ------------------------------------

      13-11月-03 12.09.31.006000 13-11月-03 12.09.31.006000 下午 +08:

      下午 00

      SQL> alter session set time_zone='-11:00';

      会话已更改。

      SQL> select localtimestamp,to_char(sysdate,'DD-MM-YYYY HH:MI:SS AM') "SYSDATE" from dual;

      LOCALTIMESTAMP SYSDATE

      ---------------------------- ------------------------

      12-11月-03 05.11.31.259000 13-11-2003 12:11:31 下午

      下午

      SQL>

      8、months_between()判断两个日期之间的月份数量

      number_value:=months_between(date_value,date_value)

      SQL> select months_between(sysdate,date'1971-05-18') from dual;

      MONTHS_BETWEEN(SYSDATE,DATE'1971-05-18')

      ----------------------------------------

      389.855143

      SQL> select months_between(sysdate,date'2001-01-01') from dual;

      MONTHS_BETWEEN(SYSDATE,DATE'2001-01-01')

      ----------------------------------------

      34.4035409

      SQL>

      9、next_day()给定一个日期值,返回由第二个参数指出的日子第一次出现在的日期值(应返回相应日子的名称字符串)

      说明:

      单行日期函数

      单行日期函数操作data数据类型,绝大多数都有data数据类型的参数,绝大多数返回的也是data数据类型的值。

      add_months(,)

      返回日期d加上i个月后的结果。i可以使任意整数。如果i是一个小数,那么数据库将隐式的他转换成整数,将会截去小数点后面的部分。

      last_day()

      函数返回包含日期d的月份的最后一天

      months_between(,)

      返回d1和d2之间月的数目,如果d1和d2的日的日期都相同,或者都使该月的最后一天,那么将返回一个整数,否则会返回的结果将包含一个分数。

      new_time(,,)

      d1是一个日期数据类型,当时区tz1中的日期和时间是d时,返回时区tz2中的日期和时间。tz1和tz2时字符串。

      next_day(,)

      返回日期d后由dow给出的条件的第一天,dow使用当前会话中给出的语言指定了一周中的某一天,返回的时间分量与d的时间分量相同。

      select next_day(''01-jan-2000'',''monday'') "1st monday",next_day(''01-nov-2004'',''tuesday'')+7 "2nd tuesday") from dual;1st monday 2nd tuesday03-jan-2000 09-nov-2004

      round([,])

      将日期d按照fmt指定的格式舍入,fmt为字符串。

      syadate

      函数没有参数,返回当前日期和时间。

      trunc([,])

      返回由fmt指定的单位的日期d.

      单行转换函数

      单行转换函数用于操作多数据类型,在数据类型之间进行转换。

      chartorwid()

      c 使一个字符串,函数将c转换为rwid数据类型。

      select test_id from test_case where rowid=chartorwid(''aaaa0saacaaaaliaaa'')

      convert(,[,])

      c尾字符串,dset、sset是两个字符集,函数将字符串c由sset字符集转换为dset字符集,sset的缺省设置为数据库的字符集。

      hextoraw()

      x为16进制的字符串,函数将16进制的x转换为raw数据类型。

      rawtohex()

      x是raw数据类型字符串,函数将raw数据类转换为16进制的数据类型。

      rowidtochar()

      函数将rowid数据类型转换为char数据类型。

      to_char([[,)

      x是一个data或number数据类型,函数将x转换成fmt指定格式的char数据类型,如果x为日期nlsparm=nls_date_language 控制返回的月份和日份所使用的语言。如果x为数字nlsparm=nls_numeric_characters 用来指定小数位和千分位的分隔符,以及货币符号。

      nls_numeric_characters ="dg", nls_currency="string"

      to_date([,[,)

      c表示字符串,fmt表示一种特殊格式的字符串。返回按照fmt格式显示的c,nlsparm表示使用的语言。函数将字符串c转换成date数据类型。

      to_multi_byte()

      c表示一个字符串,函数将c的担子截字符转换成多字节字符。

      to_number([,[,)

      c表示字符串,fmt表示一个特殊格式的字符串,函数返回值按照fmt指定的格式显示。nlsparm表示语言,函数将返回c代表的数字。

      to_single_byte()

      将字符串c中得多字节字符转化成等价的单字节字符。该函数仅当数据库字符集同时包含单字节和多字节字符时才使用。

    Oracle关于时间/日期的操作

      1.日期时间间隔操作

      当前时间减去7分钟的时间

      select sysdate,sysdate - interval '7' MINUTE from dual

      当前时间减去7小时的时间

      select sysdate - interval '7' hour from dual

      当前时间减去7天的时间

      select sysdate - interval '7' day from dual

      当前时间减去7月的时间

      select sysdate,sysdate - interval '7' month from dual

      当前时间减去7年的时间

      select sysdate,sysdate - interval '7' year from dual

      时间间隔乘以一个数字

      select sysdate,sysdate - 8 *interval '2' hour from dual

      2.日期到字符操作

      select sysdate,to_char(sysdate,'yyyy-mm-dd hh24:mi:ss') from dual

      select sysdate,to_char(sysdate,'yyyy-mm-dd hh:mi:ss') from dual

      select sysdate,to_char(sysdate,'yyyy-ddd hh:mi:ss') from dual

      select sysdate,to_char(sysdate,'yyyy-mm iw-d hh:mi:ss') from dual

      参考oracle的相关关文档(ORACLE901DOC/SERVER.901/A90125/SQL_ELEMENTS4.HTM#48515)

      3. 字符到日期操作

      select to_date('2003-10-17 21:15:37','yyyy-mm-dd hh24:mi:ss') from dual

      具体用法和上面的to_char差不多。

      4. trunk/ ROUND函数的使用

      select trunc(sysdate ,'YEAR') from dual

      select trunc(sysdate ) from dual

      select to_char(trunc(sysdate ,'YYYY'),'YYYY') from dual

      5.oracle有毫秒级的数据类型

      --返回当前时间 年月日小时分秒毫秒

      select to_char(current_timestamp(5),'DD-MON-YYYY HH24:MI:SSxFF') from dual;

      --返回当前 时间的秒毫秒,可以指定秒后面的精度(最大=9)

      select to_char(current_timestamp(9),'MI:SSxFF') from dual;

      6.计算程序运行的时间(ms)

      declare

      type rc is ref cursor;

      l_rc rc;

      l_dummy all_objects.object_name%type;

      l_start number default dbms_utility.get_time;

      begin

      for I in 1 .. 1000

      loop

      open l_rc for

      'select object_name from all_objects '||

      'where object_id = ' || i;

      fetch l_rc into l_dummy;

      close l_rc;

      end loop;

      dbms_output.put_line

      ( round( (dbms_utility.get_time-l_start)/100, 2 ) ||

      ' seconds...' );

      end;

  • 谈谈Windows程序中的字符编码

    http://www.hanzify.org/teach/index.php?Go=Show::469-1120147200

    写这篇文章的起因是这么一个问题:我们在使用和安装Windows程序时,有时会看到以“2052”、“1033”这些数字为名的文件夹,这些数字似乎和字符集有关,但它们究竟是什么意思呢?

    研究这个问题的同时,又会遇到其它问题。我们会谈到Windows的内部架构、Win32 API的A/W函数、Locale、ANSI代码页、与字符编码有关的编译参数、MBCS和Unicode程序、资源和乱码等,一起经历这段琐碎细节为主,间或乐趣点缀的旅程。

    0 Where is Win32 API

    Windows程序有用户态和核心态的说法。在32位地址空间中,0x80000000以下属于用户态,0x80000000以上属于核心态。所有硬件管理都在核心态。用户态程序的不能直接使用核心态的任何代码。所谓核心态其实只是CPU的一种保护模式。在x86 CPU上,用户态处于ring 3,核心态处于ring 0。

    从用户态进入核心态的最常用的方法是在寄存器eax填一个功能码,然后执行int 2e。这有点像DOS时代的DOS和BIOS系统调用。在NT架构中这种机制被称作system service。

    在核心态提供system service的有两个家伙:ntoskrnl.exe和win32k.sys。ntoskrnl.exe是Windows的大脑,它的上层被称为Executive,下层被称作Kernel。Win32k.sys提供与显示有关的system service。

    在用户态一侧,有一个重要的角色叫作ntdll.dll,大多数system service都是它调用的。它封装这些system service,然后提供一个API接口。这个接口被称作native API。 native API的用户是各个子系统(subsystem),包括Win32子系统、OS/2子系统、POSIX子系统。各个子系统为Win32、OS2、POSIX程序提供了运行平台。

    ntdll.dll由于提供了平台无关的API接口,所以被看作是NT系统的原生接口,由之得到了“native API”的匪号。其实它的主要工作是将调用传递到核心态。

    Win32、OS/2、POSIX,听起来很庞大。其实真正做好的只有Win32子系统。OS2、POSIX都是Console UI,即只有字符界面。提供OS/2子系统,只因为在1988年,NT的主要设计目标就是与OS/2兼容,后来由于Windows 3.0卖得很好,所以设计目标被变更为与Windows兼容。提供POSIX子系统,是为了应付美国政府的一个编号为FIPS 151-2的标准。

    Win32子系统的管理员是一个叫作csrss.exe的弟兄,它的全名是:Client/Server Run-Time Subsystem。它刚上任时,本来要分管所有的子系统,但后来POSIX和OS/2都被分别处理了,所以只管了一个Win32。即使这样也很了不起,所有的Win32程序的进程、线程们都要向它登记。

    不过Win32程序用得最多的还是Win32子系统的DLL们,最核心的DLL包括:kernel32.dll、User32.dll、Gdi32.dll、Advapi32.dll。这些DLL包装了ntdll.dll的native API。其中Gdi32.dll比较特殊,它与核心态的win32k.sys直接保持联系,以提高NT系统的图形处理能力。Win32子系统的DLL们提供的接口函数在MSDN文档中被详细介绍,它们就是Win32 API。

    附录0 Windows的启动

    计算机上电后,从BIOS的ROM开始运行。BIOS在做一些初始化后会将硬盘的第一个扇区的数据读入内存,然后将控制权交给它,这段数据被称作Master Boot Record(MBR)。

    MBR包含一段启动代码和硬盘的主分区表。这段启动代码扫描主分区表,找到第一个可以启动的分区,然后将这个分区的第一个扇区读入内存并运行。这个扇区被称作引导扇区(boot sector)。

    引导扇区的代码具备读文件系统根目录的能力,显然不同的文件系统需要不同的代码。引导扇区会从根目录中读出一个叫作ntldr的文件。顾名思义,这个文件是load NT的主要角色。它的业绩主要包括将CPU从实模式转入保护模式,启动分页机制,处理boot.ini等。

    如果boot.ini中有一句:

    C:\bootsect.rh="Red Hat Linux"

    bootsect.rh的内容是Linux引导扇区,用户又选择了“Red Hat Linux”,ntldr就会将执行Linux的引导扇区,开始Linux的引导。如果用户选择继续使用Windows,ntldr会装载并运行我们前面提到的ntoskrnl.exe。

    ntoskrnl.exe会启动会话管理器smss.exe。smss.exe启动csrss.exe和winlogon.exe。smss.exe会永远等待csrss.exe和winlogon.exe返回。如果两者之一异常中止,就会导致系统崩溃。所以病毒们经常以打击csrss.exe为乐。

    winlogon.exe负责用户登录,在完成登录后,它会启动注册表HKLM\SOFTWARE\Microsoft\Windows NT\Current Version\Winlogon项下Userinit值指定的程序。该值的缺省数据是userinit.exe。userinit.exe会装载个人设置,让硬盘响个不停,并考验我们的耐性,最后启动注册表同一项下Shell值指定的程序。该值的缺省数据是Explorer.exe。Explorer.exe运行后,我们就会看到熟悉的开始菜单和桌面。

    1 Win32 API的A/W函数

    要了解Win32子系统的DLL们提供了哪些API,最直接的方法就是用Win32dsm直接查看DLL们的导出表。这时我们会发现Win32 API中带字符串的API一般都有两个版本,例如CreateFileA和CreateFileW。当然也有例外,例如GetProcAddress函数。

    A代表ANSI代码页,W是宽字符,即Unicode字符。Windows中的Unicode字符一般指UCS2的UTF16-LE编码。让我们通过几个实例观察A/W版本间的关系。

    例1:用WIn32dsm查看gdi32.dll的汇编代码,可以看到TextOutA调用GdiGetCodePage获取当前代码页,再调用MultiByteToWideChar转换输入的字符串,然后调用一个内部函数。而TextOutW直接调用这个内部函数。

    例2:用调试器跟踪一个使用了CreateFileA的程序,可以看到:CreateFileA在将输入字符串转换为Unicode后,会调用CreateFileW。假设输入文件名是“测试.txt”,对应的数据就是:“B2 E2 CA D4 2E 74 78 74 00”。
    在调试器中可以看到传给CreateFileW的文件名数据是:“4B 6D D5 8B 2E 00 74 00 78 00 74 00 00 00”。 这是"测试.txt"对应的Unicdoe字符串。CreateFileW会接着调用ntdll.dll中的NtCreateFile。顺便看看NtCreateFile的代码:
    mov eax, 00000020
    lea edx, dword ptr [esp+04]
    int 2E
    ret 002C
    可见这个native API只是简单地调用了核心态提供的0x20号system service。

    例3:gdi32.dll中的GetGlyphOutline函数可以获取指定字符的字模。GetGlyphOutlineA和GetGlyphOutlineW函数都会调用同一个内部函数(记作F)。函数F在返回前将通过int 2E调用0x10B1号system service。
    GetGlyphOutlineW直接调用函数F。GetGlyphOutlineA在调用函数F前,要依次调用GdiGetCodePage、IsDBCSLeadByteEx和MultiByteToWideChar,将当前代码页的字符编码转换成Unicode编码。
    如果我们调用GetGlyphOutlineA时传入“baba”,这是“汉”字的GBK编码,用调试器可以看到传给函数F的字符编码是“6c49”,这是“汉”字的Unicode编码。

    从以上例子可见,A版本总会在某处将输入的字符串转换为Unicode字符串,然后和W版本执行相同的代码。在由A/W版本API引出MBCS程序和Unicode程序前,让我们先解释一下Locale和ANSI代码页。

    2 Locale和ANSI代码页

    2.1 Locale和LCID

    Locale是指特定于某个国家或地区的一组设定,包括字符集,数字、货币、时间和日期的格式等。在Windows中,每个Locale可以用一个32位数字表示,记作LCID。在winnt.h中可以看到LCID的组成。它的高16位表示字符的排序方法,一般为0。在它的低16位中,低10位是primary language的ID,高4位指定sublanguage。sublanguage被用来区分同一种语言的不同编码。下面是部分primary language和sublanguage的常数定义:

    #define LANG_CHINESE 0x04
    #define LANG_ENGLISH 0x09
    #define LANG_FRENCH 0x0c
    #define LANG_GERMAN 0x07

    #define SUBLANG_CHINESE_TRADITIONAL 0x01 // Chinese (Taiwan Region)
    #define SUBLANG_CHINESE_SIMPLIFIED 0x02 // Chinese (PR China)
    #define SUBLANG_ENGLISH_US 0x01 // English (USA)
    #define SUBLANG_ENGLISH_UK 0x02 // English (UK)

    好,现在我们可以计算简体中文的LCID了,将sublanguage的常数左移10位,即乘上1024,再加上primary language的常数:2*1024+4=2052,16进制是0804。美国英语是:1*1024+9=1033,16进制是0409。。繁体中文是1*1024+4=1028,16进制是0404。

    2.2 代码页

    每个Locale都联系着很多信息,可以通过GetLocalInfo函数读取。其中最重要的信息就是字符集了,即Locale对应的语言文字的编码。Windows将字符集称作代码页。

    每个Locale可以对应一个ANSI代码页和一个OEM代码页。Win32 API使用ANSI代码页,底层设备使用OEM代码页,两者可以相互映射。

    例如English (US)的ANSI和OEM代码页分别为“1252 (ANSI - Latin I)”和“437 (OEM - United States)”。 Chinese (PRC)的ANSI和OEM代码页都是“936 (ANSI/OEM - Simplified Chinese GBK)”。 Chinese (TW)的ANSI和OEM代码页都是“950 (ANSI/OEM - Traditional Chinese Big5)”。

    附录1中有一张很长的表。列出了我正在使用的Windows所支持的135个Locale的部分信息,包括 LCID、国家/地区名称、语言名称、语言缩写和对应的ANSI代码页。

    2.3 系统Locale、用户Locale,再谈ANSI代码页

    在Windows中,通过控制面板可以为系统和用户分别设置Locale。系统Locale决定代码页,用户Locale决定数字、货币、时间和日期的格式。这不是一个好的设计,后面会谈到它带来的问题。

    使用GetSystemDefaultLCID函数和GetUserDefaultLCID函数分别得到系统和用户的LCID。有很多材料将这两个函数和另外两个函数混淆:GetSystemDefaultUILanguage和GetUserDefaultUILanguage。

    GetSystemDefaultUILanguage和GetUserDefaultUILanguage得到的是您当前使用的Windows版本所带的UI资源的语言。

    用户程序缺省使用的代码页是当前系统Locale的ANSI代码页,可以称作ANSI编码,也就是A版本的Win32 API默认的字符编码。对于一个未指定编码方式的文本文件,Windows会按照ANSI编码解释。

    2.4 AppLocale

    如果一个文本文件采用BIG5编码,系统当前的ANSI代码页是GBK。打开这个文件,就会显示乱码。例如“中文”在BIG5中的编码是A4A4、A4E5,这两个编码在GBK中对应的字符是“いゅ”。这是日文的两个平假名。

    在Windows XP平台有一个AppLocale程序,可以以指定的语言运行非Unicode程序。用Win32dsm打开看一看,其实它只是在运行程序前设置了两个环境变量。我们可以用个批处理文件模仿一下:

    @ECHO OFF
    SET __COMPAT_LAYER=#ApplicationLocale
    SET ApplocaleID=0404
    start notepad.exe

    在简体中文平台,用这个批处理文件启动的记事本可以正确显示BIG5编码的文本文件。用它打开GBK编码的文本文件会怎么样?“中文”会被显示为“笢恅”。设置这两个环境变量会作用于当前进程和其子进程。Windows 2000平台不支持这个方法。

    3 MBCS程序和Unicode程序

    3.1 与字符编码有关的编译参数

    让我们回到Win32 API。我们在程序中使用的Win32 API没有A/W后缀,Windows的头文件会根据编译参数UNICODE将没有后缀的函数名替换为A版本或W版本,例如:

    #ifdef UNICODE
    #define CreateFile CreateFileW
    #else
    #define CreateFile CreateFileA
    #endif

    C RunTime库(CRT)使用_UNICODE和_MBCS来区分三套字符串处理函数,分别用于SBCS、MBCS和Unicdoe字符串。SBCS和MBCS分别指单字节字符串和多字节字符串。例如_tcsclen的3个版本分别为strlen、_mbslen和wcslen ,猜猜以下函数返回几?

    strlen("VOIP网关");
    _mbslen((unsigned char *)"VOIP网关");
    wcslen(L"VOIP网关");

    答案是8、6、6。L"ANSI字符串"通知编译器将ANSI字符串转换为Unicode字符串,这是VC++编译器提供的一个小甜点。不过我们应该用宏:_T("ANSI字符串")。_T宏只在我们定义了_UNICODE时才转换。这样同一套代码既可以编译MBCS版本,也可以编译Unicode版本。

    MFC用_UNICODE参数区分Unicode版本特有的代码,决定使用什么版本的导入库或静态库。

    3.2 Unicode程序、MBCS程序和多语言支持

    Unicode程序直接使用Unicode版本的CRT和Win32 API。Unicode程序的运行与当前的ANSI代码页没有关系。MBCS程序的运行依赖于ANSI代码页。如果设计者和使用者使用不同的代码页,就可能出现乱码。微软开发的程序大都是Unicode程序,不管我们怎样变换系统Locale,它们总能正常运行。

    使用VCL类库的Delphi程序都是MBCS程序。VCL框架在程序启动会调用GetThreadLocale获取当前用户的LCID,然后在当前目录查找对应的资源文件,命名规则是:程序名+.+语言缩写,语言缩写可以参见附录1。在找不到时才会使用EXE文件中的资源。不过如果系统LCID是English(United States),用户LCID是Chinese(PRC),由VCL产生的程序就会出现乱码。读者可以自己分析原因。

    为VCL程序做多语言版本。只要用Delphi自带的Resource DLL Wizard再做一个特定语言的资源DLL,原来的程序都不用改。不过很多程序员用其它组件做多语言版本,例如TsiLang 。

    MBCS程序虽然也可以做成多语言版本,但它无法在同时显示不同代码页特有的字符,这时就必须使用Unicode程序了。

    VS.NET文档中有个多语言资源的例子:SatDLL。它只用Win32 API的例子,却用了VC7项目。我在学习时将它改成了VC6项目,并纠正了它的两个问题:
    1、用GetUserDefaultUILanguage读到的是Windows资源版本,不是当前用户设置的代码页。
    2、启动时没有使用资源DLL里的菜单。

    在我的个人主页(http://fmddlmyy.home4u.china.com)上可以下载修改过的SatDLL。这个程序说明了支持多语言资源的基本思路:将不同语言资源放到不同的DLL中,在程序启动时根据当前Locale装载对应的资源DLL。必要时动态切换资源。为了标记不同语言的资源,可以将它们放到不同的目录中,以LCID作为目录名,例如“2052”、“1033”。当然我们也可以用其它方法联系LCID和资源DLL。

    MFC程序可以在App类的InitInstance函数中用AfxSetResourceHandle函数设置资源DLL。在Delphi中动态切换资源可以参考Delphi Demo目录RichEdit项目的ReInit.pas。在读取当前设定时,建议用GetSystemDefaultLCID函数,因为系统Locale决定ANSI代码页。

    3.4 资源和乱码

    通过检查可执行文件,我们可以确定VC和Delphi的资源编译器都以Unicode保存字符资源。在VC环境编辑资源时,我们会指定资源的代码页。编译器根据资源的代码页,将其转换到Unicode。

    Unicode程序直接使用以Unicode编码保存的资源。MBCS程序需要将Unicode资源先转换回当前ANSI代码页,然后再使用。如果资源中的Unicode字符串不能映射到当前代码页中的字符,就会出现??。

    例如Windows的标准对话框也会出现乱码。假设我们使用简体中文Windows,当前Locale是Chinese (TW),我们的程序是MBCS的,使用标准的打开文件对话框。因为在BIG5中没有“开”这个字,所以“打开”会被显示成“打?”。将程序编译成Unicode版本,就可以避免这个问题。

    如果字符不是保存在资源中,而是硬编码在程序中。然后开发者和用户使用不同的代码页,就会导致乱码。假设开发者的Locale是Chinese (PRC),用户的Locale是English (US),程序中硬编码了字符串“文件”。 Chinese (PRC)的ANSI代码页是GBK,“文件”的编码“CE C4 BC FE”。English (US)的ANSI代码页是Latin I,用户按照Latin I编码去解释“CE C4 BC FE”,就会看到“Îļþ”。

    回答我前面提过的一个问题:Delphi程序根据用户LCID转换资源中的字符串。如果用户LCID是Chinese (PRC),系统LCID是English (US)。那么资源中的Unicode字符串会被转换为GBK编码,然后按照Latin I显示,这时我们看到的就是类似“Îļþ”的东东,不是??。

    既然资源是以Unicode保存的,MBCS程序如果不将其转换到ANSI代码页,而用W版本的函数直接显示,就不会产生乱码。例如MFC程序菜单里的中文,在English (US)的Locale也可以正常显示。不过这取决于各部分代码的具体实现,menu bar控件里的中文在English (US)的Locale会全部显示成??。

    进一步的参考资料

    本文的第0节和附录0主要参考了《Inside Windows 2000 Third Edition》,国内出过该书的影印版。DDK文档中有大量Windows内核的信息。用Win32dsm和各种调试器查看Windows系统文件可以获得更直接的信息。

    关于Window程序的字符编码,最好的参考资料是winnt.h等SDK的包含文件、VCL、MFC、CRT的源文件。我们不需要阅读它们,只要找到自己感兴趣的信息就可以了,用Source Insight可能方便一些。

    本文所谈的不是什么万古不迁的道理,只是别的程序员的一些设定,我们因为需要使用他们的程序,所以有必要了解一些细节。研究问题的方法和兴趣永远比问题本身重要,如一句拉丁俗语所说:res, non verba,实质胜于文字。

    尾声

    “明月虽有圆缺,但毕竟永恒不灭,人生却如过眼烟云,一去不回,真不知计较为何?”

    “蛙声虽是短促,但却是万籁中一个活泼的禅机,也可以说万古如斯,永恒不迁,无奈感受到的,能有几人?”

    这是一本武侠书中的对话。在时间的长河中,人生和蛙声一样易逝。说到蛙声,我的20个月的小宝宝在喝汤后,略加酝酿,就会紧闭着嘴巴,发出很像蛙鸣的声音。我们会逗他说:“小青蛙又来了”。小家伙益发得意,不管我的抗议,将连汤带油的小下巴亲热地贴在我的身上。

     

    附录1 一些关于LCID的信息

    使用EnumSystemLocales函数可以枚举系统支持的LCID。用GetLocaleInfo可以得到ANSI代码页的ID,再通过GetCPInfoEx可以获得代码页的全称。以下是我在中文Windows XP上读到的内容。

    Posted Aug 21 2006, 09:47 PM by wicky with no comments
    Filed under:
  • MSDN Wiki

    http://msdnwiki.microsoft.com/en-us/mtpswiki/default.aspx

    ......On this site you can add content to VS 2005 documentation topics and edit contributions from other users.  Our goal is to extend the documentation with code examples, tips and tricks, and other information that you add.  To learn more about this site, check out our FAQ.  To report bugs, make suggestions, and view information about our future plans, visit the MSDN Wiki Connect Workspace ......

     

  • Cannot getRemoteAddr through WebCache

    1) Set UseWebCacheIp to On

    ($ORACLE_HOME/Apache/Apache/conf/httpd.conf)
    # Alternate "common" format to use when fronted by webcache:
    #
    # LogFormat "%{ClientIP}i %l %u %t \"%r\" %>s %b %h" common_webcache
    #
    # When webcache is forwarding requests to OHS, %h becomes the IP of
    # the originating webcache server and the real client IP is stored
    # in the ClientIP header. The common_webcache format can be used
    # in place of the common format when using webcache but with one
    # important caveat: if clients are capable of bypassing webcache
    # then it is possible to spoof the client IP by manually setting
    # the ClientIP header so the %h field should be monitored in such
    # an environment. Another alternative to specifying the ClientIP
    # header directly in a LogFormat is to use the "UseWebCacheIp"
    # directive:
    #
    UseWebCacheIp On
    #
    # When this is specified, %h is derived internally from the ClientIP
    # header and the access log format does not need to be modified.
    #

    UseWebCacheIp is a global directive that enables Oracle HTTP Server to obtain IP address of a client. It can be set to "On" or "Off", and defaults to "Off". It is not set to "On" by default because it can open a security hole in some circumstances.

    When OracleAS Web Cache acts as a reverse proxy in front of Oracle HTTP Server, the TCP connection from the client is terminated at OracleAS Web Cache. The TCP connection that Oracle HTTP Server sees actually originates at OracleAS Web Cache. Oracle HTTP Server gets the IP address of the client and uses it for various purposes, such as:

    • Populating the REMOTE_ADDR CGI variable that can be used by applications in and behind Oracle HTTP Server to identify where the client came from.

    • Evaluating mod_access allow/deny rules that allow the administrator to restrict access based on IP address.

    Without the UseWebCacheIp directive, this functionality fails when OracleAS Web Cache is used in front of Oracle HTTP Server. This is because Oracle HTTP Server sees all connections coming from the same place - the IP address where OracleAS Web Cache is running.

    With every request that OracleAS Web Cache forwards to Oracle HTTP Server, it sends a header that contains the IP address of the client connection that it received. If UseWebCacheIp is set to "On", then it directs Oracle HTTP Server to use the IP value from this header, instead of the value from the TCP connection as the client's IP address. This enables REMOTE_ADDR CGI variable to have the correct value, and allows mod_access to function correctly.

    You should set this directive only if you are sure that the clients can only connect to Oracle HTTP Server through OracleAS Web Cache. If clients can connect directly to Oracle HTTP Server, then they have to find out the header that is used to transfer the client IP, and set it so that it would seem to have come from any IP address you want. In a typical set up, with a firewall and OracleAS Web Cache, the only port open through the firewall is the OracleAS Web Cache port. Hence, the only path from the client to Oracle HTTP Server goes through OracleAS Web Cache. In this case, it is safe to turn on UseWebCacheIp.

    2) Check CLIENTIP header

    String clientIP = request.getHeader("CLIENTIP");
    if (clientIP == null || clientIP.length() == 0)
    clientIP = request.getRemoteAddr();

    3) use servlet filter

    It is true that the client ip is substituted with the Web Cache ip, but the Web Cache stores the original ip-address in the header request as "CLIENTIP".

    If you want your application to fetch the correct ip in request.getRemoteAddr(), you could write a servlet filter that wraps the HttpServletRequest with a new class that returns the correct ip.

    Example:

    public class IpChangeFilter implements Filter {

    [..]

    public void doFilter( ServletRequest pRequest, ServletResponse pResponse, FilterChain pChain) throws IOException, ServletException {

    if( pRequest instanceof HttpServletRequest)
    pChain.doFilter( new IpChangeRequest( (HttpServletRequest)pRequest), pResponse);
    else
    pChain.doFilter( pRequest, pResponse);
    }
    }

    And the wrapper:

    private class IpChangeRequest extends HttpServletRequestWrapper {

    [..]

    public String getRemoteAddr() {

    String tNewIp = getHeader( "CLIENTIP");
    if( tNewIp==null || tNewIp.length()==0)
    tNewIp = super.getRemoteAddr();

    return tNewIp;
    }

    public int getServerPort() {
    if( super.getServerPort()==7778)
    return 80;
    else
    return super.getServerPort();
    }
    }

  • Content-Disposition -> attachment 在IE 6.0.2800 会出现2次下载提问窗口?

    应该是IE的bug,只在使用POST方法的时候出现,改用GET方法就只提示一次了。

     

  • VSS Add-in Missing after Installation

    At first, remove and install VSS again to try.

    Then,

    For VS2005:

    regsvr32 ssscc.dll
    check: HKEY_LOCAL_MACHINE\SOFTWARE\SourceCodeControlProvider
    check: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\SourceSafe

    For VB6:

    check: vbaddin.ini, add vbscc=3.

     

    Posted Aug 09 2006, 12:09 PM by wicky with no comments
    Filed under:
  • Atlas July CTP and the Latest Atlas Control Toolkit Released

    Atlas 越来越像样了。也许差不多应该touch一下啦?.....

    http://weblogs.asp.net/scottgu/archive/2006/08/04/Atlas-July-CTP-and-the-Latest-Atlas-Control-Toolkit-.aspx

     

  • DevExpress eXpressApp Framework

    http://www.devexpress.com/Products/NET/Libraries/eXpressApp/Index.xml

    http://www.devexpress.com/Products/NET/Libraries/eXpressApp/ctpmaindemo.xml

    60分钟写一个Outlook! 而且还是WinForm和Web两个版本!

    只需要创建基本类和设置属性,支持报表设计,工作流,无须理会数据库。

    这样搞下去还能活吗?

     

  • AssemblyName.GetAssemblyName

    这个方法可以获得打开某个Assembly文件,获得AssemblyName,随即释放。不会加载到当前的AppDomain。

     

    Posted Aug 02 2006, 01:48 PM by wicky with no comments
    Filed under: ,
Copyright SDT, 2006-2009. All rights reserved.