JNI就是Java Native Interface, 便可以實現Java調用本地庫, 也能夠實現C/C++調用Java代碼, 從而實現了兩種語言的互通, 可讓咱們更加靈活的使用.html
經過使用JNI能夠從一個側面瞭解Java內部的一些實現.java
本文使用的環境是linux
本文使用到的一些功能:編程
C/C++調用Java代碼的通常步驟:windows
編寫Java代碼並編譯jvm
這段代碼很是簡單, 有個靜態方法和成員方法, 一個public的成員變量函數
1
2
3
4
5
6
7
8
9
10
11
|
public
class
Sample2 {
public
String name;
public
static
String sayHello(String name) {
return
"Hello, "
+ name +
"!"
;
}
public
String sayHello() {
return
"Hello, "
+ name +
"!"
;
}
}
|
因爲沒有定義構造函數, 因此會有一個默認的構造函數.測試
運行下面的命令編譯ui
>javac Sample2.java
|
能夠在當前目錄下看到Sample2.class文件, 編譯成功, 第一步完成了, So easy!spa
能夠查看Sample2類中的簽名
>javap -s -
private
Sample2
|
結果以下
Compiled from
"Sample2.java"
public
class
Sample2
extends
java.lang.Object{
public
java.lang.String name;
Signature: Ljava/lang/String;
public
Sample2();
Signature: ()V
public
static
java.lang.String sayHello(java.lang.String);
Signature: (Ljava/lang/String;)Ljava/lang/String;
public
java.lang.String sayHello();
Signature: ()Ljava/lang/String;
}
|
關於簽名的含義, 請參看使用JNI進行Java與C/C++語言混合編程(1)--在Java中調用C/C++本地庫.
編寫C/C++代碼調用Java代碼
先貼代碼吧
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
|
#include <jni.h>
#include <string.h>
#include <stdio.h>
// 環境變量PATH在windows下和linux下的分割符定義
#ifdef _WIN32
#define PATH_SEPARATOR ';'
#else
#define PATH_SEPARATOR ':'
#endif
int
main(
void
)
{
JavaVMOption options[1];
JNIEnv *env;
JavaVM *jvm;
JavaVMInitArgs vm_args;
long
status;
jclass cls;
jmethodID mid;
jfieldID fid;
jobject obj;
options[0].optionString =
"-Djava.class.path=."
;
memset
(&vm_args, 0,
sizeof
(vm_args));
vm_args.version = JNI_VERSION_1_4;
vm_args.nOptions = 1;
vm_args.options = options;
// 啓動虛擬機
status = JNI_CreateJavaVM(&jvm, (
void
**)&env, &vm_args);
if
(status != JNI_ERR)
{
// 先得到class對象
cls = (*env)->FindClass(env,
"Sample2"
);
if
(cls != 0)
{
// 獲取方法ID, 經過方法名和簽名, 調用靜態方法
mid = (*env)->GetStaticMethodID(env, cls,
"sayHello"
,
"(Ljava/lang/String;)Ljava/lang/String;"
);
if
(mid != 0)
{
const
char
* name =
"World"
;
jstring arg = (*env)->NewStringUTF(env, name);
jstring result = (jstring)(*env)->CallStaticObjectMethod(env, cls, mid, arg);
const
char
* str = (*env)->GetStringUTFChars(env, result, 0);
printf
(
"Result of sayHello: %s\n"
, str);
(*env)->ReleaseStringUTFChars(env, result, 0);
}
/*** 新建一個對象 ***/
// 調用默認構造函數
//obj = (*env)->AllocObjdect(env, cls);
// 調用指定的構造函數, 構造函數的名字叫作<init>
mid = (*env)->GetMethodID(env, cls,
"<init>"
,
"()V"
);
obj = (*env)->NewObject(env, cls, mid);
if
(obj == 0)
{
printf
(
"Create object failed!\n"
);
}
/*** 新建一個對象 ***/
// 獲取屬性ID, 經過屬性名和簽名
fid = (*env)->GetFieldID(env, cls,
"name"
,
"Ljava/lang/String;"
);
if
(fid != 0)
{
const
char
* name =
"icejoywoo"
;
jstring arg = (*env)->NewStringUTF(env, name);
(*env)->SetObjectField(env, obj, fid, arg);
// 修改屬性
}
// 調用成員方法
mid = (*env)->GetMethodID(env, cls,
"sayHello"
,
"()Ljava/lang/String;"
);
if
(mid != 0)
{
jstring result = (jstring)(*env)->CallObjectMethod(env, obj, mid);
const
char
* str = (*env)->GetStringUTFChars(env, result, 0);
printf
(
"Result of sayHello: %s\n"
, str);
(*env)->ReleaseStringUTFChars(env, result, 0);
}
}
(*jvm)->DestroyJavaVM(jvm);
return
0;
}
else
{
printf
(
"JVM Created failed!\n"
);
return
-1;
}
}
|
這段代碼大概作了這幾件事
虛擬的建立
與之相關的有這樣幾個變量
JavaVMOption options[1]; JNIEnv *env; JavaVM *jvm; JavaVMInitArgs vm_args;
JavaVM就是咱們須要建立的虛擬機實例
JavaVMOption至關於在命令行裏傳入的參數
JNIEnv在Java調用C/C++中每一個方法都會有的一個參數, 擁有一個JNI的環境
JavaVMInitArgs就是虛擬機建立的初始化參數, 這個參數裏面會包含JavaVMOption
下面就是建立虛擬機
1
2
3
4
5
6
7
8
|
options[0].optionString =
"-Djava.class.path=."
;
memset
(&vm_args, 0,
sizeof
(vm_args));
vm_args.version = JNI_VERSION_1_4;
vm_args.nOptions = 1;
vm_args.options = options;
// 啓動虛擬機
status = JNI_CreateJavaVM(&jvm, (
void
**)&env, &vm_args);
|
"-Djava.class.path=."看着眼熟吧, 這個就是傳入當前路徑, 做爲JVM尋找class的用戶自定義路徑, 咱們的Sample2.class就在當前路徑(固然也能夠不在當前路徑, 你能夠隨便修改).
vm_args.version是Java的版本, 這個應該是爲了兼容之前的JDK, 可使用舊版的JDK, 這個宏定義是在jni.h中, 有如下四種
#define JNI_VERSION_1_1 0x00010001
#define JNI_VERSION_1_2 0x00010002
#define JNI_VERSION_1_4 0x00010004
#define JNI_VERSION_1_6 0x00010006
|
vm_args.nOptions的含義是, 你傳入的options有多長, 咱們這裏就一個, 因此是1.
vm_args.options = options把JavaVMOption傳給JavaVMInitArgs裏面去.
而後就是啓動虛擬機了status = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args).
能夠經過這個返回值status , 知道虛擬機是否啓動成功
#define JNI_OK 0 /* success */
#define JNI_ERR (-1) /* unknown error */
#define JNI_EDETACHED (-2) /* thread detached from the VM */
#define JNI_EVERSION (-3) /* JNI version error */
#define JNI_ENOMEM (-4) /* not enough memory */
#define JNI_EEXIST (-5) /* VM already created */
#define JNI_EINVAL (-6) /* invalid arguments */
|
尋找class對象, 並實例化
JVM在Java中都是本身啓動的, 在C/C++中只能本身來啓動了, 啓動完以後的事情就和在Java中同樣了, 不過要使用C/C++的語法.
獲取class對象比較簡單, FindClass(env, className).
cls = (*env)->FindClass(env,
"Sample2"
);
|
在Java中的類名格式是java.lang.String, 可是className的格式有點不一樣, 不是使用'.'做爲分割, 而是'/', 即java/lang/String.
咱們知道Java中構造函數有兩種, 一種是默認的沒有參數的, 一種是自定義的帶有參數的. 對應的在C/C++中, 有兩種調用構造函數的方法.
調用默認構造函數
// 調用默認構造函數
obj = (*env)->AllocObjdect(env, cls);
|
構造函數也是方法, 相似調用方法的方式.
// 調用指定的構造函數, 構造函數的名字叫作<init>
mid = (*env)->GetMethodID(env, cls,
"<init>"
,
"()V"
);
obj = (*env)->NewObject(env, cls, mid);
|
調用方法和修改屬性
關於方法和屬性是有兩個ID與之對應, 這兩個ID用來標識方法和屬性.
jmethodID mid;
jfieldID fid;
|
方法分爲靜態和非靜態的, 因此對應的有
mid = (*env)->GetStaticMethodID(env, cls,
"sayHello"
,
"(Ljava/lang/String;)Ljava/lang/String;"
);
mid = (*env)->GetMethodID(env, cls,
"sayHello"
,
"()Ljava/lang/String;"
);
|
上面兩個方法是同名的, 都叫sayHello, 可是簽名不一樣, 因此能夠區分兩個方法.
JNI的函數都是有必定規律的, Static就表示是靜態, 沒有表示非靜態.
方法的調用以下
jstring result = (jstring)(*env)->CallStaticObjectMethod(env, cls, mid, arg);
jstring result = (jstring)(*env)->CallObjectMethod(env, obj, mid);
|
咱們能夠看到靜態方法是隻須要class對象, 不須要實例的, 而非靜態方法須要使用咱們以前實例化的對象.
屬性也有靜態和非靜態, 示例中只有非靜態的.
獲取屬性ID
fid = (*env)->GetFieldID(env, cls,
"name"
,
"Ljava/lang/String;"
);
|
修改屬性的值
(*env)->SetObjectField(env, obj, fid, arg);
// 修改屬性
|
關於jstring的說明
java的String都是使用了unicode, 是雙字節的字符, 而C/C++中使用的單字節的字符.
從C轉換爲java的字符, 使用NewStringUTF方法
jstring arg = (*env)->NewStringUTF(env, name);
|
從java轉換爲C的字符, 使用GetStringUTFChars
const
char
* str = (*env)->GetStringUTFChars(env, result, 0);
|
編譯和運行
編譯須要頭文件, 頭文件在這兩個目錄中%JAVA_HOME%\include和%JAVA_HOME%\include\win32, 第一個是與平臺無關的, 第二個是與平臺有關的, 因爲筆者的系統是windows, 因此是win32.
編譯的時候還要一個lib文件, 是對虛擬機的支持, 保證編譯經過.
cl -I%JAVA_HOME%\include -I%JAVA_HOME%\include\win32 Sample2.c %JAVA_HOME%\lib\jvm.lib
|
咱們能夠看到在當前目錄下Sample2.exe, 運行的時候須要jvm.dll(不要將其複製到當前目錄下, 這樣不能夠運行, 會致使jvm建立失敗)
set PATH=%JAVA_HOME%\jre\bin\client\;%PATH%
Sample2
|
jvm.dll在%JAVA_HOME%\jre\bin\client\目錄下, 因此我把這個目錄加入到PATH中, 而後就能夠運行
Result of sayHello: Hello, World!
Result of sayHello: Hello, icejoywoo!
|
關於C++的說明
本示例的C++版本, 請自行下載後面的源代碼來查看, 區別不是很大.
主要是JNIEnv和JavaVM兩個對象, 在C中是結構體, 是函數指針的集合, 在C++中結構體擁有類的能力, 使用起來更爲簡便, 與Java之間的差別更小一些.
結語
本文介紹了一個簡單的例子, 分析了其中的一些代碼, 筆者但願經過這篇文章讓你們對JNI的瞭解更加深刻一些.
水平有限, 錯漏在所不免, 歡迎指正!
源代碼下載: c調用java.zip
使用方法: 參照裏面的build&run.bat, 使用了%JAVA_HOME%環境變量.
注意: