开始接触Android JNI层面的内容,推荐一本不错的入门级的书《Android的设计与实现:卷一》,这两天看了一下关于Java层和Native层函数映射的章节,加深对JNI的理解。
先是写了一个非常简单的计算器,关键的运算放在Native层实现,然后把运算的结果返回到Java层,写这个的时候还是自己手动建jni文件夹,javah的命令行,写makefile文件,用ndk-build命令行来编译,后来发现要调试C代码了,才发现高版本的ndk环境已经全都集成好了,编译,运行,调试甚至和VS差不多方便,只是自己没配好而已。
下面是非常简单的计算器源码,只是用来熟悉JNI的基本语法,其中我自己碰到过的一个问题,就是LoadLibrary()调用之后,程序直接崩溃,最开始以为是模拟器是x86的模式,而编译的so文件是arm的模式,但是将模拟器改成arm之后还是崩溃,最后无奈在自己手机上测试也是如此,一打开就直接崩溃,在网上能找到的各种方法都试了,最后发现是so命名的问题具体可以参考这篇博客Android Eclipse JNI 调用 .so文件加载问题
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content"> <TextView android:id = "@+id/tvResult" android:layout_width="fill_parent" android:layout_height="wrap_content" android:height="40dp"/> </LinearLayout> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content"> <Button android:id="@+id/btnBackSpace" android:layout_width="wrap_content" android:layout_height="wrap_content" android:width="150dp" android:text = "@string/strbtnbackspace" /> <Button android:id="@+id/btnCE" android:layout_width="wrap_content" android:layout_height="wrap_content" android:width="150dp" android:text="@string/strbtnCE"/> </LinearLayout> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content"> <Button android:id="@+id/btn7" android:layout_width = "wrap_content" android:layout_height="wrap_content" android:width="75dp" android:text="@string/strbtn7"/> <Button android:id="@+id/btn8" android:layout_width = "wrap_content" android:layout_height="wrap_content" android:width = "75dp" android:text="@string/strbtn8"/> <Button android:id="@+id/btn9" android:layout_width="wrap_content" android:layout_height="wrap_content" android:width = "75dp" android:text="@string/strbtn9"/> <Button android:id="@+id/btnADD" android:layout_width="wrap_content" android:layout_height="wrap_content" android:width = "75dp" android:text="@string/strbtnADD"/> </LinearLayout> <LinearLayout android:layout_width="fill_parent" android:layout_height = "wrap_content"> <Button android:id="@+id/btn4" android:layout_width="wrap_content" android:layout_height = "wrap_content" android:width="75dp" android:text="@string/strbtn4"/> <Button android:id="@+id/btn5" android:layout_width="wrap_content" android:layout_height="wrap_content" android:width="75dp" android:text="@string/strbtn5"/> <Button android:id="@+id/btn6" android:layout_width = "wrap_content" android:layout_height="wrap_content" android:width="75dp" android:text="@string/strbtn6"/> <Button android:id="@+id/btnSUB" android:layout_width="wrap_content" android:layout_height="wrap_content" android:width = "75dp" android:text="@string/strbtnSUB"/> </LinearLayout> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content"> <Button android:id="@+id/btn1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:width="75dp" android:text="@string/strbtn1"/> <Button android:id="@+id/btn2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:width="75dp" android:text="@string/strbtn2"/> <Button android:id="@+id/btn3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:width="75dp" android:text="@string/strbtn3"/> <Button android:id="@+id/btnMUL" android:layout_width="wrap_content" android:layout_height="wrap_content" android:width = "75dp" android:text="@string/strbtnMUL"/> </LinearLayout> <LinearLayout android:layout_width = "fill_parent" android:layout_height="wrap_content"> <Button android:id="@+id/btn0" android:layout_width="wrap_content" android:layout_height="wrap_content" android:width = "75dp" android:text="@string/strbtn0"/> <Button android:id="@+id/btnC" android:layout_width="wrap_content" android:layout_height="wrap_content" android:width = "75dp" android:text="@string/strbtnC"/> <Button android:id="@+id/btnRESULT" android:layout_width="wrap_content" android:layout_height="wrap_content" android:width = "75dp" android:text="@string/strbtnRESULT"/> <Button android:id="@+id/btnDIV" android:layout_width="wrap_content" android:layout_height="wrap_content" android:width = "75dp" android:text="@string/strbtnDIV"/> </LinearLayout> </LinearLayout>
calc xml
public class MainActivity extends Activity implements OnClickListener{ static{ System.loadLibrary("CalcJni"); } enum OP { NON, ADD, SUB, MUL, DIV } private TextView tvResult = null; private Button btn0 =null; private Button btn1 =null; private Button btn2 =null; private Button btn3 =null; private Button btn4 =null; private Button btn5 =null; private Button btn6 =null; private Button btn7 =null; private Button btn8 =null; private Button btn9 =null; private Button btnAdd =null; private Button btnSub =null; private Button btnMul =null; private Button btnDiv =null; private Button btnEqu =null; private Button btnBackspace=null; private Button btnCE=null; private Button btnC=null; private OP operator = OP.NON; private int num1; private int num2; private int result; private native int Add(int num1,int num2); private native int Sub(int num1,int num2); private native int Mul(int num1,int num2); private native int Div(int num1,int num2); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); btn0 = (Button)findViewById(R.id.btn0); btn1 = (Button)findViewById(R.id.btn1); btn2 = (Button)findViewById(R.id.btn2); btn3 = (Button)findViewById(R.id.btn3); btn4 = (Button)findViewById(R.id.btn4); btn5 = (Button)findViewById(R.id.btn5); btn6 = (Button)findViewById(R.id.btn6); btn7 = (Button)findViewById(R.id.btn7); btn8 = (Button)findViewById(R.id.btn8); btn9 = (Button)findViewById(R.id.btn9); btnAdd = (Button)findViewById(R.id.btnADD); btnSub = (Button)findViewById(R.id.btnSUB); btnMul = (Button)findViewById(R.id.btnMUL); btnDiv = (Button)findViewById(R.id.btnDIV); tvResult = (TextView)findViewById(R.id.tvResult); tvResult.setTextSize(30); tvResult.setGravity(Gravity.RIGHT); btnBackspace=(Button)findViewById(R.id.btnBackSpace); btnCE=(Button)findViewById(R.id.btnCE); btnC=(Button)findViewById(R.id.btnC); btnEqu = (Button)findViewById(R.id.btnRESULT); btnBackspace.setOnClickListener(this); btnCE.setOnClickListener(this); btn0.setOnClickListener(this); btn1.setOnClickListener(this); btn2.setOnClickListener(this); btn3.setOnClickListener(this); btn4.setOnClickListener(this); btn5.setOnClickListener(this); btn6.setOnClickListener(this); btn7.setOnClickListener(this); btn8.setOnClickListener(this); btn9.setOnClickListener(this); btnAdd.setOnClickListener(this); btnSub.setOnClickListener(this); btnMul.setOnClickListener(this); btnDiv.setOnClickListener(this); btnEqu.setOnClickListener(this); } @Override public void onClick(View v) { // TODO Auto-generated method stub switch (v.getId()) { case R.id.btnBackSpace: String mystr = tvResult.getText().toString(); try { tvResult.setText(mystr.substring(0, mystr.length()-1)); } catch (Exception e) { // TODO: handle exception tvResult.setText(""); } break; case R.id.btnCE: tvResult.setText(null); break; //btn 0 -- 9 case R.id.btn0: String myString0 = tvResult.getText().toString(); myString0 += "0"; tvResult.setText(myString0); break; case R.id.btn1: String myString1 = tvResult.getText().toString(); myString1 += "1"; tvResult.setText(myString1); break; case R.id.btn2: String myString2 = tvResult.getText().toString(); myString2 += "2"; tvResult.setText(myString2); break; case R.id.btn3: String myString3 = tvResult.getText().toString(); myString3 += "3"; tvResult.setText(myString3); break; case R.id.btn4: String myString4 = tvResult.getText().toString(); myString4 += "4"; tvResult.setText(myString4); break; case R.id.btn5: String myString5 = tvResult.getText().toString(); myString5 += "5"; tvResult.setText(myString5); break; case R.id.btn6: String myString6 = tvResult.getText().toString(); myString6 += "6"; tvResult.setText(myString6); break; case R.id.btn7: String myString7 = tvResult.getText().toString(); myString7 += "7"; tvResult.setText(myString7); break; case R.id.btn8: String myString8 = tvResult.getText().toString(); myString8 += "8"; tvResult.setText(myString8); break; case R.id.btn9: String myString9 = tvResult.getText().toString(); myString9 += "9"; tvResult.setText(myString9); break; //+-*/ case R.id.btnADD: String myAddString = tvResult.getText().toString(); if (myAddString.equals(null)) { return; } num1 = Integer.valueOf(myAddString); tvResult.setText(null); operator = OP.ADD; break; case R.id.btnSUB: String mySubString = tvResult.getText().toString(); if (mySubString.equals(null)) { return; } num1 = Integer.valueOf(mySubString); tvResult.setText(null); operator = OP.SUB; break; case R.id.btnMUL: String myMulString = tvResult.getText().toString(); if (myMulString.equals(null)) { return; } num1 = Integer.valueOf(myMulString); tvResult.setText(null); operator = OP.MUL; break; case R.id.btnDIV: String myDivString = tvResult.getText().toString(); if (myDivString.equals(null)) { return; } num1 = Integer.valueOf(myDivString); tvResult.setText(null); operator = OP.DIV; break; case R.id.btnRESULT: String myResultString = tvResult.getText().toString(); if(myResultString.equals(null)){ return; } num2 = Integer.valueOf(myResultString); switch (operator) { case ADD: result = Add(num1, num2); break; case SUB: result = Sub(num1, num2); break; case MUL: result = Mul(num1, num2); break; case DIV: result = Div(num1, num2); break; default: break; } tvResult.setText(Integer.toString(result)); break; default: break; } } }
calc java
JNIEXPORT jint JNICALL Java_com_example_calcjni_MainActivity_Add (JNIEnv * env, jobject obj, jint num1, jint num2) { return (jint)(num1+num2); } JNIEXPORT jint JNICALL Java_com_example_calcjni_MainActivity_Sub (JNIEnv * env, jobject obj , jint num1, jint num2) { return (jint)(num1-num2); } JNIEXPORT jint JNICALL Java_com_example_calcjni_MainActivity_Mul (JNIEnv * env, jobject obj, jint num1, jint num2) { return (jint)(num1*num2); } JNIEXPORT jint JNICALL Java_com_example_calcjni_MainActivity_Div (JNIEnv * env, jobject obj, jint num1, jint num2) { if(num2==0) return 0; return (jint)(num1/num2); }
calc native
我们经常会写如下的代码输出日志:
Log.d(TAG,”Debug Log”);
我们就以Log系统为例来学习JNI。
我们先看一下Log类的内容,在android源码的\frameworks\base\core\java\android\Log.java文件中
/** * Send a {@link #DEBUG} log message. * @param tag Used to identify the source of a log message. It usually identifies * the class or activity where the log call occurs. * @param msg The message you would like logged. */ public static int d(String tag, String msg) { return println_native(LOG_ID_MAIN, DEBUG, tag, msg); } /** @hide */ public static final int LOG_ID_MAIN = 0; /** @hide */ public static final int LOG_ID_RADIO = 1; /** @hide */ public static final int LOG_ID_EVENTS = 2; /** @hide */ public static final int LOG_ID_SYSTEM = 3; /** @hide */ public static native int println_native(int bufID, int priority, String tag, String msg);
可以看到所有的Log的方法都调用了native 的println_native方法,在android源码中的\frameworks\base\core\jni\android_until_Log.cpp文件中实现:
/* * In class android.util.Log: * public static native int println_native(int buffer, int priority, String tag, String msg) */ /* *JNI方法增加了JNIEnv和jobject两参数,其余的参数和返回值只是将Java层参数映**射成JNI的数据类型,然后通过调用本地库和JNIEnv提供的JNI函数处理数据,最后返给java层 */ static jint android_util_Log_println_native(JNIEnv* env, jobject clazz, jint bufID, jint priority, jstring tagObj, jstring msgObj) { const char* tag = NULL; const char* msg = NULL; if (msgObj == NULL) { //异常处理 jclass npeClazz; npeClazz = env->FindClass("java/lang/NullPointerException"); assert(npeClazz != NULL); //抛出异常 env->ThrowNew(npeClazz, "println needs a message"); return -1; } if (bufID < 0 || bufID >= LOG_ID_MAX) { jclass npeClazz; npeClazz = env->FindClass("java/lang/NullPointerException"); assert(npeClazz != NULL); env->ThrowNew(npeClazz, "bad bufID"); return -1; } if (tagObj != NULL) tag = env->GetStringUTFChars(tagObj, NULL); msg = env->GetStringUTFChars(msgObj, NULL); //向内核写入日志 int res = __android_log_buf_write(bufID, (android_LogPriority)priority, tag, msg); if (tag != NULL) env->ReleaseStringUTFChars(tagObj, tag); env->ReleaseStringUTFChars(msgObj, msg); return res; }
至此,JNI层已经实现了在java层声明的Native层方法,但是这两个又是如何联系到一起的呢?我们再看android_util_Log.cpp的源码
/* * JNI registration. */ static JNINativeMethod gMethods[] = { /* name, signature, funcPtr */ { "isLoggable", "(Ljava/lang/String;I)Z", (void*) android_util_Log_isLoggable }, {"println_native","(IILjava/lang/String;Ljava/lang/String;)I",(void*)android_util_Log_println_native }, };
在\dalvik\libnativehelper\include\nativehelper\Jni.h文件中有JNINativeMethod 的定义:
typedef struct { const char* name; //java层声明的native函数的函数名 const char* signature; //Java函数的签名 void* fnPtr; //函数指针,指向JNI层的实现方法 } JNINativeMethod;
我们可以看到printIn_native的对应关系:
{"println_native","(IILjava/lang/String;Ljava/lang/String;)I",(void*)android_util_Log_println_native }
Java层声明的函数名是print_native
Java层声明的native函数的签名为(IILjava/lang/String;Ljava/lang/String;)I
JNI方法实现方法的指针为(void*)android_util_Log_println_native
我们知道了java层和JNI层的映射关系,但是如何把这种关系告诉Dalvik虚拟机呢?,我们继续看android_util_Log.cpp的源码
int register_android_util_Log(JNIEnv* env) { jclass clazz = env->FindClass("android/util/Log"); if (clazz == NULL) { LOGE("Can‘t find android/util/Log"); return -1; } levels.verbose = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "VERBOSE", "I")); levels.debug = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "DEBUG", "I")); levels.info = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "INFO", "I")); levels.warn = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "WARN", "I")); levels.error = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "ERROR", "I")); levels.assert = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "ASSERT", "I")); return AndroidRuntime::registerNativeMethods(env, "android/util/Log", gMethods, NELEM(gMethods)); } }; // namespace android
这个函数的最后调用了AndroidRuntime::registerNativeMethods函数
可以在\frameworks\base\core\jni\AndroidRuntime.cpp 中找到registerNativeMethods的实现
/* * Register native methods using JNI. */ /*static*/ int AndroidRuntime::registerNativeMethods(JNIEnv* env, const char* className, const JNINativeMethod* gMethods, int numMethods) { return jniRegisterNativeMethods(env, className, gMethods, numMethods); }
他的内部实现只是调用了jniRegisterNativeMethods ()。
在\dalvik\libnativehelper\JNIHelp.c中jniRegisterNativeMethods函数的实现
/* * Register native JNI-callable methods. * * "className" looks like "java/lang/String". */ int jniRegisterNativeMethods(JNIEnv* env, const char* className, const JNINativeMethod* gMethods, int numMethods) { jclass clazz; LOGV("Registering %s natives\n", className); clazz = (*env)->FindClass(env, className); if (clazz == NULL) { LOGE("Native registration unable to find class ‘%s‘\n", className); return -1; } int result = 0; if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) { LOGE("RegisterNatives failed for ‘%s‘\n", className); result = -1; } (*env)->DeleteLocalRef(env, clazz); return result; }
这里是调用了JNIEnv的RegisterNatives函数,可以阅读函数的注释,注册一个类的Native方法。已经告诉了虚拟机java层和native层的映射关系。
/* * Register one or more native functions in one class. * * This can be called multiple times on the same method, allowing the * caller to redefine the method implementation at will. */ static jint RegisterNatives(JNIEnv* env, jclass jclazz, const JNINativeMethod* methods, jint nMethods) { JNI_ENTER(); ClassObject* clazz = (ClassObject*) dvmDecodeIndirectRef(env, jclazz); jint retval = JNI_OK; int i; if (gDvm.verboseJni) { LOGI("[Registering JNI native methods for class %s]\n", clazz->descriptor); } for (i = 0; i < nMethods; i++) { if (!dvmRegisterJNIMethod(clazz, methods[i].name, methods[i].signature, methods[i].fnPtr)) { retval = JNI_ERR; } } JNI_EXIT(); return retval; }
其作用是向clazz参数指定的类注册本地方法,这样,虚拟机就能得到Java层和JNI层之间的对应关系,就可以实现java和native层代码的交互了。我们注意到在Log系统的实例中,JNI层实现方法和注册方法中都使用了JNIEnv这个指针,通过它调用JNI函数,访问Dalvik虚拟机,进而操作Java对象。
我们可以在\Dalvik\libnativehelper\include\nativehelper\jni.h中找到JNIEnv的定义:
struct _JNIEnv; struct _JavaVM; typedef const struct JNINativeInterface* C_JNIEnv; #if defined(__cplusplus) //定义了C++ typedef _JNIEnv JNIEnv; //C++中的JNIEnv的类型 typedef _JavaVM JavaVM; #else typedef const struct JNINativeInterface* JNIEnv; typedef const struct JNIInvokeInterface* JavaVM; #endif
这里只是用关键字typedef关键字做了类型定义,那么_JNIEnv和JNINativeInterface的定义
/* * C++ object wrapper. * * This is usually overlaid on a C struct whose first element is a * JNINativeInterface*. We rely somewhat on compiler behavior. */ struct _JNIEnv { /* do not rename this; it does not seem to be entirely opaque */ const struct JNINativeInterface* functions; #if defined(__cplusplus) jint GetVersion() { return functions->GetVersion(this); } jclass DefineClass(const char *name, jobject loader, const jbyte* buf, jsize bufLen) { return functions->DefineClass(this, name, loader, buf, bufLen); } jclass FindClass(const char* name) { return functions->FindClass(this, name); } jmethodID FromReflectedMethod(jobject method) { return functions->FromReflectedMethod(this, method); } ………..
_JNIEnv只是对const struct JNINativeInterface*类型的封装,并间接调用const struct JNINativeInterface*上定义的方法
/* * Table of interface function pointers. */ struct JNINativeInterface { …… jclass (*FindClass)(JNIEnv*, const char*); jboolean (*IsSameObject)(JNIEnv*, jobject, jobject); …… };
这里才真正涉及JNI函数的调用,也只是一个接口,具体的实现要参考Dalvik虚拟机。
但是我们可以得出如下结论:
C++中:JNIEnv就是struct _JNIEnv。JNIEnv *env 等价于 struct _JNIEnv *env ,在调用JNI函数的时候,只需要env->FindClass(JNIEnv*,const char ),就会间接调用JNINativeInterface结构体里面定义的函数指针,而无需首先对env解引用。
C中:JNIEnv就是const struct JNINativeInterface *。JNIEnv *env 等价于const struct JNINativeInterface ** env,因此要得到JNINativeInterface结构体里面的函数指针就必须先对env解引用得到(*env),得到const struct JNINativeInterface *,才是真正指向JNINativeInterface结构体的指针,然后再通过它调用具体的JNI函数,因此需要这样调用:
(*env)->FindClass(JNIEnv*,const char*)。
接下来了解关于Jni和java层数据类型的关系,Jni.h文件中关于基本数据类型的定义
/* * Primitive types that match up with Java equivalents. */ #ifdef HAVE_INTTYPES_H # include <inttypes.h> /* C99 */ typedef uint8_t jboolean; /* unsigned 8 bits */ typedef int8_t jbyte; /* signed 8 bits */ typedef uint16_t jchar; /* unsigned 16 bits */ typedef int16_t jshort; /* signed 16 bits */ typedef int32_t jint; /* signed 32 bits */ typedef int64_t jlong; /* signed 64 bits */ typedef float jfloat; /* 32-bit IEEE 754 */ typedef double jdouble; /* 64-bit IEEE 754 */ #else typedef unsigned char jboolean; /* unsigned 8 bits */ typedef signed char jbyte; /* signed 8 bits */ typedef unsigned short jchar; /* unsigned 16 bits */ typedef short jshort; /* signed 16 bits */ typedef int jint; /* signed 32 bits */ typedef long long jlong; /* signed 64 bits */ typedef float jfloat; /* 32-bit IEEE 754 */ typedef double jdouble; /* 64-bit IEEE 754 */ #endif
关于一些返回状态值的定义:
#define JNI_FALSE 0 #define JNI_TRUE 1 #define JNI_OK (0) /* no error */ #define JNI_ERR (-1) /* generic error */ #define JNI_EDETACHED (-2) /* thread detached from the VM*/ #define JNI_EVERSION (-3) /* JNI version error */ #define JNI_COMMIT 1 /* copy content, do not free buffer */ #define JNI_ABORT 2 /* free buffer w/o copying back */
JNI引用类型采用了与Java类型相似的继承关系,树根是Jobject
下面是Jni.h中关于引用类型的定义,在C++中全都继承自class jobjct{};而C中都是void*的指针。
#ifdef __cplusplus /* * Reference types, in C++ */ class _jobject {}; class _jclass : public _jobject {}; class _jstring : public _jobject {}; class _jarray : public _jobject {}; class _jobjectArray : public _jarray {}; //java层 object[] class _jbooleanArray : public _jarray {}; //java层 boolean[] class _jbyteArray : public _jarray {}; //byte[] class _jcharArray : public _jarray {}; //char[] class _jshortArray : public _jarray {}; //short[] class _jintArray : public _jarray {}; //in[] class _jlongArray : public _jarray {}; class _jfloatArray : public _jarray {}; class _jdoubleArray : public _jarray {}; class _jthrowable : public _jobject {}; typedef _jobject* jobject; typedef _jclass* jclass; typedef _jstring* jstring; typedef _jarray* jarray; typedef _jobjectArray* jobjectArray; typedef _jbooleanArray* jbooleanArray; typedef _jbyteArray* jbyteArray; typedef _jcharArray* jcharArray; typedef _jshortArray* jshortArray; typedef _jintArray* jintArray; typedef _jlongArray* jlongArray; typedef _jfloatArray* jfloatArray; typedef _jdoubleArray* jdoubleArray; typedef _jthrowable* jthrowable; typedef _jobject* jweak; #else /* not __cplusplus */ /* * Reference types, in C. */ typedef void* jobject; typedef jobject jclass; typedef jobject jstring; typedef jobject jarray; typedef jarray jobjectArray; typedef jarray jbooleanArray; typedef jarray jbyteArray; typedef jarray jcharArray; typedef jarray jshortArray; typedef jarray jintArray; typedef jarray jlongArray; typedef jarray jfloatArray; typedef jarray jdoubleArray; typedef jobject jthrowable; typedef jobject jweak; #endif /* not __cplusplus */
JNI接口指针值JNI实现方法的第一个参数,其类型是JNIEnv。第二个参数因本地方法是静态还是非静态而不同,非静态本地方法的第二个参数是对Java对象的引用,而静态本地方法的第二个参数是对其java类的引用,其余的参数都对应与java方法的参数。可以借助javah 工具来生成对应的native函数声明。
而在Java层和native层都是支持函数重载,仅仅依靠函数名无法确定唯一的一个方法,所以JNI提供了一套签名规则,用一串字符串来唯一确定一个方法:
(参数1类型签名 参数2类型签名……参数n类型签名)返回值类型
和smali语言中的规则一样,就不加以赘述了,可以参考非虫的《Android软件安全与逆向分析》中的相关章节或者这篇文章smali语法文档,只简单举个例子。
还是以我们之前的println_native为例:
Java层的声明 public static native int println_native(int buffer, int priority, String tag, String msg) ;
对应的签名就是 (IILjava/lang/String;Ljava/lang/String;)I
至此我们实现的JNI层方法和java层声明的方法建立的唯一的映射关系。
接下来我们继续学习在JNI层访问java层对象,在JNI层操作jobject,就是要访问这个对象并操作它的变量和方法,我们常用的两个JNI函数FindClass() 和 GetObjectClass():
C++中的函数原型:
jclass FindClass(const char* name);
class GetObjectClass(jobject obj);
C中的函数原型:
jclass (*FindClass)(JNIEnv*,const char* name);
class (*GetObjectClass)(JNIEnv*,jobject obj);
通过给FindClass传入要查找类的全限定类名(以”/”分隔路径),返回一个jclass的对象,这样就可以操作这个类的方法和变量了。
下面是一个特别简单的例子,点击button以后,调用native层的getReply()方法,然后在native层getReply()方法的实现中反向调用java层的callBack()方法,输入日志。
public class MainActivity extends Activity { static{ System.loadLibrary("NewJni"); } private String TAG = "CCDebug"; private Button btnButton = null; private native String getReply(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); btnButton = (Button)findViewById(R.id.btn1); btnButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub Log.d(TAG, getReply()); } }); } private void callBack() { Log.d(TAG, "call back form native !"); throw new NullPointerException(); } }
java
#include <jni.h> #ifdef __cplusplus extern "C" { #endif JNIEXPORT jint JNICALL Java_com_example_newjni_MainActivity_getReply (JNIEnv * env, jobject obj); JNIEXPORT jstring JNICALL Java_com_example_newjni_MainActivity_getReply (JNIEnv * env, jobject obj) { jclass jcls = env->GetObjectClass(obj); jmethodID jmId = env->GetMethodID(jcls,"callBack","()V"); env->CallVoidMethod(obj,jmId); if(env->ExceptionCheck()) { env->ExceptionDescribe(); env->ExceptionClear(); } return env->NewStringUTF("Hello From JNI!"); }
native
这是利用javah生成的函数声明,严格遵守NDk的语法要求,当然,我们自己也可以像Log系统那样,自己注册函数的映射关系而不必遵守NDK语法,下面就是将getReply()函数手动注册的例子,但是手动注册我自己目前还存在几个问题:
1. native的代码始终不能下断点到JNI_Onload()函数中
2. 第一次点击Button,native层代码没有响应,必须是第二次点击才会响应
public class MainActivity extends Activity { private static final String TAG = "CCDebug"; Button btnButton = null; private native String getReply(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); btnButton = (Button)findViewById(R.id.btn); btnButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub loadLibrary("jniException"); Log.d(TAG, getReply()); } }); } private void callBack() { Log.d(TAG, "call back form native !"); throw new NullPointerException(); } /* * 如果在onClick()函数中直接调用System.loadLibrary(),在调试native代码时会出现 * No symbol table is loaded. Use the "file" command. * 而不能调试native源代码 */ public static void loadLibrary(String libName) { System.loadLibrary(libName); } }
#include <jni.h> #include <string.h> #ifdef __cplusplus extern "C" { #endif JNIEXPORT jstring JNICALL MyFunc (JNIEnv *env, jobject obj); static int registerNativeMethods(JNIEnv* env,const char *className, JNINativeMethod* gMethods,int numMethods); static int registerNatives(JNIEnv *env); #ifdef __cplusplus } #endif #define LOGD(msg) \ __android_log_write(ANDROID_LOG_ERROR,"CCDebug",msg); JNIEXPORT jstring JNICALL MyFunc (JNIEnv *env, jobject obj) { /* * 通过JNI函数GetObjectClass得到传入对象的类信息 * 这里传入的对象就是调用Native方法的那个对象 */ jclass jcls = env->GetObjectClass(obj); //根据类信息得到callback方法的jmethodID jmethodID jmId = env->GetMethodID(jcls,"callBack","()V"); //调用callback方法 env->CallVoidMethod(obj,jmId); /* * 如果检查是否有异常发生 * 如果有异常发生就处理,否则异常将会抛给java层的callback方法 */ if(env->ExceptionCheck()) //检查异常 { env->ExceptionDescribe(); env->ExceptionClear(); //清除异常 } return env->NewStringUTF("Show Message Form JNI!"); } static JNINativeMethod gmethods[] = { { "getReply", "()Ljava/lang/String;", (void*)MyFunc }, }; static int registerNativeMethods(JNIEnv* env,const char *className, JNINativeMethod* gMethods,int numMethods) { jclass clazz; clazz = env->FindClass(className); if(clazz == NULL) { return JNI_FALSE; } //调用JNIEnv提供的注册函数向虚拟机注册 if(env->RegisterNatives(clazz,gMethods,numMethods)<0) { return JNI_FALSE; } return JNI_TRUE; } static int registerNatives(JNIEnv *env) { if (!registerNativeMethods(env,"com/example/jniexception/MainActivity",gmethods,sizeof(gmethods)/sizeof(gmethods[0]))) { return JNI_FALSE; } return JNI_TRUE; } jint JNI_OnLoad(JavaVM* vm, void* reserved) { jint result = -1; JNIEnv* env = NULL; if (vm->GetEnv((void**)&env,JNI_VERSION_1_4)) { return result; } if (registerNatives(env)!=JNI_TRUE) { return result; } result = JNI_VERSION_1_4; return result; }
总结:这两天收获还是很大的,尽管中间也遇到了诸多的问题,网上能找到的答案也不尽然,很感谢那些给出了办法解决了我问题的人,下面附上我这两天发现的几篇我觉得很好的文章:
安卓动态调试七种武器之长生剑 - Smali Instrumentation