JNI例子和JNI参数说明

Java是一种跨平台编程语言,JVM虚拟机封装了Java程序与底层硬件的交互逻辑,只要操作系统安装了Java运行环境,Java 代码就可以一次编译到处运行。Java的跨平台是优点但是有时候也会打破这种优点,比如当Java想要调用操作系统特有的功能。
Java的跨语言调用通过JNI(Java Native Interface)来完成。

JNI 例子

我们以一个Java调用c的例子来了解JNI的调用流程。实现Java调用c需要如下步骤:

  1. 编写Java Native接口并对代码进行编译。
  2. 根据步骤2Java代码生成c头文件,头文件中包含接口的声明。
  3. 编写c代码,代码中引入步骤2生成的头文件。
  4. 将步骤3的代码编译成为动态链接库。
  5. Java代码中调用动态链接库。

1. 编写Java Native代码

Java代码的绝对路径:/Users/user/Documents/workSpace/microservice/doc/demo/demo-code/src/main/java/jni

Java代码中声明native接口。

//包名
package jni;

/**
 * Datetime: 2023/5/23
 * Author: YanYong
 */
public class JniTest {
		//本地方法接口
    public native void helloWorld(String s);
}

JniTest.java编译成class文件,生成的class文件与JniTest.java在同一个目录下。如下是编译指令:

javac /Users/user/Documents/workSpace/microservice/doc/demo/demo-code/src/main/java/jni/JniTest.java

2. 生成头文件

进入的/Users/user/Documents/workSpace/microservice/doc/demo/demo-code/src/main/java目录下,然后执行java -classath . -jni 包名.类名称指令,如下例子:

javah -classpath . -jni jni.JniTest

生产的头文件的名称规则是:包名_类名称.h。如下是JniTest生成的jni_JniTest.h文件内容:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class jni_JniTest */

#ifndef _Included_jni_JniTest
#define _Included_jni_JniTest
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     jni_JniTest
 * Method:    helloWorld
 * Signature: (Ljava/lang/String;)V
 */
JNIEXPORT void JNICALL Java_jni_JniTest_helloWorld
  (JNIEnv *, jobject, jstring);

#ifdef __cplusplus
}
#endif
#endif

3. 编写c代码

前面的流程只是生成了c代码的头文件,接下来就是引入jni_JniTest.h头文件,并实现相关的方法:

#include <stdio.h>
#include "jni_JniTest.h"

JNIEXPORT void JNICALL Java_jni_JniTest_helloWorld
(JNIEnv *env, jobject thisObject, jstring str){
    const char *strs = (*env)->GetStringUTFChars(env,str,0);
    printf("%s\n",strs);
    return;
}

如上Java_jni_JniTest_helloWorld是接口helloWorld的本地方法实现,它的名称命名规则是Java_包名_Java类名_native方法名。在Java_jni_JniTest_helloWorld方法上有三个参数:

  • envJNI函数指针的集合,如果在c代码中想要调用Java的特性,可以通过env中的函数指针来实现。
  • thisObjectthis对象。如果是静态方法,那么该方法的第二个参数是jclass
  • str:方法参数。
4. 生成动态库

c代码完成后,通过gcc 命令将其编译成为动态链接库。不同的环境,生成的文件的后缀名不同。

  • mac环境:生成dylib结尾的文件
gcc -I$JAVA_HOME/include -I$JAVA_HOME/include/linux -fPIC -o demoJni.dylib -shared demo1.c
  • linux环境:生成的是.so文件。
gcc -I$JAVA_HOME/include -I$JAVA_HOME/include/linux -fPIC -o demoJni.so -shared demo1.c

注意这里几个gcc的选项,-shared是说明要生成动态库,而两个 -I的选项,是因为我们用到<jni.h>相关的头文件,放在<jdk>/include <jdk>/include/linux两个目录下。

5.Jni调用

System.load 加载动态链接库,参数是绝对地址。

public static void main(String[] args) {
    try {
        System.load("/Users/user/Documents/cspace/MyFirstProject/MyFirstProject/demoJni.dylib");
    }catch (Exception e){
        e.printStackTrace();
        System.exit(1);
    }

    new JniTest().helloWorld("hello world ");
}

执行结果:hello world.

JNI参数说明

Java 基本类型与C映射

JNI会将Java中的基本类型和引用类型与c映射出一套可供c代码调用的数据结构。

  • 基本类型

    Java基本类型c数据结构
    booleanjboolean
    bytejbyte
    charjchar
    shortjshort
    intjint
    longjlong
    floatjfloat
    doublejdouble
    voidvoid
  • 引用类型

    
    jobject
    |- jclass (java.lang.Class)
    |- jstring (java.lang.String)
    |- jthrowable (java.lang.Throwable)
    |- jarray (arrays)
       |- jobjectArray (object arrays)
       |- jbooleanArray (boolean arrays)
       |- jbyteArray (byte arrays)
       |- jcharArray (char arrays)
       |- jshortArray (short arrays)
       |- jintArray (int arrays)
       |- jlongArray (long arrays)
       |- jfloatArray (float arrays)
       |- jdoubleArray (double arrays)
    

JNIEnv参数

C代码中可能需要获取Java代码传递过来的参数或者Java对象中的全局变量,这些功能通过JNI函数来实现。JVMJNI函数的函数指针聚合到了JNIEnv里。

JNIEnv是一个线程私有的数据结构,JVM会为每个线程创建一个JNIEnv对象,并且c代码不能将当前的JNIEnv分享给其他线程。如下是JNIEnv使用例子:

//java 代码
public native void helloWorld(String s);
int i = 0xDEADBEEF;

改造一下前面的c例子代码,通过JNIEnv里面的函数指针去获取 Java对象中的变量i

#include <stdio.h>
#include "jni_JniTest.h"

JNIEXPORT void JNICALL Java_jni_JniTest_helloWorld
(JNIEnv *env, jobject thisObject, jstring str){
    const char *strs = (*env)->GetStringUTFChars(env,str,0);
    printf("%s\n",strs);
    //获取class
    jclass cls = (*env)->GetObjectClass(env, thisObject);
  	//获取class中变量i 的 fieldID
    jfieldID fieldID = (*env)->GetFieldID(env, cls, "i", "I");
  	//通过fieldID获取变量i值
    jint value = (*env)->GetIntField(env, thisObject, fieldID);
    printf("Hello, World 0x%x\n", value);
    return;
}

JNI调用

public static void main(String[] args) {
    try {
        System.load("/Users/user/Documents/cspace/MyFirstProject/MyFirstProject/demoJni.dylib");
    }catch (Exception e){
        e.printStackTrace();
        System.exit(1);
    }

    new JniTest().helloWorld("begin");
}

输出:

begin
Hello, World 0xdeadbeef

从结果可以看出,c代码成功获取到了java对象中的变量i。更多关于函数指针请参考:https://docs.oracle.com/en/java/javase/11/docs/specs/jni/functions.html

附录

提示'jni_md.h' file not found错误,拷贝一下缺少的文件。

sudo cp /Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/include/darwin/jni_md.h /Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/include
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值