編寫ios和android共用的c/c++庫時 使用iconv的問題
因為在專案中需要同時維護ios和android,不同的程式碼不利於開發的便捷和以後的維護,所以在最近的一個專案中,兩種手機應用的通訊部分打算使用c/c++庫來統一編寫,ios呼叫.a靜態庫,android呼叫.so動態庫的方式來實現。
由於通訊時,從服務端獲取到的中文資料為GBK編碼,android和ios通過c++庫獲取到的中文亂碼,於是打算在c++庫層統一將GBK轉成UTF-8後再傳遞給上層應用。
由於優先考慮跨平臺的方案,最終我採用iconv庫來實現轉碼功能。參考網上搜到的一個程式碼如下
#ifndef STRINGUTIL_H_ #define STRINGUTIL_H_ #include <cstring> #include <iconv.h> #ifdef _WIN32 #pragma comment(lib,"iconv.lib") #endif int code_convert(const char *from_charset,const char *to_charset,const char *inbuf,size_t inlen,char *outbuf,size_t outlen) { iconv_t cd; const char **pin = &inbuf; char **pout = &outbuf; cd = iconv_open(to_charset,from_charset); if (cd==0) return -1; memset(outbuf,0,outlen); iconv(cd, const_cast<char**>(pin), &inlen,pout, &outlen); iconv_close(cd); return 0; } /* UTF-8 to GBK */ int u2g(const char *inbuf, size_t inlen, char *outbuf, size_t outlen) { return code_convert("UTF-8","GBK",inbuf,inlen,outbuf,outlen); } /* GBK to UTF-8 */ int g2u(const char *inbuf, size_t inlen, char *outbuf, size_t outlen) { return code_convert("GBK", "UTF-8", inbuf, inlen, outbuf, outlen); } #endif /* STRINGUTIL_H_ */ </span>
程式碼用g++編譯,在ubuntu上測試正常,但在移植到ios和android均出現問題。
1.首先講ios上出現的問題,這個比較簡單。
使用xcode能夠成功編譯出.a靜態庫,但是在ios應用編譯時,出現如下問題:
Undefined symbols for architecture x86_64:
"_iconv", referenced from:
code_convert(char const*, char const*, char const*, unsigned long, char*, unsigned long) in libVmNet.a(VmNet-EA133239D29A369D.o)
"_iconv_close", referenced from:
code_convert(char const*, char const*, char const*, unsigned long, char*, unsigned long) in libVmNet.a(VmNet-EA133239D29A369D.o)
"_iconv_open", referenced from:
code_convert(char const*, char const*, char const*, unsigned long, char*, unsigned long) in libVmNet.a(VmNet-EA133239D29A369D.o)
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
後來在網上搜到的解決方法,原來需要在專案中新增libiconv.2.4.0.tbd動態庫。然後重新編譯app成功執行。
2.接下來講在android上出現的問題。
在android studio中編譯.so庫,使用的是最新版的2.2.2,預設使用的是cmake編譯。
編譯中,出現找不到iconv.h標頭檔案,網上搜索解決方法,大致有以下幾種方法:
1.專案中新增iconv庫的原始碼,跟專案一起編譯。用到了android.mk,這個又跟現在官方推薦使用的cmake相違背了,我下載了iconv的原始碼,一大堆,不太懂,暫時放棄這條路子。
2.先編譯一個libiconv.so的動態庫,然後編譯自己的庫。這個是用到了android.mk,不想用這個,嫌麻煩,放棄。
3.據說ndk自帶了iconv的支援,只是需要在android.mk中增加
LOCAL_WHOLE_STATIC_LIBRARIES += android_support
$(call import-module,Android/support)
又是android.mk,但我用的是cmake,放棄。
雖然放棄了方法3,但是從中可以知道ndk有自帶的iconv功能,在一個叫android_support的靜態庫中,於是,我找到了iconv.h所在的路徑
/Users/zhourui/Library/Android/sdk/ndk-bundle/sources/android/support/include,libandroid_support.a所在路徑
/Users/zhourui/Library/Android/sdk/ndk-bundle/sources/cxx-stl/llvm-libc++/libs/${ANDROID_ABI}/libandroid_support.a;
於是參考了google安卓官方文件中對cmake引數的解釋,在CMakeLists.txt中添加了以下引數:
# 相當於g++ 中的 -I引數,這個引數讓cmake能找到iconv.h這個標頭檔案
include_directories(/Users/zhourui/Library/Android/sdk/ndk-bundle/sources/android/support/include)
target_link_libraries( # 這是我需要生成的庫檔案VmNet.so VmNet
# Links the target library to the log library # included in the NDK. # 使用android_support.a庫 /Users/zhourui/Library/Android/sdk/ndk-bundle/sources/cxx-stl/llvm-libc++/libs/${ANDROID_ABI}/libandroid_support.a ${log-lib} )
CMakeLists.txt中只需要這麼配置即可。完成了標頭檔案路徑搜尋和靜態庫的連結。
但是直接編譯還是會出錯,會提示
error:unknown type name 'iconv_t'
error:use of undeclared identifier 'iconv_open'到使用到iconv.h的轉碼檔案中檢視,發現能找到iconv.h檔案,但是iconv_t怎麼會未定義呢,於是進入到iconv.h檔案中檢視,發現iconv.h的程式碼如下
#ifndef NDK_ANDROID_SUPPORT_ICONV_H
#define NDK_ANDROID_SUPPORT_ICONV_H
#if !defined(__LP64__)
#ifdef __cplusplus
extern "C" {
#endif
#include <stddef.h>
typedef void* iconv_t;
iconv_t iconv_open(const char*, const char*);
size_t iconv(iconv_t, char**, size_t*, char**, size_t*);
int iconv_close(iconv_t);
#ifdef __cplusplus
} // extern "C"
#endif
#endif // !__LP64__</span>
我發現其中有一段是我用紅色標註的,#if !defined(__LP64__) 這句表示在編譯64位程式時,標頭檔案便是空的了,那麼便表示ndk中的iconv不支援64位。
到app下的build.gradle中檢視有這麼一段:
externalNativeBuild {
cmake { cppFlags "-std=c++11 -fexceptions" abiFilters 'x86', 'x86_64', 'armeabi', 'armeabi-v7a', 'arm64-v8a' } }
這個表示編譯出.so動態庫包含x86_64和arm64-v8a兩種64位庫,那麼將這兩種abi去除即可。
externalNativeBuild {
cmake { cppFlags "-std=c++11 -fexceptions" abiFilters 'x86', 'armeabi', 'armeabi-v7a' // 由於不支援64位,所以只保留32位} }
再次編譯出.so動態庫,使用在app專案中編譯成功後能正常執行並轉碼。