Chinaunix首页 | 论坛 | 博客
  • 博客访问: 9463634
  • 博文数量: 1750
  • 博客积分: 12961
  • 博客等级: 上将
  • 技术积分: 20091
  • 用 户 组: 普通用户
  • 注册时间: 2009-01-09 11:25
个人简介

偷得浮生半桶水(半日闲), 好记性不如抄下来(烂笔头). 信息爆炸的时代, 学习是一项持续的工作.

文章分类

全部博文(1750)

文章存档

2024年(26)

2023年(26)

2022年(112)

2021年(217)

2020年(157)

2019年(192)

2018年(81)

2017年(78)

2016年(70)

2015年(52)

2014年(40)

2013年(51)

2012年(85)

2011年(45)

2010年(231)

2009年(287)

分类: Android平台

2013-02-03 15:24:54

字集编码系统的问题真的是一门相当深刻的学问,我们已花了相当多的篇幅,从最开始的 LC_CTYPE 讲起,经过 mbs 与 wcs 的互转,一路深入到 iconv,现在已经要开始谈整个系统最核心的部分了,而这部分将可能牵涉到更多程序设计的细节,恐怕读者们再读下去要受不了了。故在这一小节中我们将不 再触及程序设计的部分,而改以较宏观的角度来看整个系统,并回顾之前我们之前谈过的内容,做一个总结。

我们先来看 glibc iconv 的心脏部分,它是藏在系统的 /usr/lib/gconv 目录中,称之为 gconv-module。各位读者可以去看看该目录的内容,会见到里头有一大堆 .so 文件,这些文件都是系统所支持的所有字集的表格,有些可能是某字集 A 与某字集 B 的对应表,但大部分是该字集与系统的基底字集 (也就是 UCS4) 的对应表。而所有 iconv 以及 wcs 与 mbs 互转函式,就是根据这些表格来工作的。

由于世界上有在使用的字集编码相当多,故它们不可能全部写在 glibc 里头并静态连接起来,那会造成程序执行时消耗太多内存,而且很不实际。故它们全部是以 ``模块档案'' 的形式存在,静静躺在硬盘中,当系统或程序执行有需要时,才由 glibc 动态加载记忆中与程序连结。事实上在其它的 UNIX 系统中也是采用这种方式,只要该 UNIX 系统有支持 shared lib 与模块动态加载的话。

在这个目录中,有一个特别的档案 -- gconv-modules,各位已在前面介绍的,如何安装 zh_CN.GB2312 那部分中见过它了。它的内容就是此目录中所有表格的索引目录,包括每个表格可以做什么样的编码系统转换、转换时需要经过几个步骤、还有各编码系统可适用的 别名 ...等等。而未来这个表格所表达的内容还会再扩充。如果我们希望再加一个 Big5 与 GB2312 的转换表格的话,方式很简单,就是先按照一定的规格将转换表格写好,编译成 .so 文件之后摆在这个目录,然后再编辑一下 gconv-modules 的内容加入新表格的索引,就没问题了。至于表格的规格以及 gconv-modules 的格式,我们在这里不深究,有兴趣的读者请参考 info libc,看 * glibc iconv Implementation:: 一节。

现在可以回到我们以前所见过的 ``吊诡'' 问题了。我们已经见到整个 glibc 的 I18N 架构中,有两份各字集编码与系统基底字集的对应表,一个就在 gconv 里头,它专门负责不同字集编码之间的转换工作。另一个是在编译 locale 时藏在 repertoire map 里头,它只负责 locale 的 LC_CTYPE 资料中,每个字的 ``符号'' 与基底字集间的对应,有了这样的对应,才能让 isw*() 与 tow*() 函式可以正确工作。

说得更具体一点,回头看看我们前面写过的宽字符分类处理范例程序。这个程序一开始时,使用 mbsrtowcs() 函式将所有读入的 mbs 转成 wcs,这里用到的表格是 gconv 里头的,而它到底用那一个表格,则由目前 locale LC_CTYPE 的设定来决定,等决定好后,再呼叫 iconv,使用 gconv 来做字集转换。然后转出来的 wcs 字要交给 isw*() 函式来分类时,所参考的却是 repertoire map 里头的表格。如果 repertoire map 实作不完全,就算已有完整个 gconv 表格,isw*() 函式还是无法动。而且,反过来也是一样。

因此,做类似的事情,却需要参考两个不同来源的表格,这就是吊诡之处了。事实上 repertoire map 是 glibc 独有的设计,目的是希望将 ``字集'' 与 ``字集编码方式'' 分开,这在其它 UNIX 系统下是见不到的。而根据 glibc 的作者 Ulrich Drepper 的说法,目前的目标是先将 repertoire map 发展完整,让整个 cwtype.h 字符分类系统可以动起来,未来也许会将 repertoire map 与 gconv 合并,也许就只留下 gconv 而已,而 repertoire map 就取消了。


简单说, mb/wb函数从当前locale中找到charset的名字, 调用iconv函数,
iconv先读进/usr/lib/gconv/gconv-modules, 根据charset的名字找到
应该用的module (.so), 然后装上该module, 由其作真正的转换.


但不论未来的发展如何,我们先前所谈的所有 ISO C89 与 XPG2 标准都不会变的,故我们在发展相关程序时,只要谨守这些标准,不论走到那都可以适用,甚至移植到其它的 UNIX 系统也是一样,当然前提是它们要有提供这些标准的支持。而这里可能变量较大的是 iconv 的部分,因为不同的系统间所能提供的支持程度可能会有相当大的差异。而我们的建议是,由于 UCS2 (Unicode)、UCS4、UTF8 等编码系统已经渐渐被大部分的 UNIX 系统所支持,严然成为标准的一部分,故我们应该可以很放心地使用 iconv 接口做某字集与 UCS2 等编码系统的转换,而且在某些情况下使用 iconv 也是必须的。但如果要做两个交集呈度不高的编码系统互转 (如 Big5 与 GB2312 之间) 时,也许还是稍微保守一些,由我们自己在程序里做转换表格会较为适当。


1. wchar_t 实际上是用 4 个 bytes 来储存一个 UCS-4 编码的字符设计的(glibc 里面)。
实际 ISO C 标准并没有强制要求这样(允许定义成 char 保持和 char 的一致)。于是 glibc
里面的 wchar_t 可以表示出 ISO 10646 标准下所有字符。实际 locale 里面多见的是 utf-8,
这是一种兼容 ASCII 的变长编码标准(非 ASCII 字符使用 2-6 个 bytes)完全编码了 ISO 10646 下
所有字符。今后,将仅仅鼓励使用 utf-8 标准进行外部编码。似乎这样看来,一个程序使用
utf-8 编码就能解决所有的语言问题了... 但是,这不是真的。

2. 注意到很多 char array 的函数返回 int,为了保持一致 wchar_t array 返回 wint_t。wchar_t 的范围
是 WCHAR_MIN 和 WCHAR_MAX 两个 macro 声明的。WEOF 是 wchar_t 的 eof 版本。
MB_LEN_MAX 表示所有 locale 里面 multi-byte 字符最多需要编码字节,而 MB_CUR_LEN_MAX 是
 当前的 locale 下最长的,前者是一个常数,后者是一个运行时决定的数字(写程序时候必须注意)。
这些声明一部分在 limits.h 一部分在 stdlib.h。

有些字符集包含了一类带有状态编码的字符,如很多 Latin 语言系带有的 accent,通常用一个
编码表示一种 accent,后面一个字符表示需要添加 accent 的字符(因此如果需要输出 accent 本身,
后面还得加另外一个字符,比如空格)。这种字符就是表示进入到某种状态,常用 mbstate_t 类型描述,
一些函数需要利用这种信息(wchar.h)。可用 mbsinit() 测试是否处于读入新字符状态。

wint_t btowc( int c )
将常规 char 转换成为 wchar_t,转换规则由 LC_CTYPE 决定。失败返回 WEOF。反过来是 wctob()。
size_t mbrtowc (wchar_t *restrict pwc, const char *restrict s, size_t n, mbstate_t *restrict ps)
将 multi-byte 字符转换成 wchar_t,其中就要用到 mbstate_t 了。反过来是 wcrtomb()。
size_t mbrlen (const char *restrict s, size_t n, mbstate_t *ps)
求 multi-byte 字符串长。字符串的转换使用
size_t mbsrtowcs (wchar_t *restrict dst, const char **restrict src, size_t len, mbstate_t *restrict ps)
反过来是 wcsrtombs()。这个函数也有 +n 版本,就是 mbsnrtowcs() 和 wcsnrtombs()。

3. non-reentrant 版本的函数,前面的函数去掉 r 就是对应的 non-reentrant 版本的函数,
可以去掉对局部 mbstate_t 的依赖。

4. 我们不可能仅仅做 locale 到 wchar_t 的转换,而通过改变 LC_CTYPE 从而实现转换会引起一些麻烦
因此我们有必要使用 iconv() 函数进行转换(对应还有 iconv 的命令)。为了使用 iconv,引入 iconv_t 类型,
首先用
iconv_t iconv_open (const char *tocode, const char *fromcode)
建立一个转换,再使用
size_t iconv (iconv_t cd, char **inbuf, size_t *inbytesleft, char **outbuf, size_t *outbytesleft)
转换,最后
int iconv_close (iconv_t cd)
关闭掉。注意不同线程之间不能共享这个 iconv_t 的 descriptor。这些在 iconv.h 中给出了定义。

5. gconv 配置文件,用于描述使用 iconv 的方法,因为默认情况下 iconv 是依赖于 UCS-4 作为中间桥梁
但是很多编码之间有快速转换方法的,这些通过 module 实现。如何写 iconv 的 module 可以看 gconv.h,
这里不看细节了,偶对编码转换的实现不大感兴趣,有兴趣的可以看这节的内容。

6. locale category 也就是 LC_ 这里的 LC 了,有以下类型 LC_COLLATE(用于校正),
LC_CTYPE(用于字符串编码),LC_MONETARY(用于管理货币输出),LC_NUMERIC(非货币数值),
LC_TIME(时间输出),LC_MESSAGES(程序中使用的信息),LC_ALL 会将所有前面的类型覆盖掉,
主要方便偷懒... LANG 如果前面的没有设定就用其值覆盖掉,LANGUAGE 可以设置多个 locale,依次尝试之。
设置 locale 的基本函数为
char * setlocale (int category, const char *locale)
第一个参数可以用定义过的宏,如 LC_ALL 等等,后面的试设置的类型,用一个字符串表示,
如果为 NULL 则返回当前的类型,但是注意不应该把指针留下(如果后面的程序要修改当前的 locale)
最好 strdup 之。ISO C 仅仅定义了 C 和 POSIX 两种 locale,而且一个程序进入后处于的 locale 是 C。

7. 使用 locale 可以用 ISO C 标准下的 localeconv(),但是建议只在很苛刻的 portability 下才使用,具体原因么
看看这里。该函数声明如下
struct lconv * localeconv (void)
它返回了一个 struct lconv,我们不能对此进行修改。但是可以利用该结构里面的一些信息来格式化我们需要
输出的字符串,如日期,数字等等。该结构体较为复杂,这里不举例其各项成员。我们看看 X/Open 标准下的
实现方式,主要相关的函数(langinfo.h)为
char * nl_langinfo (nl_item item)
与前者不同,这里主要利用 nl_item 返回对应项的内容,我们知道 strftime() 函数可以输出格式化的
日期时间信息,其声明如下
size_t strftime (char *s, size_t max, const char *format, const struct tm *tm)
我们可以如下利用该函数
strftime (s, len, nl_langinfo (D_T_FMT), tp);
这样把不同 locale 下的输出搞定,下面的程序为我们展示了不同 locale 下时间格式输出不同的可能:
#include 
#include 
#include 
#include 

void showTime( const char *locale, struct tm *t ) ;

int
main( int argc, char *argv[] )
{
  time_t t = time( NULL ) ;
  struct tm *curtime = localtime( &t ) ;

  showTime( "C", curtime ) ;
  showTime( "en_US", curtime ) ;
  showTime( "en_GB", curtime ) ;
  showTime( "zh_CN.utf-8", curtime ) ;

  return 0 ;
}

void
showTime( const char *locale, struct tm *t )
{
  const int len = 100 ;
  char buff[ len ] ;

  // Show current locale
  setlocale( LC_ALL, locale ) ;
  printf( "Current locale is %s, here is strftime() result:\n",
          setlocale( LC_ALL, NULL ) ) ;
  strftime( buff, len, nl_langinfo( D_T_FMT ), t ) ;
  puts( buff ) ;
}
程序输出如下:
Current locale is C, here is strftime() result:
Sun Nov 19 21:55:54 2006
Current locale is en_US, here is strftime() result:
Sun 19 Nov 2006 09:55:54 PM CST
Current locale is en_GB, here is strftime() result:
Sun 19 Nov 2006 21:55:54 CST
Current locale is zh_CN.utf-8, here is strftime() result:
2006年11月19日 星期日 21时55分54秒
没想到中文也可以正常输出,太赞了!类似于 strftime(),另有一个函数用于格式化数值和货币
ssize_t strfmon (char *s, size_t maxsize, const char *format, ...)
其使用和 sprintf() 函数类似,其中对数值输出的格式说明类型更多,如 %n %i 表示货币,n = national
而 i = international,比如美元会产生 $ 或者 USD 的说明,具体看文档吧,so complicated,这里再做
一个简单的例子说明输出结果什么样子。这是程序:
#include 
#include 
#include 
#include 

void show_number( const char *locale ) ;

int
main( int argc, char *argv[] )
{
  show_number( "C" ) ;
  show_number( "en_US" ) ;
  show_number( "en_GB" ) ;
  show_number( "zh_CN.utf-8" ) ;

  return 0 ;
}

void
show_number( const char *locale )
{
  const int len = 300 ;
  char buff[ len ] ;

  // Show current locale
  setlocale( LC_ALL, locale ) ;
  printf( "Current locale is %s, here is strfmon() result:\n",
          setlocale( LC_ALL, NULL ) ) ;
  strfmon( buff, len, "%=*+20#5n%=*+20#5n\n%=*+20#5i%=*+20#5i",
           123.45, -567.89, 12345678.9, -0.123456 ) ;
  puts( buff ) ;
}
程序输出如下
Current locale is C, here is strfmon() result:
            **123.45           -**567.89
         12345678.90           -****0.12
Current locale is en_US, here is strfmon() result:
          $***123.45         -$***567.89
   USD 12,345,678.90      -USD *****0.12
Current locale is zh_CN.utf-8, here is strfmon() result:
        ¥***123.45       ¥-***567.89
    CNY12,345,678.90       CNY-*****0.12
英文的(en_GB 删掉了,里面有非法字符 -.-b)

8. 回答 yes or no,居然还有这种东西(是 glibc 的扩展,使用到了 LC_MESSAGE 的信息),函数是
int rpmatch (const char *response)
返回 1 表示是,0 表示不是,-1 表示两个都不是 -.-b 可以简单的验证一下该东东对不对
由于还没学过 gettext,程序输出就只用英文了,表怪我,这是程序
#include 
#include 
#include 

void is_a_duck( const char *locale ) ;

int
main( int argc, char *argv[] )
{
  is_a_duck( "C" ) ;
  is_a_duck( "en_US" ) ;
  is_a_duck( "zh_CN.utf-8" ) ;

  return 0 ;
}

void
is_a_duck( const char *locale )
{
  char *line ;
  size_t len ;

  // Show current locale
  setlocale( LC_ALL, locale ) ;
  line = NULL ;
  len = 0 ;
  printf( "Current locale is %s. Are you a duck? ",
          setlocale( LC_ALL, NULL ) ) ;
  while( getline( &line, &len, stdin ) >= 0 ) {
    /* Check the response.  */
    int res = rpmatch( line ) ;
    if (res >= 0) {
      /* We got a definitive answer.  */
      if( res > 0 )
        puts( "Yeah, you are a duck!" ) ;
      else
        puts( "Then why you resembles a duck so much?!" ) ;
      break;
    }
  }
  /* Free what getline allocated.  */
  free( line ) ;
}
这是交互过程:
Current locale is C. Are you a duck? hhh
no
Then why you resembles a duck so much?!
Current locale is en_US. Are you a duck? yes
Yeah, you are a duck!
Current locale is zh_CN.utf-8. Are you a duck? 你管我是不是
是
Yeah, you are a duck!
汗,有段程序从 libc 的 manual 里面 copy 出来改了改...

阅读(4561) | 评论(0) | 转发(0) |
0

上一篇:emdebian

下一篇:android linux kernel 映射关系

给主人留下些什么吧!~~