JNI技术学习(二):JAVA 与 C++ 之间传递String,自定义类,自定义类的数组

博主在实习中遇到了实际开发的问题,关于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,记录下来,方便忘记时查看

猜你喜欢

转载自blog.csdn.net/Applying/article/details/82393212