鍍金池/ 教程/ Java/ JVM 查找 native 方法的規(guī)則
Android NDK 開(kāi)發(fā)環(huán)境
Android NDK 簡(jiǎn)介
JNI 字符串處理
JVM 查找 native 方法的規(guī)則
JNI 開(kāi)發(fā)流程
JNI 局部引用、全局引用和弱全局引用
JNI 數(shù)據(jù)類型與 Java 數(shù)據(jù)類型的映射關(guān)系
JNI 概述
JNI 調(diào)用性能測(cè)試及優(yōu)化
C/C++ 訪問(wèn) Java 實(shí)例變量和靜態(tài)變量
JNI 調(diào)用構(gòu)造方法和父類實(shí)例方法
C/C++ 訪問(wèn) Java 實(shí)例方法和靜態(tài)方法
開(kāi)發(fā)自己的 NDK 程序
JNI 訪問(wèn)數(shù)組

JVM 查找 native 方法的規(guī)則

通過(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 方法

JVM 查找 native 方法有兩種方式:

  • 按照 JNI 規(guī)范的命名規(guī)則
  • 調(diào)用 JNI 提供的 RegisterNatives 函數(shù),將本地函數(shù)注冊(cè)到 JVM 中。(后面會(huì)詳細(xì)介紹)

本文通過(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  

JNIEXPORT 和 JNICALL 的作用

在上篇文章中,我們?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);
  • 第一個(gè)參數(shù):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)。
  • 第二個(gè)參數(shù):調(diào)用 Java 中 native 方法的實(shí)例或 Class 對(duì)象,如果這個(gè) native 方法是實(shí)例方法,則該參數(shù)是 jobject,如果是靜態(tài)方法,則是 jclass。
  • 第三個(gè)參數(shù):Java 對(duì)應(yīng) JNI 中的數(shù)據(jù)類型,Java 中 String 類型對(duì)應(yīng) JNI 的 jstring 類型。(后面會(huì)詳細(xì)介紹 JAVA 與 JNI 數(shù)據(jù)類型的映射關(guān)系)。

函數(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è)int參數(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)