什么是JNI?
JNI(Java Native Interface) Java调用 c/c++,c/c++调用Java的一套API。
JNI不是Android开发独有的,Java项目工程也会用到。
JNI调用C流程
- 定义
native
方法 -
javah
命令,生成.h
头文件 - 复制生成的
.h
头文件到CPP
工程中 - 复制
jni.h
和jni_md.h
文件到CPP
工程中 - 在
.c
文件中,实现第二步声明的函数 - 生成
dll
文件,并复制到项目根目录
1. 定义native方法
package com.loveq;
public class Main {
//定义native方法
public native static String sayHelloFromC();
public static void main(String[] args) {
}
}
2.javah
命令,生存.h
头文件
//2.使用javah生成头文件(javah加全路径) 例如: javah com.loveq.Main (这里在src路径下执行)
3.复制生成的.h
头文件到CPP
工程中
4. 复制jni.h
和jni_md.h
文件到CPP
工程中
jni.h
和jni_md.h
在JDK
的include
文件夹中,然后修改通过javah
生成的头文件,将#include
改为 #include "jni.h"
(<>
代表加载系统的)
在.c
文件中,实现第二步声明的函数
#include "com_loveq_Main.h"
//c中的函数名等于Java_完整类名_函数名
JNIEXPORT jstring JNICALL Java_com_loveq_Main_sayHelloFromC
(JNIEnv * env, jclass jcls){
//将C的字符串转为java字符串
return (*env)->NewStringUTF(env, "hello from c");
}
c中的函数名等于Java_完整类名_函数名。
6.生成dll
文件,并复制到项目根目录
6.1 配置管理器
6.2 新建平台
6.3 选择x64平台
6.4 保存关闭
6.5 修改生成属性
6.6 选择动态库dl
6.7 复制dll到项目根路径
复制到项目根路径后,就可以加载使用了,这样就完成基本的Java
调用C
。
package com.loveq;
public class Main {
public native static String sayHelloFromC();
public static void main(String[] args) {
System.out.println(sayHelloFromC());
}
static {
//加载动态库
System.loadLibrary("Project5");
}
}
JNI 详解
Java 调用C的方法
package com.loveq;
public class Main {
//静态方法
public native static String sayHelloFromC();
public native String sayHelloFromC2();
public static void main(String[] args) {
System.out.println(sayHelloFromC());
}
static {
System.loadLibrary("Project5");
}
}
注意这里的第一个方法是静态的,第二个是普通的native
方法。我们看一下通过javah
上传头文件,这两个方法会有什么不同。
/*
* Class: com_loveq_Main
* Method: sayHelloFromC
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_loveq_Main_sayHelloFromC
(JNIEnv *, jclass);
/*
* Class: com_loveq_Main
* Method: sayHelloFromC2
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_loveq_Main_sayHelloFromC2
(JNIEnv *, jobject);
通过上面可以看到,主要是第二个参数不同,静态方法第二个参数是jclass
,这里代表Main.class
,而第二个方法jobject
代表类对象的实例,这里代表Main main = new Main()
,中的main
。
总结:每个native函数,都至少有两个参数(JNIEnv*
,jclass
或者jobject
)
- 当
native
方法为静态方法时:
jclass
代表native
方法所属类的class对象 - 当
native
方法为非静态方法时:
jobject
代表native
方法所属的对象
C 调用 Java
要实现C
调用 Java
,必须先了解Java
基本数据类型与JNI
数据类型的映射及其签名。这在C
调用 Java
时会用到。
Java类型 | JNI类型 | 签名 |
---|---|---|
boolean | jboolean | Z |
byte | jbyte | B |
char | jchar | C |
short | jshort | S |
int | jint | I |
long | jlong | L |
float | jfloat | F |
double | jdouble | D |
void | void | V |
String | jstring | L +(以/分割包的完整类型)+; 如String 的签名是Ljava/lang/String; |
object | jobject | 见上面 |
object | jobjectArray | 以[ 开头,在加上数组元素类型的签名,如:int[] 的签名是[I ,在比如int[][] 的签名是[[I ,object 的签名是[L/java/lang/Object; |
byte[] | jByteArray | 见上面 |
C访问Java的成员
java代码
public class Main {
public String name = "rc";
public static void main(String[] args) {
Main main = new Main();
System.out.println("c修改之前的name :" + main.name);
main.getField();
System.out.println("c修改之后的name :" + main.name);
}
static {
System.loadLibrary("Project5");
}
}
c代码
JNIEXPORT jstring JNICALL Java_com_loveq_Main_getField
(JNIEnv * env, jobject jobj){
//获取class对象
jclass cls = (*env)->GetObjectClass(env, jobj);
//获取属性id,第三个参数是属性名字,第四个参数是该属性的签名,因为在Java中name的类型是String,所以签名是Ljava/lang/String;
jfieldID fid =(*env)->GetFieldID(env, cls, "name", "Ljava/lang/String;");
//因为String type是Object 所以这里使用GetObjectField方法
//GetField
jstring jstr = (*env)->GetObjectField(env, jobj, fid);
//打印地址
printf("jstr:%#x \n", &jstr);
//jstring 转 c字符串
char *c_str = (*env)->GetStringUTFChars(env, jstr, JNI_FALSE);
char text[20] = "Hello ";
//拼接字符串
strcat(text, c_str);
//c字符串 转 jstring
jstring new_jstr = (*env)->NewStringUTF(env, text);
//修改属性
//SetField
(*env)->SetObjectField(env, jobj, fid, new_jstr);
//释放资源
(*env)->ReleaseStringUTFChars(env, jstr, c_str);
return new_jstr;
}
C访问Java静态成员
java代码
public class Main {
public String name = "rc";
public static int password = 123456;
public native String getField();
public native void getStaticField();
public native static String sayHelloFromC();
public native String sayHelloFromC2();
public static void main(String[] args) {
System.out.println(sayHelloFromC());
Main main = new Main();
System.out.println("c修改之前的password :" + Main.password);
main.getStaticField();
System.out.println("c修改之后的password :" + Main.password);
}
static {
System.loadLibrary("Project5");
}
}
c代码
JNIEXPORT void JNICALL Java_com_loveq_Main_getStaticField
(JNIEnv * env, jobject object){
//获取class
jclass jcls = (*env)->GetObjectClass(env, object);
//jfieldID
jfieldID fid = (*env)->GetStaticFieldID(env, jcls, "password", "I");
jint password = (*env)->GetStaticIntField(env, jcls, fid);
password = 123;
(*env)->SetStaticIntField(env, jcls, fid, password);
}
C访问Java代码
Java代码
public class Main {
public int getRandomNumber(int max) {
System.out.println("getRandomNumber method call");
return new Random().nextInt(max);
}
public native int accessMethod();
public static void main(String[] args) {
Main main = new Main();
main.accessMethod();
}
static {
System.loadLibrary("Project5");
}
}
C代码
JNIEXPORT jint JNICALL Java_com_loveq_Main_accessMethod
(JNIEnv * env, jobject jobj){
//1.获取class
jclass jcls = (*env)->GetObjectClass(env, jobj);
//第一个参数 env 第二个参数class 第三个参数方法名,第四个方法的签名
jmethodID jmid =(*env)->GetMethodID(env, jcls, "getRandomNumber", "(I)I");
//调用方法,最后一个是方法参数
jint random = (*env)->CallIntMethod(env, jobj, jmid, 200);
printf("random num %d", random);
}
查看方法签名的步骤,1.
javac Main.java
, 2.javap -s Main.class