iconv 可能是我遇过的最让人困惑的C标准库函数了——因为它竟然一次性的返回 5 个值,如果算上全局变量 errno 的话,那就是 6 个(如果你知道还有哪个 libc 函数能超过它的,请告诉我)。而作为调用者,必须对返回的 6 个值逐一检查,然后,OMG,可能还需要再调用 iconv 继续转码...如此反复。
所以每当我需要在 C 里面 iconv 一个字符串的话,我会去找 php 4.x 的源代码(现在最新应该是 4.4.9),从 ext/iconv/iconv.c 里面 Copy&Paste 一段代码,再自己简单包装一下使用。
在上周我帮一个程序员提供这个函数包装的时候,我不禁开始思考怎么会有一个如此违背 UNIX 设计哲学的函数存在,后来想到两种可能。一个是字符编码转换这件事情,本来就是一个不那么 UNIX 的操作,再牛的天才,也只能如此设计接口;另一个可能是这个函数完全是 UNIX 商业化过程中为了向非 ANSI 字符语种国家推广,而由一个标准化委员会搞出来的怪物。
如果对 iconv 使用的复杂程度还没有一个感性认识的话,从 PHP 搞来的这段东西会帮助理解这一点:
-
/*
-
--------------------------------------------------------------------
-
The PHP License, version 3.01
-
Copyright (c) 1999 - 2008 The PHP Group. All rights reserved.
-
--------------------------------------------------------------------
-
-
"This product includes PHP software, freely available from
-
<<a href="http://www.php.net/software/>".
-
*/
-
#include">http://www.php.net/software/>".
-
*/
-
#include</a> <stdlib.h>
-
#include <string.h>
-
#include <iconv.h>
-
#include <errno.h>
-
-
//from PHP 4.4.9 ext/iconv/iconv.c
-
//为了便于在 blog 上表现,把返回简化成 3, 2, 1, 0, -1, -2, -3
-
//返回值非零表示转码失败,其中大于零则说明返回的 out 不需要 free
-
//这段代码只在 Linux 下测试
-
int _iconv_string(const char *in_p, size_t in_len, char **out, size_t *out_len, const char *in_charset, const char *out_charset)
-
{
-
iconv_t cd;
-
size_t in_left, out_size, out_left;
-
char *out_p, *out_buf, *tmp_buf;
-
size_t bsz, result = 0;
-
int retval = 0;
-
-
cd = iconv_open(out_charset, in_charset);
-
if (cd == (iconv_t)(-1)) {
-
if (errno == EINVAL) {
-
return 1; // WRONG_CHARSET
-
} else {
-
return 2;
-
}
-
}
-
-
in_left= in_len;
-
out_left = in_len + 32; /* Avoid realloc() most cases */
-
out_size = 0;
-
bsz = out_left;
-
out_buf = (char *)malloc(bsz+1);
-
out_p = out_buf;
-
-
while (in_left > 0) {
-
result = iconv(cd, (char **) &in_p, &in_left, (char **) &out_p, &out_left);
-
out_size = bsz - out_left;
-
if (result == (size_t)(-1)) {
-
if (errno == E2BIG && in_left > 0) {
-
/* converted string is longer than out buffer */
-
bsz += in_len;
-
tmp_buf = (char*)realloc(out_buf, bsz+1);
-
if (tmp_buf != NULL) {
-
out_p = out_buf = tmp_buf;
-
out_p += out_size;
-
out_left = bsz - out_size;
-
continue;
-
}
-
}
-
}
-
break;
-
}
-
if (result != (size_t)(-1)) {
-
/* flush the shift-out sequences */
-
for (;;) {
-
result = iconv(cd, NULL, NULL, (char **) &out_p, &out_left);
-
out_size = bsz - out_left;
-
if (result != (size_t)(-1)) {
-
break;
-
}
-
if (errno == E2BIG) {
-
bsz += 16;
-
tmp_buf = (char *)realloc(out_buf, bsz);
-
if (tmp_buf == NULL) {
-
break;
-
}
-
out_p = out_buf = tmp_buf;
-
out_p += out_size;
-
out_left = bsz - out_size;
-
} else {
-
break;
-
}
-
}
-
}
-
iconv_close(cd);
-
if (result == (size_t)(-1)) {
-
switch (errno) {
-
case EINVAL:
-
retval = -1;
-
break;
-
case EILSEQ:
-
retval = -2;
-
break;
-
case E2BIG:
-
retval = -3;
-
break;
-
default:
-
free(out_buf);
-
return 3;
-
}
-
}
-
*out_p = '\0';
-
*out = out_buf;
-
*out_len = out_size;
-
return retval;
-
}
-
-
//qyb wrap _iconv_string
-
//由调用者负责 free 返回值
-
char *iconv_string(const char *string, const char *in_charset, const char *out_charset)
-
{
-
char *result;
-
int retval;
-
size_t in_len, out_len;
-
in_len = strlen(string);
-
retval = _iconv_string(string, in_len, &result, &out_len, in_charset, out_charset);
-
if (retval > 0) {
-
return NULL;
-
}
-
return result;
-
}
Topic:
技术
评论
GNU iconv
不在标准库里
没记错的话libiconv支持的平台不多……
也可以用mbtowc,wctomb,setlocale代替iconv
现在已经是最新的 POSIX (IEEE Std
现在已经是最新的 POSIX (IEEE Std 1003.1, 2004) 的一部分了