JNI回调

  在使用JNI的过程中,大多数情况可能都是Java调用本地语言,但不可避免的也会碰到本地语言中调用Java中方法的情况。这就碰到了回调的问题,做了小研究特此记录下来。

  这里要讲到的回调的例子,是基于上一篇《初始JNI》,增加了回调。

1、定义回调方法

1
2
3
4
5
6
7
8
9
/**
* C++程序回调本方法,并传递数据到此
* @param strArg
*/
public void receiveCPPTest(String strArg){
System.out.println("来自C++程序的回调,数据:" + strArg);
}

2、本地语言中调用Java方法

  在C++的源码文件中按照步骤来找到Java中的对应的方法并调用。

1)获取Java中的目标类

  • 根据类名获取
      JNIEnv中的FindClass方法可以根据方法名直接得到Java中的类
  • 根据类对象获取
      JNIEnv中同样提供了根据Java类的对象来获取Java类的方法,GetObjectClass。

2)获取要调用Java中的方法

  此处的获取目标方法,会需要用到方法的标识,即方法名和方法签名(参数和返回值)。

3)执行目标方法

&esmp; 在执行目标方法的时候要注意,如果方法有参数需要传入,应该特别注意进行类型和编码的转换。

执行回调的c++源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
void testCallback(JNIEnv *env, jobject obj) {
// 1. 获取目标类
char* classname = "JNIDemo";
jclass objClass = env->FindClass(classname);
//jclass objClass = env->GetObjectClass(obj);
// 2. 获取要调用的方法,需要传入类、方法名、方法签名
jmethodID methodID = env->GetMethodID(objClass, "receiveCPPTest", "(Ljava/lang/String;)V");
jstring methodArg = StringUtils::str2jstring(env, "C++ call Java method.");
// 3. 执行目标方法:类对象,方法ID,方法参数……
env->CallVoidMethod(obj, methodID, methodArg);
}
/*
* Class: JNIDemo
* Method: test1
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_JNIDemo_test1
(JNIEnv *env, jobject obj)
{
cout << "Hello World, this is C++ printer." << endl;
testCallback(env, obj);
}

3、触发本地代码中的回调方法

  此处有多种选择,毕竟C++执行Java回调的方法在任何地方都可以调用,此例子中我在Java中定义了一个测试方法来触发。

Java中调用test1来触发此回调函数。

1
2
3
4
5
6
System.loadLibrary("JavaCpp");
JNIDemo jniDemo = new JNIDemo();
// 1、测试无参无返回值的方法
jniDemo.test1();

4、执行回调

运行此程序,得到下图的结果。

可以看到C++中的代码正确的调用了Java中的方法,并传递了参数到Java中。

备注

查看方法签名

JDK中本身提供了查看签名的方法。

1
javap -s JNIDemo

  可以很清晰的看到receiveCPPTest方法的签名是

1
(Ljava/lang/String;)V

参数是String类型,返回值是void。

jstring和string

  因为Java和C++中数据类型的区别,所以需要进行转换。上面例子中已经写好了一个,代码贴出来。

头文件StringUtils.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#pragma once
#include "stdafx.h"
#include <jni.h>
#include <string>
using namespace std;
class StringUtils
{
public:
StringUtils();
~StringUtils();
static jstring str2jstring(JNIEnv* env, const char* pat);
static string jstring2str(JNIEnv* env, jstring jstr);
static char* str2chars(string jstr);
static char* jstring2chars(JNIEnv* env, jstring jstr);
};

源码文件StringUtils.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#include "stdafx.h"
#include "StringUtils.h"
StringUtils::StringUtils()
{
}
StringUtils::~StringUtils()
{
}
jstring StringUtils::str2jstring(JNIEnv* env, const char* pat)
{
//定义java String类 strClass
jclass strClass = (env)->FindClass("Ljava/lang/String;");
//获取String(byte[],String)的构造器,用于将本地byte[]数组转换为一个新String
jmethodID ctorID = (env)->GetMethodID(strClass, "<init>", "([BLjava/lang/String;)V");
//建立byte数组
jbyteArray bytes = (env)->NewByteArray(strlen(pat));
//将char* 转换为byte数组
(env)->SetByteArrayRegion(bytes, 0, strlen(pat), (jbyte*)pat);
// 设置String, 保存语言类型,用于byte数组转换至String时的参数
jstring encoding = (env)->NewStringUTF("GB2312");
//将byte数组转换为java String,并输出
return (jstring)(env)->NewObject(strClass, ctorID, bytes, encoding);
}
string StringUtils::jstring2str(JNIEnv* env, jstring jstr)
{
char* rtn = jstring2chars(env, jstr);
std::string stemp(rtn);
free(rtn);
return stemp;
}
char* StringUtils::str2chars(string strArg)
{
char* c;
const int len = strArg.length();
c = new char[len + 1];
strcpy_s(c, len + 1, strArg.c_str());
return c;
}
//jstring to char*
char* StringUtils::jstring2chars(JNIEnv* env, jstring jstr)
{
char* rtn = NULL;
jclass clsstring = env->FindClass("java/lang/String");
jstring strencode = env->NewStringUTF("utf-8");
jmethodID mid = env->GetMethodID(clsstring, "getBytes", "(Ljava/lang/String;)[B");
jbyteArray barr = (jbyteArray)env->CallObjectMethod(jstr, mid, strencode);
jsize alen = env->GetArrayLength(barr);
jbyte* ba = env->GetByteArrayElements(barr, JNI_FALSE);
if (alen > 0)
{
rtn = (char*)malloc(alen + 1);
memcpy(rtn, ba, alen);
rtn[alen] = 0;
}
env->ReleaseByteArrayElements(barr, ba, 0);
return rtn;
}

总结

  其实,JNI也没有太难,只是需要注意的细节挺多的,再加上需要同时了解本地语言,所以可能一定程度增加了点难度(对于不懂本地语言的童鞋),总体来说还算简洁。

大爷给小弟的零花钱
显示 Gitment 评论