一、开发步骤:
- 编写带有
native
声明的方法的Java
类 - 使用
javac
命令编译编写的Java
类得到class
文件,如:javac NativeTest.java
- 使用
javah -jni ****
来生成后缀名为.h
的头文件,如:javah -classpath D:\Study\idea\IdeaProjects\demo\target\classes -jni bytecode.NativeTest
- 使用其他语言(
C
、C++
)实现本地方法 - 将本地方法编写的文件生成动态链接库(
windows
下是.dll
文件,Linux
下是.so
文件)
要特别注意第 2 步,使用 javah
生成 .h
文件时,正确格式为:javah -classpath .class文件所在目录(不含包) -jni 完整类名
,这个 “ .class
文件所在目录(不含包) ” 既可以用绝对目录也可以用相对目录,.h
文件生成后位于执行 javah
命令时所在的目录
二、示例:
- 编写含有
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));
}
}
-
编译
Java
源文件生成.class
文件NativeTest.class
-
生成
.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++
程序的时候实现这些方法。
- 实现本地方法
新建一个CLion
项目NativeTest
,将前面生成的bytecode_NativeTest.h
拷贝到项目中,然后将 Java 目录下的jni.h
和jni_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
因为cygwin
下 gnu
是不带 __int64
这个宏的。
所以需要在 jni_md.h
修改 __int64
// 原文件
typedef __int64 jlong;
// 修改为
#ifdef __GNUC__
typedef long long jlong;
#else
typedef __int64 jlong;
#endif
- 生成动态链接库
将NativeTestImpl.cpp
源文件编译为NativeTest.dll
动态链接库文件,并需要链接两个文件:
jni.h
在JDK
的include/
目录下,jni_md.h
在JDK
的include/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
方法了。
网友评论