博主在实习中遇到了实际开发的问题,关于JNI,之前已经有一个博客和小的demo:
https://blog.csdn.net/Applying/article/details/81572167
现在的需求是:JAVA端获取到一定格式的数据,并传递给C++端,C++端获取这部分数据之后,进行算法的操作,将操作结果重新以另外特定的结构格式传递给JAVA端使用。
思路:其实我们现在要实现的,就是JAVA与C++之间交互特定的数据结构,这里常见碰到的有三种:
- String类
- JAVA类
- 类数组
接下来,分3个部分,简单写一下demo:
##1. JAVA与C++之间传递String
###JAVA端代码:
// Hello.java:
public class Hello {
static {
System.loadLibrary("HelloC");
}
public native Student testGetStudent();
public native void testPassStudent(Student student);
}
###C++端代码:
// Impl.cpp:
JNIEXPORT jstring JNICALL Java_aa_bb_cc_Hello_testGetString
(JNIEnv * env, jobject)
{
// 1. 创建字符串
char * a = "hello java";
// 2. 直接转为jstring类型
jstring returnValue = env->NewStringUTF(a);
return returnValue;
}
JNIEXPORT void JNICALL Java_aa_bb_cc_Hello_testPassString
(JNIEnv * env, jobject, jstring string)
{
const char * a = env->GetStringUTFChars(string, NULL);
cout << a << endl;
return;
}
###Junit测试代码及结果:
// testHello.java:
class HelloTest {
private Hello hello = new Hello();
@Test
void testGetString() {
System.loadLibrary("HelloC");
String temp = hello.testGetString();
System.out.println(temp);
}
@Test
void testPassString() {
System.loadLibrary("HelloC");
hello.testPassString("hello ");
}
}
##2. JAVA与C++之间传递JAVA类
###JAVA端代码:
// Hello.java:
public class Hello {
static {
System.loadLibrary("HelloC");
}
public native Student testGetStudent();
public native void testPassStudent(Student student);
}
// 这里定义了一个学生类,Student.java:
public class Student {
private int height;
private int weight;
private int[] grade;
public Student(int height, int weight, int[] grade) {
super();
this.height = height;
this.weight = weight;
this.grade = grade;
}
public Student() {
}
@Override
public String toString() {
return "Student [height=" + height + ", weight=" + weight + ",
grade=" + Arrays.toString(grade) + "]";
}
}
###C++端代码:
// Impl.cpp:
struct Student{
int height;
int weight;
int grade[2];
};
JNIEXPORT jobject JNICALL Java_aa_bb_cc_Hello_testGetStudent
(JNIEnv * env, jobject)
{
// 1. 模拟原C++代码中的结构体
Student student;
student.height = 166;
student.weight = 60;
student.grade[0] = 100;
student.grade[1] = 100;
// 2. 获得对应JAVA类的各个属性
// 这里的“Laa/bb/cc/Student;”,去通过javah指令生成的头文件里即可找到
jclass objClass = env->FindClass("Laa/bb/cc/Student;");
jfieldID heightFieldId = env->GetFieldID(objClass, "height", "I");
jfieldID weightFieldId = env->GetFieldID(objClass, "weight", "I");
// “[” 代表一个数组结构,“I”代表的是Int
jfieldID gradeFieldId = env->GetFieldID(objClass, "grade", "[I");
// 3. 创建新的对象并将结构体中数据赋值给JAVA对象
jobject object = env->AllocObject(objClass);
const jint array[2] = {student.grade[0], student.grade[1]};
jintArray array_ = env->NewIntArray(2);
env->SetIntArrayRegion(array_, 0, 2, array);
env->SetIntField(object, heightFieldId, student.height);
env->SetIntField(object, weightFieldId, student.weight);
// 这里关于这个数组转换,我不太清楚是否有更简单的方法
env->SetObjectField(object, gradeFieldId, array_);
return object;
}
JNIEXPORT void JNICALL Java_aa_bb_cc_Hello_testPassStudent
(JNIEnv *env, jobject, jobject object)
{
// 1. 获得对应JAVA类的各个属性
jclass jclass = env->FindClass("Laa/bb/cc/Student;");
jfieldID heightFieldId = env->GetFieldID(jclass, "height", "I");
jfieldID weightFieldId = env->GetFieldID(jclass, "weight", "I");
jfieldID gradeFieldId = env->GetFieldID(jclass, "grade", "[I");
// 2. 获得传入类的具体属性值
jint height = env->GetIntField(object, heightFieldId);
jint weight = env->GetIntField(object, weightFieldId);
jintArray grade = (jintArray)env->GetObjectField(object, gradeFieldId);
jint* array = env->GetIntArrayElements(grade, NULL);
// 3. 将具体的属性值再封装成C++中的结构体
// 这里直接打印测试函数传参是否成功
cout << "height: " << height << " weight: " << weight << endl
<< "grade[0]: " << array[0] << " grade[1]: " << array[1] <<endl;
return;
}
###Junit测试代码及结果:
// testHello.java:
class HelloTest {
private Hello hello = new Hello();
@Test
void testGetStudent() {
System.loadLibrary("HelloC");
Student student = hello.testGetStudent();
System.out.println(student.toString());
}
@Test
void testPassStudent() {
System.loadLibrary("HelloC");
Student student = new Student(171, 67, new int[] {99, 100});
hello.testPassStudent(student);
}
}
##3. JAVA与C++之间传递类数组
###JAVA端代码:
// Hello.java:
public class Hello {
static {
System.loadLibrary("HelloC");
}
public native Student[] testGetStudentArray();
public native void testPassStudentArray(Student[] students);
}
###C++端代码:
// Impl.cpp:
struct Student{
int height;
int weight;
int grade[2];
};
JNIEXPORT jobjectArray JNICALL Java_aa_bb_cc_Hello_testGetStudentArray
(JNIEnv * env, jobject)
{
const int number = 2;
// 1. 模拟数据
Student a[number];
a[0].height = 180; a[0].weight = 100;
a[0].grade[0] = 100; a[0].grade[1] = 99;
a[1].height = 181; a[1].weight = 101;
a[1].grade[0] = 88; a[1].grade[1] = 88;
// 2. 获得JAVA类的各个属性
jclass jclass = env->FindClass("Laa/bb/cc/Student;");
jfieldID heightFieldId = env->GetFieldID(jclass, "height", "I");
jfieldID weightFieldId = env->GetFieldID(jclass, "weight", "I");
jfieldID gradeFieldId = env->GetFieldID(jclass, "grade", "[I");
// 这里获得构造方法
jmethodID initMethodId = env->GetMethodID(jclass, "<init>", "()V");
// 3. 将数据封装成对象再分装成数组
jobject object;
jobjectArray array = env->NewObjectArray(number, jclass, NULL);
for (int i = 0; i < number; i++) {
object = env->NewObject(jclass, initMethodId);
env->SetIntField(object, heightFieldId, a[i].height);
env->SetIntField(object, weightFieldId, a[i].weight);
jintArray tempArray = env->NewIntArray(number);
const jint array_[2] = {a[i].grade[0], a[i].grade[1]};
env->SetIntArrayRegion(tempArray, 0, 2, array_);
env->SetObjectField(object, gradeFieldId, tempArray);
// 将对象插入数组的特定位置
env->SetObjectArrayElement(array, i, object);
}
return array;
}
JNIEXPORT void JNICALL Java_aa_bb_cc_Hello_testPassStudentArray
(JNIEnv *env, jobject, jobjectArray array)
{
// 1. 获得对应JAVA类的各个属性
jclass jclass = env->FindClass("Laa/bb/cc/Student;");
jfieldID heightFieldId = env->GetFieldID(jclass, "height", "I");
jfieldID weightFieldId = env->GetFieldID(jclass, "weight", "I");
jfieldID gradeFieldId = env->GetFieldID(jclass, "grade", "[I");
int length = env->GetArrayLength(array);
// 2. 将数组拆分,每个对象转为结构体
jobject obj;
Student stu;
for (int i = 0; i < length; i++) {
obj = env->GetObjectArrayElement(array, i);
stu.height = env->GetIntField(obj, heightFieldId);
stu.weight = env->GetIntField(obj, weightFieldId);
jintArray temp = (jintArray)env->GetObjectField(obj, gradeFieldId);
env->GetIntArrayRegion(temp, 0, env->GetArrayLength(temp), (jint*)stu.grade);
// 这里用打印测试结果替代转为结构体
cout << "height: " << stu.height << " weight: " << stu.weight << endl
<< "grade[0]: " << stu.grade[0] << " grade[1]: " << stu.grade[1] <<endl;
}
return;
}
###Junit测试代码及结果:
// testHello.java:
class HelloTest {
private Hello hello = new Hello();
@Test
void testGetStudentArray() {
System.loadLibrary("HelloC");
Student[] students = hello.testGetStudentArray();
for (Student student : students) {
System.out.println(student.toString());
}
}
@Test
void testPassStudentArray() {
System.loadLibrary("HelloC");
ArrayList<Student> students = new ArrayList<>();
students.add(new Student(171, 67, new int[] {100, 99}));
students.add(new Student(155, 45, new int[] {100, 99}));
Student[] stu = new Student[students.size()];
students.toArray(stu);
hello.testPassStudentArray(stu);
}
}
##4. 一些遇到的坑&建议
###① 自定义的类一定要有无参的构造函数
这里如果没有无参的构造函数,在执行的时候,会出现:
###② 将java.library.path指定为动态链路库的生成路径
我们在VS处生成动态链路库文件xx.dll,将编辑器的java.library.path路径指定为对应的生成路径,可以减少生成之后移动到java.library.path的麻烦。关于设置java.library.path的路径:
eclipse可以参考:里面有提到如何设置
https://blog.csdn.net/Applying/article/details/81572167
对于IDEA:RUN-》Edit Configurations:
在VM options处,配置:-Djava.library.path=xxx
即可
关于一些函数签名表or参数类型后期再整理一波
这个只是一个实现的demo,记录下来,方便忘记时查看