通過(guò)第一篇文章,大家明白了調(diào)用 native 方法之前,首先要調(diào)用 System.loadLibrary 接口加載一個(gè)實(shí)現(xiàn)了native 方法的動(dòng)態(tài)庫(kù)才能正常訪問(wèn),否則就會(huì)拋出 java.lang.UnsatisfiedLinkError 異常,找不到 XX 方法的提示?,F(xiàn)在我們想想,在 Java 中調(diào)用某個(gè) native 方法時(shí),JVM 是通過(guò)什么方式,能正確的找到動(dòng)態(tài)庫(kù)中 C/C++ 實(shí)現(xiàn)的那個(gè) native 函數(shù)呢?
JVM 查找 native 方法有兩種方式:
本文通過(guò)第一篇文章 HelloWorld 示例中的 Java_com_study_jnilearn_HelloWorld_sayHello 函數(shù)來(lái)詳細(xì)介紹第一種方式:
/* 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
在上篇文章中,我們?cè)趯?HelloWorld.c 編譯成動(dòng)態(tài)庫(kù)的時(shí)候,用-I
參數(shù)包含了 JDK 安裝目錄下的兩個(gè)頭文件目錄:
gcc -dynamiclib -o /Users/yangxin/Library/Java/Extensions/libHelloWorld.jnilib jni/HelloWorld.c -framework JavaVM -I/$JAVA_HOME/include -I/$JAVA_HOME/include/darwin
其中第一個(gè)目錄為jni.h
頭文件所在目錄,第二個(gè)是跨平臺(tái)頭文件目錄(Mac os x系統(tǒng)下的目錄名為 darwin,在 Windows 下目錄名為 win32,linux 下目錄名為 linux),用于定義與平臺(tái)相關(guān)的宏,其中用于標(biāo)識(shí)函數(shù)用途的兩個(gè)宏 JNIEXPORT 和 JNICALL,就定義在 darwin 目錄下的jni_md.h
頭文件中。在 Windows 中編譯 dll 動(dòng)態(tài)庫(kù)規(guī)定,如果動(dòng)態(tài)庫(kù)中的函數(shù)要被外部調(diào)用,需要在函數(shù)聲明中添加__declspec
(dllexport)標(biāo)識(shí),表示將該函數(shù)導(dǎo)出在外部可以調(diào)用。在 Linux/Unix 系統(tǒng)中,這兩個(gè)宏可以省略不加。這兩個(gè)平臺(tái)的區(qū)別是由于各自的編譯器所產(chǎn)生的可執(zhí)行文件格式不一樣。這里有篇文章詳細(xì)介紹了兩個(gè)平臺(tái)編譯的動(dòng)態(tài)庫(kù)區(qū)別:http://www.cnblogs.com/chio/archive/2008/11/13/1333119.html。JNICALL 在 Windows 中的值為__stdcall
,用于約束函數(shù)入棧順序和堆棧清理的規(guī)則。
Windows 下jni_md.h
頭文件內(nèi)容:
#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
頭文件內(nèi)容:
#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
頭文件可以看出來(lái),JNIEXPORT 和 JNICALL 是一個(gè)空定義,所以在 Linux 下 JNI 函數(shù)聲明可以省略這兩個(gè)宏。
函數(shù)的命名規(guī)則:
用 javah 工具生成函數(shù)原型的頭文件,函數(shù)命名規(guī)則為:Java_
類全路徑_方法名。如Java_com_study_jnilearn_HelloWorld_sayHello
,其中Java_
是函數(shù)的前綴,com_study_jnilearn_HelloWorld
是類名,sayHello
是方法名,它們之間用 _
(下劃線) 連接。
函數(shù)參數(shù):
JNIEXPORT jstring JNICALL Java_com_study_jnilearn_HelloWorld_sayHello(JNIEnv *, jclass, jstring);
JNIEnv*
是定義任意 native 函數(shù)的第一個(gè)參數(shù)(包括調(diào)用 JNI 的 RegisterNatives 函數(shù)注冊(cè)的函數(shù)),指向 JVM 函數(shù)表的指針,函數(shù)表中的每一個(gè)入口指向一個(gè) JNI 函數(shù),每個(gè)函數(shù)用于訪問(wèn) JVM 中特定的數(shù)據(jù)結(jié)構(gòu)。函數(shù)返回值類型:夾在 JNIEXPORT 和 JNICALL 宏中間的 jstring,表示函數(shù)的返回值類型,對(duì)應(yīng) Java 的String 類型。
總結(jié):當(dāng)我們熟悉了 JNI 的 native 函數(shù)命名規(guī)則之后,就可以不用通過(guò)javah
命令去生成相應(yīng) java native方法的函數(shù)原型了,只需要按照函數(shù)命名規(guī)則編寫(xiě)相應(yīng)的函數(shù)原型和實(shí)現(xiàn)即可。
比如com.study.jni.Utils
類中還有一個(gè)計(jì)算加法的 native 實(shí)例方法add
,有兩個(gè)in
t參數(shù)和一個(gè)int
返回值:public native int add(int num1, int num2)
,對(duì)應(yīng) JNI 的函數(shù)原型就是:JNIEXPORT jint JNICALL Java_com_study_jni_Utils_add(JNIEnv *, jobject, jint,jint)
。