一、JNI书写步骤
JNI是Java Native Interface的缩写,通过使用 Java本地接口书写程序,可以确保代码在不同的平台上方便移植。 从Java1.1开始,JNI标准成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互。JNI一开始是为了本地已编译语言,尤其是C和C++而设计的,但是它并不妨碍你使用其他编程语言,只要调用约定受支持就可以了。使用java与本地已编译的代码交互,通常会丧失平台可移植性。但是,有些情况下这样做是可以接受的,甚至是必须的。例如,使用一些旧的库,与硬件、操作系统进行交互,或者为了提高程序的性能。JNI标准至少要保证本地代码能工作在任何Java 虚拟机环境。
1.1 编写Java程序
public class TestJNI{
//所有native关键词修饰的都是对本地的声明
public native void get();
}
1.2 编译Java程序
javac TestJNI.java
1.3 生成扩展名为h的头文件
javah命令用于生成头文件。新版jdk-10.0.2已经移除了javah命令工具,使用javac HelloWorld.java -h JniH 代替javah HelloWorld 命令生成扩展名为h的头文件。
在下面这行命令中.表示头文件生成的位置,mine.TestJNI表示java的类路径
注意:我们执行javah一定要在mine(根据自己的java文件位置决定)的父级别目录下.
javah -classpath . -jni mine.TestJNI
头文件内容:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class mine_TestJNI */
#ifndef _Included_mine_TestJNI
#define _Included_mine_TestJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: mine_TestJNI
* Method: get
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_mine_TestJNI_get
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
1.4 编写本地方法
创建后缀为
.c
或.cpp
的文件,实现头文件里面的方法,这里我使用的工具是JetBrains CLion 2019.2 x64:
第一步:
创建一个新的工程
第二步:
把头文件mine_TestJNI.h
、jni.h
、jni_md.h
拷贝到TestJNI工程下。删除testJNI.cpp已经testJNI.h
主意:jni.h
文件在你的JDK目录的include目录下,jni_md.h
在include的win32目录下。
第三步:
创建mine_TestJNI.cpp
文件实现mine_TestJNI.h
头文件里面的方法。
mine_TestJNI.cpp
文件内容:
#include "mine_TestJNI.h"
#include <iostream>
#include <stdio.h>
JNIEXPORT void JNICALL Java_mine_TestJNI_get (JNIEnv *, jobject) {
printf("this is C++ print");
}
1.5 生成动态库
第一步编辑CMake文件:
cmake_minimum_required(VERSION 3.12)
project(TestJNI)
set(CMAKE_CXX_STANDARD 14)
add_library(TestJNI SHARED mine_TestJNI.cpp mine_TestJNI.h jni.h jni_md.h)
第二步运行生成动态库
注意:如果出现以下错误,修改对应位置的 #include <jni.h>
为 #include "jni.h"
即可。
1.6 运行程序
public class TestJNI {
public native void get();
static {
System.load("D:\\Jumper\\upload\\libTestJNI.dll");//载入本地库
}
public static void main(String[] args) {
new TestJNI().get();
}
}
二、类型对应表
Java 类型 | 本地 C 类型 | 实际表示的 C 类型(Win32) | 说明 |
---|---|---|---|
boolean | jboolean | unsigned char | 无符号,8 位 |
byte | jbyte | signed char | 有符号,8 位 |
char | jchar | unsigned short | 无符号,16 位 |
short | jshort | short | 有符号,16 位 |
int | jint | long | 有符号,32 位 |
long | jlong | __int64 | 有符号,64 位 |
float | jfloat | float | 32 位 |
double | jdouble | double | 64 位 |
void | void | N/A | N/A |
三、相关问题
3.1 如何将java传入的String参数转换为c的char*,然后使用
java传入的String参数,在c文件中被jni转换为jstring的数据类型,在c文件中声明char* test,然后test = (char*)(*env)->GetStringUTFChars(env, jstring, NULL);注意:test使用完后,通知虚拟机平台相关代码无需再访问:(*env)->ReleaseStringUTFChars(env, jstring, test);
JNIEXPORT void JNICAL Java_test_string(JNIEnv *env, jclass thiz, jstring str) {
const char *test = (* env)->GetStringUTFChars(env, str,0);
(*env)->ReleaseStringUTFChars(env, str, test); //通知JVM清除内存
}
3.2 将c中获取的一个char*转化为string
这个char*如果是一般的字符串的话,作为string传回去就可以了。如果是含有’\0’的buffer,最好作为bytearray传出,因为可以制定copy的length,如果copy到string,可能到’\0’就截断了。
有两种方式传递得到的数据:
1)方法一
一种是在jni中直接new一个byte数组,然后调用函数(*env)->SetByteArrayRegion(env, bytearray, 0, len, buffer);将buffer的值copy到bytearray中,函数直接return bytearray就可以了。
JNIEXPORT jstring JNICAL Java_test_string(JNIEnv *env, jclass thiz, jstring str) {
jbyteArray bytearray;
bytearray = (*env)->NewByteArray(env,len);
(*env)->SetByteArrayRegion(env, bytearray, 0, len, buffer);
return bytearray;
}
1)方法二
JNIEXPORT jstring JNICAL Java_test_string(JNIEnv *env, jclass thiz, jstring str) {
jstring p;
p = (* env)->NewStringUTF(env,"success");
return p;
}
3.3 不知道占用多少空间的buffer,如何传递出去呢?
在jni的c文件中new出空间,传递出去。java的数据不初始化,指向传递出去的空间即可。