在NDK下做網路傳輸時,遇到一個編碼轉換的問題,就是對方傳過來的檔案名稱是中文GBK編碼,需要轉成UTF8來處理。
平常在C/C++環境下編程時,系統都會提供字元編碼轉換的API。如Windows下有MultiByteToWideChar之類的函數,Linux下有iconv庫,純C下用wcstombs、mbstowcs也可以做。然而我在我機上的Android NDK目錄下,並沒有找到iconv庫,直接寫iconv函數無法編譯;而mbstowcs需要的本地庫似乎也沒有,即使能編譯也會運行失敗。這一來,似乎就沒有辦法,只能自己手工寫碼錶庫轉換了。
後來還是想到一個辦法:由於Android下NDK是由Java JNI調用的,而Java本身的字元編碼轉換功能是現成的,直接new一個String,傳入位元組和編碼,就可以擷取指定另一個編碼的字元了。
通過網上查資料實驗,終於編譯運行通過,問題解決。Java這邊調JNI的代碼就不貼了,C程式大概內容及說明如下:
//首先是包含標頭檔,並定義全域變數 #include <string.h>#include <jni.h>#include <pthread.h> //初始JNI虛擬機器環境和線程JavaVM* gJavaVM;JNIEnv* gJniEnv;pthread_t gJvmThread; //Java字串的類和擷取位元組的方法IDjclass gStringClass;jmethodID gmidStringInit;jmethodID gmidStringGetBytes; //初始化JNI環境,此函數由JNI調用jstring Java_com_huz_test_CharsetTest_InitJNIEnv(JNIEnv* env, jobject obj){ (*env)->GetJavaVM(env, &gJavaVM); gJniEnv=env; gJvmThread=pthread_self();//記住當前JNI環境的線程 //擷取Java String類和回調方法ID資訊,由於每次轉換都需要,因此用全域變數記下來,免得浪費時間重複執行 gStringClass= (*env)->FindClass(env,"java/lang/String"); gmidStringGetBytes= (*env)->GetMethodID(env,gStringClass, "getBytes", "(Ljava/lang/String;)[B"); gmidStringInit= (*env)->GetMethodID(env,gStringClass, "<init>", "([BLjava/lang/String;)V"); ... return (*env)->NewStringUTF(env, "OK");} //由Java String轉為指定編碼的charint jstringToPchar(JNIEnv* env, jstring jstr, const char * encoding, char* outbuf, int outlen){ char* rtn = NULL; jstring jencoding; if (encoding==HNULL) jencoding= (*env)->NewStringUTF(env,"utf-8"); else jencoding=(*env)->NewStringUTF(env,encoding); jbyteArray barr= (jbyteArray)(*env)->CallObjectMethod(env,jstr, gmidStringGetBytes, jencoding); jsize alen = (*env)->GetArrayLength(env,barr); jbyte* ba = (*env)->GetByteArrayElements(env,barr, JNI_FALSE); if (alen > 0) { if(outlen==0) return alen; if(outlen<=alen) return -1; rtn=outbuf; memcpy(rtn, ba, alen); rtn[alen] = 0; } (*env)->ReleaseByteArrayElements(env,barr, ba, 0); return alen;} //由指定編碼以零結束的char轉為Java Stringjstring pcharToJstring(JNIEnv* env, const char* pat, const char* encoding){ jstring jencoding; jbyteArray bytes = (*env)->NewByteArray(env,strlen(pat)); (*env)->SetByteArrayRegion(env,bytes, 0, strlen(pat), (jbyte*)pat); if (encoding==HNULL) jencoding= (*env)->NewStringUTF(env,"utf-8"); else jencoding=(*env)->NewStringUTF(env,encoding); return (jstring)(*env)->NewObject(env,gStringClass, gmidStringInit, bytes, jencoding);} //在C代碼中執行字串編碼轉換//參數分別為:原始字元,原始編碼,目標字元緩衝區,目標編碼,目標緩衝區大小,返加轉換結果的長度int changeCharset(char * src_buf, char * src_encoding, char * dst_buf, char * dst_encoding, int dst_size){ JNIEnv *env; jstring jtemp; int res; //由於初始化只執行了一次,本函數與初始JNI調用可能不在同一線程,因此需要判斷當前線程 if(gJvmThread==pthread_self()) { //如果是同一個線程,直接轉 env=gJniEnv; jtemp=pcharToJstring(env, src_buf, src_encoding); res=jstringToPchar(env, jtemp,dst_encoding, dst_buf, dst_size); } else { //如果不是同一個線程,先Attach再轉 env=gJniEnv; (*gJavaVM)->AttachCurrentThread(gJavaVM,&env,NULL); jtemp=pcharToJstring(env, src_buf, src_encoding); res=jstringToPchar(env, jtemp,dst_encoding, dst_buf, dst_size); (*gJavaVM)->DetachCurrentThread(gJavaVM); } return res;}
雖然JNI回調能解決此問題,但用起來很麻煩,速度上估計也很慢,適合少量的資料處理。估計以後Android的NDK新版應該會解決此問題。