通過第一篇文章,大家明白了調用native方法之前,首先要調用System.loadLibrary接口加載一個實現了native方法的動態庫才能正常訪問,否則就會拋出java.lang.UnsatisfiedLinkError異常,找不到XX方法的提示。現在我們想想,在Java中調用某個native方法時,JVM是通過什么方式,能正確的找到動態庫中C/C++實現的那個native函數呢?
**JVM查找native方法有兩種方式:**
1> 按照JNI規范的命名規則
2> 調用JNI提供的RegisterNatives函數,將本地函數注冊到JVM中。(后面會詳細介紹)
本文通過第一篇文章HelloWorld示例中的Java_com_study_jnilearn_HelloWorld_sayHello函數來詳細介紹第一種方式:
~~~
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_study_jnilearn_HelloWorld */
#ifndef _Included_com_study_jnilearn_HelloWorld
#define _Included_com_study_jnilearn_HelloWorld
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_study_jnilearn_HelloWorld
* Method: sayHello
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_study_jnilearn_HelloWorld_sayHello
(JNIEnv *, jclass, jstring);
#ifdef __cplusplus
}
#endif
#endif
~~~
***JNIEXPORT 和 JNICALL的作用:***
在上篇文章中,我們在將HelloWorld.c編譯成動態庫的時候,用-I參數包含了JDK安裝目錄下的兩個頭文件目錄:
~~~
gcc -dynamiclib -o /Users/yangxin/Library/Java/Extensions/libHelloWorld.jnilib jni/HelloWorld.c -framework JavaVM -I/$JAVA_HOME/include -I/$JAVA_HOME/include/darwin
~~~
其中第一個目錄為jni.h頭文件所在目錄,第二個是跨平臺頭文件目錄(mac os x系統下的目錄名為darwin,在windows下目錄名為win32,linux下目錄名為linux),用于定義與平臺相關的宏,其中用于標識函數用途的兩個宏**JNIEXPORT**和?JNICALL,就定義在darwin目錄下的**jni_md.h**頭文件中。在Windows中編譯dll動態庫規定,如果動態庫中的函數要被外部調用,需要在函數聲明中添加__declspec(dllexport)標識,表示將該函數導出在外部可以調用。在Linux/Unix系統中,這兩個宏可以省略不加。這兩個平臺的區別是由于各自的編譯器所產生的可執行文件格式不一樣。這里有篇文章詳細介紹了兩個平臺編譯的動態庫區別:[http://www.cnblogs.com/chio/archive/2008/11/13/1333119.html](http://www.cnblogs.com/chio/archive/2008/11/13/1333119.html)。JNICALL在windows中的值為__stdcall,用于約束函數入棧順序和堆棧清理的規則。
Windows下jni_md.h頭文件內容:
~~~
#ifndef _JAVASOFT_JNI_MD_H_
#define _JAVASOFT_JNI_MD_H_
#define JNIEXPORT __declspec(dllexport)
#define JNIIMPORT __declspec(dllimport)
#define JNICALL __stdcall
typedef long jint;
typedef __int64 jlong;
typedef signed char jbyte;
#endif
~~~
**Linux下jni_md.h頭文件內容:**
~~~
#ifndef _JAVASOFT_JNI_MD_H_
#define _JAVASOFT_JNI_MD_H_
#define JNIEXPORT
#define JNIIMPORT
#define JNICALL
typedef int jint;
#ifdef _LP64 /* 64-bit Solaris */
typedef long jlong;
#else
typedef long long jlong;
#endif
typedef signed char jbyte;
#endif
~~~
從Linux下的jni_md.h頭文件可以看出來,JNIEXPORT 和 JNICALL是一個空定義,所以在Linux下JNI函數聲明可以省略這兩個宏。
**函數的命名規則:**
用javah工具生成函數原型的頭文件,函數命名規則為:Java_類全路徑_方法名。如Java_com_study_jnilearn_HelloWorld_sayHello,其中Java_是函數的前綴,com_study_jnilearn_HelloWorld是類名,sayHello是方法名,它們之間用 _(下劃線) 連接。
**函數參數:**
JNIEXPORT jstring JNICALL Java_com_study_jnilearn_HelloWorld_sayHello(JNIEnv *, jclass, jstring);
第一個參數:JNIEnv* 是定義任意native函數的**第一個參數**(包括調用JNI的RegisterNatives函數注冊的函數),指向JVM函數表的指針,函數表中的每一個入口指向一個JNI函數,每個函數用于訪問JVM中特定的數據結構。
第二個參數:調用java中native方法的實例或Class對象,如果這個native方法是實例方法,則該參數是jobject,如果是靜態方法,則是jclass
第三個參數:Java對應JNI中的數據類型,Java中String類型對應JNI的jstring類型。(后面會詳細介紹JAVA與JNI數據類型的映射關系)
**函數返回值類型:**
夾在JNIEXPORT和JNICALL宏中間的jstring,表示函數的返回值類型,對應Java的String類型
**總結:**
當我們熟悉了JNI的native函數命名規則之后,就可以不用通過javah命令去生成相應java native方法的函數原型了,只需要按照函數命名規則編寫相應的函數原型和實現即可。
比如com.study.jni.Utils類中還有一個計算加法的native實例方法add,有兩個int參數和一個int返回值:public native int add(int num1, int num2),對應JNI的函數原型就是:JNIEXPORT jint JNICALL Java_com_study_jni_Utils_add(JNIEnv *, jobject, jint,jint);?
- 前言
- JNI/NDK開發指南(開山篇)
- JNI/NDK開發指南(一)—— JNI開發流程及HelloWorld
- JNI/NDK開發指南(二)——JVM查找java native方法的規則
- JNI/NDK開發指南(三)——JNI數據類型及與Java數據類型的映射關系
- JNI/NDK開發指南(四)——字符串處理
- Android NDK開發Crash錯誤定位
- JNI/NDK開發指南(五)——訪問數組(基本類型數組與對象數組)
- JNI/NDK開發指南(六)——C/C++訪問Java實例方法和靜態方法
- JNI/NDK開發指南(七)——C/C++訪問Java實例變量和靜態變量
- JNI/NDK開發指南(八)——調用構造方法和父類實例方法
- JNI/NDK開發指南(九)——JNI調用性能測試及優化
- JNI/NDK開發指南(十)——JNI局部引用、全局引用和弱全局引用
- Android JNI局部引用表溢出:local reference table overflow (max=512)
- JNI/NDK開發指南(十一)——JNI異常處理