1. 程式人生 > >編寫ios和android共用的c/c++庫時 使用iconv的問題

編寫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專案中編譯成功後能正常執行並轉碼。