一、编译和测试环境

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啥的,看下图:
ubuntu下交叉编译iconv库到arm 32位使用,实现GB2312与UTF-8的转换-LMLPHP
后来查资料解决了该问题,但是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手册的相关文档。
ubuntu下交叉编译iconv库到arm 32位使用,实现GB2312与UTF-8的转换-LMLPHP
ubuntu下交叉编译iconv库到arm 32位使用,实现GB2312与UTF-8的转换-LMLPHP

如果使用动态库,要将动态库文件放到交叉编译器的库下面,同时修改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/libliconvlcharset,包含头文件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失败的解决方法)

10-11 07:26