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%环境变量.
注意: