JNI

作者: asdf____ | 来源:发表于2020-04-14 07:54 被阅读0次

一、开发步骤:

  1. 编写带有 native 声明的方法的 Java
  2. 使用 javac 命令编译编写的 Java 类得到 class文件,如:javac NativeTest.java
  3. 使用 javah -jni **** 来生成后缀名为 .h 的头文件,如:javah -classpath D:\Study\idea\IdeaProjects\demo\target\classes -jni bytecode.NativeTest
  4. 使用其他语言(CC++)实现本地方法
  5. 将本地方法编写的文件生成动态链接库(windows 下是 .dll文件,Linux 下是 .so 文件)

要特别注意第 2 步,使用 javah 生成 .h 文件时,正确格式为:javah -classpath .class文件所在目录(不含包) -jni 完整类名,这个 “ .class 文件所在目录(不含包) ” 既可以用绝对目录也可以用相对目录,.h 文件生成后位于执行 javah 命令时所在的目录

二、示例:

  1. 编写含有 native 方法的 Java 源文件 NativeTest.java
// Java 程序中调用 native 方法前在 VM Options 中设置 .dll 文件的绝对路径
-Djava.library.path=D:\Study\idea\IdeaProjects\demo\src\main\java\bytecode
package bytecode;

public class NativeTest {

    public native void say();

    public native String sayWithMsg(String msg);

    // 静态 native 方法
    public static native int sayWithMsgAndNum(String msg, int num);

    static {
        // 参数值是动态链接库名称,不要求必须和类名一致
        System.loadLibrary("NativeTest");
    }

    public static void main(String[] args) {
        NativeTest nativeTest = new NativeTest();
        nativeTest.say();
        System.out.println(nativeTest.sayWithMsg("java"));
        System.out.println(sayWithMsgAndNum("java", 100));
    }
}
  1. 编译 Java 源文件生成 .class 文件 NativeTest.class

  2. 生成 .h 头文件 bytecode_NativeTest.h

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

#ifndef _Included_bytecode_NativeTest
#define _Included_bytecode_NativeTest
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     bytecode_NativeTest
 * Method:    say
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_bytecode_NativeTest_say
  (JNIEnv *, jobject);

/*
 * Class:     bytecode_NativeTest
 * Method:    sayWithMsg
 * Signature: (Ljava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_bytecode_NativeTest_sayWithMsg
  (JNIEnv *, jobject, jstring);

/*
 * Class:     bytecode_NativeTest
 * Method:    sayWithMsgAndNum
 * Signature: (Ljava/lang/String;I)I
 */
JNIEXPORT jint JNICALL Java_bytecode_NativeTest_sayWithMsgAndNum
  (JNIEnv *, jclass, jstring, jint);

#ifdef __cplusplus
}
#endif
#endif

这个 .h 文件可以这样理解:
其中最关键的就是定义了几个 Java_~ 方法,它们和 Java 代码中的方法一一对应,可以把 .h 文件类比 Java 里的接口,只定义不实现,然后我们在本地方法里面实现这些方法,也就是说我们在编写 C/C++ 程序的时候实现这些方法。

  1. 实现本地方法
    新建一个 CLion 项目 NativeTest,将前面生成的 bytecode_NativeTest.h 拷贝到项目中,然后将 Java 目录下的 jni.hjni_md.h 文件拷贝至 NativeTest 项目里。
    C:\Program Files\Java\jdk1.8.0_231\include\jni.h
    C:\Program Files\Java\jdk1.8.0_231\include\win32\jni_md.h
    拷贝后将 bytecode_NativeTest.h 文件中的 #include <jni.h> 改成 #include "jni.h"
    创建 NativeTestImpl.c 实现 JNI 方法
#include "bytecode_NativeTest.h"

JNIEXPORT void JNICALL Java_bytecode_NativeTest_say(JNIEnv *jniEnv, jobject obj) {
    printf("a\n");
}

JNIEXPORT jstring JNICALL Java_bytecode_NativeTest_sayWithMsg(JNIEnv *jniEnv, jobject obj, jstring msg) {
    printf("b\n");
    return msg;
}

JNIEXPORT jint JNICALL Java_bytecode_NativeTest_sayWithMsgAndNum(JNIEnv *jniEnv, jobject obj, jstring msg, jint num) {
    printf("c\n");
    return num + 500;
}

注意直接编译会有问题:
cygwin jni 报错 '__int64' does not name a type error: 'jlong' does not name a type
因为cygwingnu 是不带 __int64 这个宏的。
所以需要在 jni_md.h 修改 __int64

// 原文件
typedef __int64 jlong;
// 修改为
#ifdef __GNUC__
typedef long long jlong;
#else
typedef __int64 jlong;
#endif
  1. 生成动态链接库
    NativeTestImpl.cpp 源文件编译为 NativeTest.dll 动态链接库文件,并需要链接两个文件:
    jni.hJDKinclude/ 目录下,jni_md.hJDKinclude/win32/ 目录下
    如下-I 参数是指定链接的路径:
// Linux 下使用
gcc -shared -I"C:\Program Files\Java\jdk1.8.0_231\include" -I"C:\Program Files\Java\jdk1.8.0_231\include\win32" NativeTestImpl.c -o NativeTest.dll
// Windows 下用这个,否则调用 native 方法时会报错
x86_64-w64-mingw32-gcc -shared -I"C:\Program Files\Java\jdk1.8.0_231\include" -I"C:\Program Files\Java\jdk1.8.0_231\include\win32" NativeTestImpl.c -o NativeTest.dll

然后将生成的 NativeTest.dll 动态链接库拷贝到和 NativeTest.java 文件相同的包下,就可以在 Java 程序中正确调用 native 方法了。

相关文章

网友评论

      本文标题:JNI

      本文链接:https://www.haomeiwen.com/subject/evbamhtx.html