利用Ubuntu將c++檔案生成.so庫
記錄生成.so庫的步驟,以防自己每次忘記。用的是eclipse。
一、先在eclipse中生成一個android工程,然後在android工程下的src新建一個package,例如名字為com.android.aa,在其下新建一個java檔案,這個檔案相當於與.so庫的一個對接介面,其形式為:
package com.android.aa;
public class NativeAA{
static{
System.loadLibrary("aa"); //載入aa.so庫
}
public native byte[] sent(String input); //呼叫so庫中的sent函式
}
儲存一下,就可以在./bin/classes/com/android/aa檔案下生成NativeAA.class檔案。
二、利用javah生成jni的標頭檔案
首先利用cmd進入目錄中:例如工程放在了F盤,工程為test:
f: //這是進入了f盤
cd ./test/bin/classes目錄下,執行下一命令:
javah com.android.aa.NativeAA
就會在classes生成了一個com_android_aa_NativeAA.h的標頭檔案
裡面就是一個函式的宣告:
JNIEXPORT jbyteArray JNICALL Java_com_android_aa_NativeAA_sent
(JNIEnv*,jobject , jstring);(其他的省略,並且java中有幾個函式宣告,.h就對應生成幾個,我這裡是一個)
三、生成與.h對應的.cpp檔案
在Ubuntu中建立一個資料夾,例如:我在project的檔案下建立了一個aa的檔案下,然後建立一個jni檔案下,將com_android_aa_NativeAA.h方進去,再新建一個對應的.cpp
,在命令視窗輸入gedit com_android_aa_NativeAA.cpp,就建立以個.cpp檔案。再建立實現sent函式的.cpp和.h這裡命名為sent.cpp和sent.h。
sent.cpp的中的函式sent實現的是輸入為char*,輸出的引數為char*型別,其宣告為:
int sent(char* sentInput,char **sentInput);
char*sentInput是輸入,char**sentInput是輸出(實際需要的是char*,返回二級指標下面會說明),返回int是標誌位,這個程式執行下來返回0表示成功;其他值表示失敗。
com_android_aa_NativeAA.cpp的形式:
#include "com_android_aa_NativeAA.h"
#define TAG "so-jni"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG,__VA_ARGS__) //
//功能:實現將jstring轉換為char*
char*jstringTostring(JNIEnv*env,jstring jstr);
JNIEXPORT jbyteArray JNICALL Java_com_android_aa_NativeAA_sent
(JNIEnv*env,jobject object, jstring jinput) //jstring jinput對應的是java檔案中的String input
{
//首先將jstring轉成char型別,因為我的例程中sent.cpp中的函式的輸入引數是char*;
char *inputChar=jstringTostring(env,jinput);
if(inputChar==NULL)
{
LOGD("jstring轉換char失敗!")
return NULL;
}
else
{
char *outData=new char[255];
memset(outData,0,255);
if(outData==NULL)
return NULL;
int ret=sentInput(InputChar,&outData); //outData是返回值,&outData這樣做是返回一組陣列。
if(ret==0)
{
//這是將c++中的char*轉為為java中的byte[];
jbyte *joutData=(jbyte*)outData;
jbyteArray jarrOutData=env->NewByteArray(255);
env->SetByteArrayRegion(jarrOutData,0,255,joutData);
delete[ ] outData;
return jarrOutData;
}
else
{
delete[] outData;
return NULL;
}
}
delete[ ] inputData;
}
//將Java中的String轉換為c++中的char*
char*jstringTostring(JNIEnv* env,jstring jstr)
{
char*ttn=NULL;
jclass clsstring=env->env->FindClass("java/lang/string");
jstring strInput=env->NewStringUTF("utf-8");
jmethodID mid=env->GetMethodID(clsstring,"getBytes","(Ljava/lang/String;)[B");
jbyteArray barr=(jbyteArray)env->CallObjectMethod(jstr,mid,strencode);
jsize alen=env->GetArrayLength(barr);
jbyte*ba=env->GetByteArrayElements(barr,JNI_FALSE);
if(alen>0)
{
rtn=(char*)malloc(alen+1);
memcpy(rtn,ba,alen);
rtn[alen]=0;
}
env->ReleaseByteArrayElements(barr,ba,0);
return rtn;
}
四、兩個makefile檔案(1)Application.mk:用於描述APP需要的native model
其內容為:
APP_PLATFORM :=android-19
APP_STL :=gnustl_static #要使用到的標準c++庫
APP_CPPFLAGS :=-frtti -fexceptions
APP_ABI :=armeabin
分析:
Application.mk常用的有四個:
a. APP_PLATFORM :=android-19
表示使用ndk庫函式版本號。一般和SDK的版本相對應,各個版本在NDK目錄下的platforms資料夾中
b. APP_STL :=stlport_static
定義NDK的編譯系統的執行時庫,有如下幾種:
system:預設最小的c++執行庫,生成的應用體積小,記憶體佔用少,但部分功能將無法支援
stIport_static:使用STLport作為靜態庫(推薦使用)
stlport_shared:STLport作為動態庫(不推薦,可能產生相容性和部分低版本的Android韌體)
gnustl_static:使用GNU libstdc++作為靜態庫
c. APP_ABI:=armeabi
表示要編譯成對應指令集cpu的動態庫,armeabi:ARMv7指令集,armeabi-v7a:ARMv7+硬體FPU指令集,x86:IA-32指令集,all支援常用的指令集
d. APP_CPPFLAGS:該變數列出了一些編譯器標誌,在便以任何模組的c++程式碼時這些標誌都會被傳給編譯器;
e. APP_OPTIM:=debug(我自己的專案沒用這一項)
設定編譯型別,debug:除錯版本,so動態庫中帶除錯資訊(可以使用gdb-server盡心動態段斷點低除錯),release:釋出版本,so庫不帶除錯資訊
(2)Android.mk:用於描述構建系統的原始檔以及shareed libraries。
其內容:
LOCAL_PATH :=&(call my-dir)
APP_STL :=gnustl_static #要用到標準c++庫,如果不用可以註釋掉
include $(CLEAR_VARS)
LOCAL_LDLIBS:=-llog
LOCAL_MODULE :=aa #aa為行程so庫的庫名
LOCAL_SRC_FILES :=sent.cpp \
com_android_usbkey_NativeUsbBase.cpp \
LOCAL_CFLAGS :=-arm -mfloat-abi=softfp -mfpu=vfp -Wmultichar
include $(BUILD_SHARED_LIBRARY)
總結:
要行程.so庫的流程就這些(可根據自己的情況新增.cpp),需注意的是:ubuntu上必須安裝ndk上述檔案才可以形成.so庫。
在命令視窗需輸入的:
1.首先進入jni檔案所在的目錄(刪除的檔案都放在jni檔案裡);
2.輸入命令:ndk-build即可生成so庫(注意:清楚so庫的命令為:ndk-build clean)