一、编译和测试环境
Host:Ubuntu 16.04.7 LTS 64位
开发板: I.MX6ULL armv7架构 32位
二、 问题描述与解决方案
在6ull开发板上默认使用iconv_open(“utf-8”,“gb2312”)函数时,会返回错误,使用perror函数获取对应错误编号errno代表的错误字符串信息“invalid argument”。经过查询资料得知iconv相关函数为libc中的函数,初步分析得出结论为有可能是libc版本中iconv相关函数的版本不同造成的,因此要更新iconv相关函数。
更新iconv相关函数有两种方法:
第一,更新libc库;
第二,更新libiconv库。
第一种方法更新libc库比较麻烦,因为我们用的是编译好的交叉编译器,这中方法需要重新编译生成交叉编译器,并且也需要使用新编译生成的交叉编译工具重新编译应用程序,因此本方法代价太大,采用第二种方法。
第二种方法为只更新libiconv库,到iconv官网下载最新的库源码包,下载地址为:http://ftp.gnu.org/gnu/libiconv
我下载的是libiconv-1.16版本。
查了挺多资料发现有些人的解决方案是下载的1.14版本,然后编译会生成preloadable_libiconv.so库文件,设置开发板环境变量
$ export LD_PRELOAD=/lib/preloadable_libiconv.so
我这边是放在/etc/profile 文件里面,一开始参照网上配置文件,生成的64位,运行时直接报错wrong ELF class啥的,看下图:
后来查资料解决了该问题,但是iconv_open函数还是报同样的错误,可能是编译生成的32位版本还是不对,希望后面有时间更熟悉这块能回头找到原因吧,暂时没找到原因就下载了另外一个版本。
下载文件后解压压缩包,然后在配置和编译生成需要的库文件:
tar -vxzf libiconv-1.16.tar.gz
./configure --prefix=$PWD/output_lib CC=arm-linux-gnueabihf-gcc --host=arm-linux --enable-shared -enable-static
# --prefix 指定存放生成文件的路径 output_lib --host:指定编译平台
make
makeinstall
保存生成文件的文件夹output_lib下有4个文件夹,lib下面有动态库和静态库文件,include下面是头文件,share下面含有inonc的man手册的相关文档。
如果使用动态库,要将动态库文件放到交叉编译器的库下面,同时修改makefile包含库文件 :
-L
(
p
r
o
p
a
t
h
)
/
i
c
o
n
v
/
l
i
b
−
l
i
c
o
n
v
−
l
c
h
a
r
s
e
t
,包含头文件
−
I
(pro_path)/iconv/lib -liconv -lcharset,包含头文件 -I
(propath)/iconv/lib−liconv−lcharset,包含头文件−I(pro_path)/iconv/include,下面附有我的示例,动态库还需要将库文件放入板子下面,我的是在/usr/lib下,/etc/profile添加了库链接路径/usr/lib;静态库则需要将*.a静态库文件放入项目下面,生成的可执行文件会大很多。
sudo cp -d libiconv.so libiconv.so.2 libiconv.so.2.6.1 libcharset.so.1.0.0 libcharset.so.1 libcharset.so /usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/arm-linux-gnueabihf/lib/
# ssh2使用静态库 ssl crypto iconv charset使用动态库的情况
LIB_ROOT = -lssh2 -lssl -lcrypto -liconv -lcharset
LIBPATH = -L./external_libs/libssh2/lib $(LIB_ROOT)
# iconv charset使用静态库
# LIBPATH += -L./external_libs/iconv/lib $(LIB_ROOT)
实现代码
Uint8 code_convert(char *from_charset, char *to_charset, char *inbuf, size_t inlen, char *outbuf, size_t outlen)
{
iconv_t cd;
char **pin = &inbuf;
char **pout = &outbuf;
cd = iconv_open(to_charset, from_charset);
if ((int)cd == -1)
{
perror("#### iconv_open errno:");
return -1;
}
memset(outbuf, 0, outlen);
if ((int)iconv(cd, pin, &inlen, pout, &outlen) == -1)
{
perror("#### iconv errno:");
iconv_close(cd);
return -1;
}
iconv_close(cd);
return 0;
}
Uint8 GB2312ToUTF8(char *inbuf,size_t inlen,char *outbuf,size_t outlen)
{
return code_convert("GB2312//IGNORE","UTF-8//IGNORE",inbuf,inlen,outbuf,outlen); // IGNORE 可以忽略无效字符
}
需要注意的地方:GB2312存储1个汉字需要两个字节,UTF-8存储汉字需要三个字节,假如有n个汉字,outlen数据长度最少是inlen+n;通过man手册看incnv_open函数,发现返回-1是错误,否则返回正确的文件描述符,按理应该是大于2吧,但是网上部分博客写的是返回0错误然后退出,虽然运行起来没问题,因为返回0也不是正确的文件描述符,但是当真的出现错误时是返回-1,并不会进入if条件中,导致iconv_open函数调用的perror没有打印出正确的错误信息,知道iconv函数才显示错误的文件秒描述符,实际上应该是iconv_open函数直接返回-1,显示invalid argument,此处当时也让我掉坑里,让我一时误解。
参考文献
【原创】64位Linux下交叉编译 iconv到arm 32位使用
linux iconv函数失败,Linux 编码转换 (iconv失败的解决方法)