Java平台有一个和本地C代码进行互操作的API,称为Java本地接口(JNI)
求助本地代码是有缺陷的。如果应用的某个部分使用其他语言编写的,那么就必须为需要支持的每个平台提供一个单独的本地类库。
建议在有必要的时候才使用本地代码:
- 你的应用需要访问系统的各个特性和设备,这些特性和设备通过Java平台是无法访问的。
- 你已经有了大量的测试过和调试过的用另一种语言编写的代码,并且知道如何将其导出到所有的目标平台上。
- 通过基准测试,你已经发现所编写的Java代码比用其它语言编写的等价代码要慢得多。
JNI 并不支持 Java类 和C++类之间的任何映射机制。
一、Java程序调用C函数
native
关键字 提醒编译器该方法将在外部定义。
public class HelloNative {
// java 调用 native
public static native void greeting();
static {
// System.loadLibrary("HelloNative");
System.load("/Users/jxf/workspace/Java/project/jni/out/production/jni/libHelloNative.so");
}
}
通过工具 javah
生成对应的本地函数名(前提是HelloNative类已经编译,HelloNative.class已生成)
javah com.test.jni.HelloNative
注意要有类的完整包名。这条命令生成了 com_test_jni_HelloNative.h
文件(实际包目录跟具体包名相关),具体代码:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_test_jni_HelloNative */
#ifndef _Included_com_test_jni_HelloNative
#define _Included_com_test_jni_HelloNative
# 你可以使用C++实现代码,然而,那样你必须将实现本地方法的函数声明为 extern "C"(阻止C++编译器生成C++特有的代码)
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_test_jni_HelloNative
* Method: greeting
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_test_jni_HelloNative_greeting
(JNIEnv *, jclass);
#ifdef __cplusplus
}
#endif
#endif
本地代码的函数命名必须遵守Java运行环境预期的那样才行:
1、使用完整的Java方法名。比如 com.testj.ni.HelloNative.greeting
2、用 下划线 替换所有的句话,并且加上 Java_ 前缀。比如:Java_com_test_jni_HelloNative_greeting
如果你重载本地方法,必须在名称后面附加两个下划线,后面再加上已编码的参数类型。例如,如果有一个本地方法greeting 和 另一个本地方法 greeting(int repeat),那么第一个称为 Java_com_test_jni_HelloNative_greeting__,第二个称为Java_com_test_jni_HelloNative_greeting__I
然后,我们手动创建源代码文件 com_test_jni_HelloNative.c
,代码如下:
#include "com_test_jni_HelloNative.h"
#include <stdio.h>
//实现具体方法:
JNIEXPORT void JNICALL Java_com_test_jni_HelloNative_greeting(JNIEnv *env, jclass cl)
{
printf("Hello Native World\n");
}
之后,将本地C代码编译到一个动态装载库中,具体方法依赖于编译器,Mac 下使用Gnu C 编译器,使用如下命令:
gcc -fPIC -I /Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/include/ -I /Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/include/darwin -shared -o libHelloNative.so com_test_jni_HelloNative.c
-fPIC -- Generate position-independent code if possible (large mode)
-I <dir> Add directory to include search path
-o <file> Write output to <file>
/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/include/
/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/include/darwin 这两个路径是生成so库时需要依赖的我本机的路径。
/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/include/ 我本机的目录截图:
我们生成的 .h
头文件依赖于 jni.h
,jni.h
又依赖于jni_md.h
编译时,如果没有写 include 路径,就会出现如下错误:
In file included from com_test_jni_HelloNative.c:2:
In file included from ./com_test_jni_HelloNative.h:2:
/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/include/jni.h:45:10: fatal error:
'jni_md.h' file not found
#include "jni_md.h"
^~~~~~~~~~
1 error generated.
如果编译过程顺利,会生成 libHelloNative.so
最后,我们要在程序中添加一个对 System load
、loadLibrary
方法的调用。为了确保虚拟机在第一次使用该类之前就会装载这个库,需要使用静态初始化代码块。本例中我们使用 load
方法
load 加载要求传入的路径为
an absolute path name
loadLibrary 要求传入的路径为 library name。装载指定名字的库,该库位于库搜索路径中,依赖于操作系统。
使用:
public class Main {
public static void main(String[] args) {
HelloNative.greeting();
}
}
结果:
Hello Native World
二、数值参数 和 返回值
java:
public static native int getNumber(int num);
c:
JNIEXPORT jint JNICALL Java_com_test_jni_HelloNative_getNumber(JNIEnv *env, jclass cl, jint param)
{
jint res = param * 2;
return res;
}
使用:
System.out.println("jni method: " + HelloNative.getNumber(10));
结果:
jni method: 20
当在C 和 Java之间传递数字时,我们应该知道它们 彼此之间的对应类型。例如,尽管C拥有int 和 long 的数据类型,但是它们的实现确实取决于平台的。在一些平台上,int类型是16位的,在另外一些平台是32位的。当然,在Java平台上int类型总是32位的整数。基于这个原因,Java本地接口定义了jint、jlong 等类型。
java语言 | C语言 | 字节 |
---|---|---|
boolean | jboolean | 1 |
byte | jbyte | 1 |
char | jchar | 2 |
short | jshort | 2 |
int | jint | 4 |
long | jlong | 8 |
float | jfloat | 4 |
double | jdouble | 8 |
三、字符串
java:
public static native String getString();
c:
JNIEXPORT jstring JNICALL Java_com_test_jni_HelloNative_getString(JNIEnv *env, jclass cl)
{
jstring jstr;
char greeting[] = "nativeString\n";
jstr = (*env)->NewStringUTF(env, greeting);
return jstr;
}
使用:
System.out.println(HelloNative.getString());
结果:
nativeString
所有针对JNI函数的调用都使用到了env指针,该指针是每一个本地方法的第一个参数,env指针是函数指针表的指针。
四、native 访问 java对象的属性
java:
private int flag = 0;
// native 访问 java实例的属性
public native void setFlag(int value);
public int getFlag() {
return flag;
}
c:
JNIEXPORT void JNICALL Java_com_test_jni_HelloNative_setFlag(JNIEnv *env, jobject this_obj, jint value)
{
//get the class
jclass cls = (*env)->GetObjectClass(env, this_obj);
// get the field ID
jfieldID id_flag = (*env)->GetFieldID(env, cls, "flag", "I");
//set the field value
(*env)->SetIntField(env, this_obj, id_flag, value);
}
使用:
HelloNative obj = new HelloNative();
//访问实例域
obj.setFlag(100);
System.out.println("flag: " + obj.getFlag());
结果:
flag: 100
五、native 访问 java对象的静态属性
java:
private static float num = 10;
public native void setNum(float value);
public float getNum(){
return num;
}
c:
JNIEXPORT void JNICALL Java_com_test_jni_HelloNative_setNum(JNIEnv *env, jobject this_obj, jfloat value)
{
jclass cls = (*env)->GetObjectClass(env, this_obj);
jfieldID id_num = (*env)->GetStaticFieldID(env, cls, "num", "F");
(*env)->SetStaticFloatField(env, cls, id_num, value);
}
使用:
obj.setNum(1110);
System.out.println("static num: " + obj.getNum());
结果:
static num: 1110.0
六、native 调用 java 方法
java:
public native static void callJavaOtherMethod();
c:
JNIEXPORT void JNICALL Java_com_test_jni_HelloNative_callJavaOtherMethod(JNIEnv *env, jclass cl)
{
jmethodID id_greeting = (*env)->GetStaticMethodID(env, cl, "greeting", "()V");
(*env)->CallStaticVoidMethod(env, cl, id_greeting);
}
调用:
HelloNative.callJavaOtherMethod();
结果:
Hello Native World
为了访问实例域和调用Java中定义的方法,必须学习“编入”数据类型的名称和方法签名的规则。
类型简写 | java类型 |
---|---|
B | byte |
C | char |
D | double |
F | float |
I | int |
J | long |
S | short |
V | void |
Z | boolean |
Lclassname; | 类的类型 |
描述数组用 [
例如:[Ljava/lang/String;
一个float[][]可描述为:[[F
要建立一个方法的完整签名,需要把括号内的参数类型都列出来,然后列出返回值类型。例如一个接受两个整形参数并返回一个整数的方法编码为:(II)I
七、native 抛出一个 Exception
java:
public native static void throwAException();
c:
JNIEXPORT void JNICALL Java_com_test_jni_HelloNative_throwAException(JNIEnv *env, jclass cls)
{
(*env)->ThrowNew(env, (*env)->FindClass(env, "java/lang/NullPointerException"), "a exception from native code");
return;
}
使用:
HelloNative.throwAException();
结果:
Exception in thread "main" java.lang.NullPointerException: a exception from native code
at com.test.jni.HelloNative.throwAException(Native Method)
at com.test.Main.main(Main.java:29)
其它:
https://www.cnblogs.com/chaoren399/p/6232467.html
https://www.cnblogs.com/EasonJim/p/9445282.html
https://www.cnblogs.com/ylz8401/p/9605498.html
网友评论