Java Native Interface(Java本地接口) 简称 JNI,是一种编程框架,使得 JVM 中的 Java程序 可以调用本地应用/或库,也可以被其他程序调用。其过程可以不负责任的理解成 Java 的反射,因为代码逻辑和反射调用很像。
源码
本文所有涉及的所有源码:https://github.com/gavinliu/Study-JNI
平台 | 编译环境 |
---|---|
macOS | gcc |
HelloStringFromC
在java中编写native方法
public class HelloStringFromC {
public static native String helloStringFromC();
public static void main(String[] args) {
}
}
生成jni头文件
javac -jni HelloStringFromC
将会生成 HelloStringFromC.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include
/* Header for class HelloStringFromC */
#ifndef _Included_HelloStringFromC
#define _Included_HelloStringFromC
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: HelloStringFromC
* Method: helloStringFromC
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_HelloStringFromC_helloStringFromC
(JNIEnv *, jclass);
#ifdef __cplusplus
}
#endif
#endif
编写native代码
#include <stdio.h>
#include <stdlib.h>
#include "HelloStringFromC.h"
JNIEXPORT jstring JNICALL Java_HelloStringFromC_helloStringFromC(JNIEnv* env, jclass cls) {
return (*env)->NewStringUTF(env, "Hello");
}
编译so库
gcc HelloStringFromC.c -fPIC -shared -o libhelloString.so
java调用
public class HelloStringFromC {
public static native String helloStringFromC();
public static void main(String[] args) {
System.out.println(helloStringFromC());
}
static {
System.load("/Users/gavin/Workspace/Study-JNI/01.HelloStringFromC/libhelloString.so");
}
}
> Hello
数据类型
基本数据类型
Java 数据类型 | JNI 数据类型 | C 数据类型 |
---|---|---|
blean | jboolean | unsigned char |
byte | jbyte | signed char |
char | jchar | unsigned short |
short | jshort | short |
int | jint | int |
long | jlong | long long |
float | jfloat | float |
double | jdouble | double |
引用类型
Java 数据类型 | JNI 数据类型 |
---|---|
Object | jobject |
Class | jclass |
String | jstring |
array[] | jarray |
数组类型
Java 数据类型 | JNI 数据类型 |
---|---|
boolean[] | jbooleanArray |
byte[] | jbyteArray |
char[] | jcharArray |
short[] | jshortArray |
int[] | jintArray |
long[] | jlongArray |
float[] | jfloatArray |
double | jdoubleArray |
objcet | jobjectArray |
JNIEnv
struct JNINativeInterface_;
typedef const struct JNINativeInterface_ *JNIEnv;
JNIEnv
其实是 JNINativeInterface_
的指针别名,这个结构是C和Java交互的关键类,提供了C数据类型和JNI数据类型的互相转换和C调用Java的相关方法等。
Java 调 C
Java调C的套路:
-
在Java源码中编写native方法
-
使用
javah
生成 jni 头文件 -
实现
jni
方法 -
编译生成 so 动态链接库
-
在Java中使用动态链接库
静态调用
public static native String getFullName(String firstName);
// ==>
JNIEXPORT jstring JNICALL Java_CallC_getFullName
(JNIEnv *, jclass, jstring);
静态调用生成的JNI方法,第二个参数为 jclass,代表Java中 CallC.class 这个对象。
对象调用
public native String getFirstName(String[] fullName);
// ==>
JNIEXPORT jstring JNICALL Java_CallC_getFirstName
(JNIEnv *, jobject, jobjectArray);
对象调用生成的JNI方法,第二个参数为 jobject,代表Java中 CallC 这个对象。
示例
-
Java 调 C 传入一个字符串,返回拼接后的新字符串
-
Java 调 C 传入一个字符串数组,返回数组第一个元素
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "CallC.h"
JNIEXPORT jstring JNICALL Java_CallC_getFullName
(JNIEnv* env, jclass jcl, jstring firstName) {
// jstring -> char*
const char* fn = (*env)->GetStringUTFChars(env, firstName, JNI_FALSE);
const char* ln = " Liu";
char src[50], dest[50];
strcpy(src, ln);
strcpy(dest, fn);
// C的拼接字符串
strcat(dest, src);
return (*env)->NewStringUTF(env, dest);
}
JNIEXPORT jstring JNICALL Java_CallC_getFirstName
(JNIEnv* env, jobject jobj, jobjectArray fullName) {
// 从 对象数组中获取元素
jstring elm = (jstring) (*env)->GetObjectArrayElement(env, fullName, 0);
return elm;
}
public class CallC {
public static native String getFullName(String firstName);
public native String getFirstName(String[] fullName);
public static void main(String[] args) {
System.out.println(getFullName("Gavin"));
String[] fullName = {"Gavin", "Liu"};
System.out.println(new CallC().getFirstName(fullName));
}
static {
System.load("/Users/gavin/Workspace/Study-JNI/02.Java2C/libcallC.so");
}
}
> Gavin Liu
> Gavin
C 调 Java
C调Java的套路:
-
获取 FieldID 或者 MethedID
-
通过 ID 可以 GET|SET Field,和调用 Methed
其中第一步需要用到 Field 和 Methed 签名,可以用 javap
命令获取:
javap -s -p CallJava
Compiled from "CallJava.java"
public class CallJava {
public java.lang.String firstName;
descriptor: Ljava/lang/String;
public CallJava();
descriptor: ()V
public native java.lang.String changeName();
descriptor: ()Ljava/lang/String;
public native java.lang.String sayHiFromC();
descriptor: ()Ljava/lang/String;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
public void sayHiFromJava();
descriptor: ()V
static {};
descriptor: ()V
}
示例
-
C 改变 Java 中的成员变量
-
C 调用 Java 方法
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "CallJava.h"
JNIEXPORT jstring JNICALL Java_CallJava_changeName
(JNIEnv * env, jobject obj) {
// 获取 jclass
jclass cls = (*env)->GetObjectClass(env, obj);
// 获取 fieldID
jfieldID firstNameID = (*env)->GetFieldID(env, cls, "firstName", "Ljava/lang/String;");
// 获取 属性
jstring firstName = (*env)->GetObjectField(env, obj, firstNameID);
// 拼接新字符串
const char* str = (*env)->GetStringUTFChars(env, firstName, JNI_FALSE);
char src[50] = "super ";
strcat(src, str);
// 设置 属性
jstring newName = (*env)->NewStringUTF(env, src);
(*env)->SetObjectField(env, obj, firstNameID, newName);
return newName;
}
JNIEXPORT void JNICALL Java_CallJava_sayHiFromC
(JNIEnv * env, jobject obj) {
// 获取 jclass
jclass cls = (*env)->GetObjectClass(env, obj);
// 获取 methodID
jmethodID methodID = (*env)->GetMethodID(env, cls, "sayHiFromJava", "()V");
// 调用方法
(*env)->CallVoidMethod(env, obj, methodID);
printf("%s\n", "sayHiFromC");
}
public class CallJava {
static {
System.load("/Users/gavin/Workspace/Study-JNI/03.C2Java/libcallJava.so");
}
public native String changeName();
public native void sayHiFromC();
public String firstName = "Gavin";
public static void main(String[] args) {
CallJava c = new CallJava();
System.out.println("c.firstName \t" + c.firstName);
System.out.println("c.changeName() \t" + c.changeName());
System.out.println("c.firstName \t" + c.firstName);
c.sayHiFromC();
}
public void sayHiFromJava() {
System.out.println("sayHiFromJava");
}
}
c.firstName Gavin
c.changeName() super Gavin
c.firstName super Gavin
sayHiFromJava
sayHiFromC
其他
JNIEnv
中还有很多方法,就不作一一介绍,掌握了其套路,代码就很容易写了,比如说调用方法是 CallVoidMethod
,调用静态方法就是 CallStaticVoidMethod
。
C & C++ 的一些不同
JNIEnv 对象不一样
在C中: JNIEnv*
是一个二级指针,所以是使用 (*env )->
在C++中: JNIEnv*
是一个一级指针,则是使用 env->
引用类型不一样
在C中:是使用结构体实现
在C++中:是使用对象实现
官方文档
http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/jniTOC.html