关于如何使用JNI从C ++调用Java代码的文章和问题不计其数,我可以做到,我可以从C ++调用一些Java函数。
现在我找不到以下任何信息:
假设我有一个Java函数,需要将回调函数传递给它。此回调函数在稍后的某个时间从另一个线程调用。
现在,我想从C ++程序调用此函数,并且一旦调用了回调函数,就希望调用C ++回调。有人可以指出我的信息来源吗?
背景是我想在现有C ++项目中使用Java库(全部在Linux上,尽管我怀疑这是否相关)。通过JNI调用Java函数的开销在这里不是问题。
答案 0 :(得分:2)
您是正确的,以某种方式很难找到此文档。 但是我仍然记得以前的项目是我做这件事的方式。 您必须阅读一些免费的在线文档,以尽自己的一份力量,因为我可能会错过一些细节。我将在本文末尾给您链接。
因此,如果我了解您的话,您想从Java调用本机C ++函数。 首先请记住,Java本机接口不是C ++,而是C。 就像大多数高级编程语言的本机接口(到目前为止,我所见过的一样)。
创建本机接口的Java视图。那就是创建一个Java类并声明本机方法。您可以使用关键字native
。您无需声明任何实现即可。
使用javac -h
生成本机头文件。阅读该工具的文档。在Java 7中,有一个名为javah
的单独工具。但是对于当前的Java 11,您应该使用javac
。
使用C或C ++为生成的标头中声明的函数提供实现。编译并将它们链接到共享对象(*.so
或*.dll
)。
在Java应用程序运行时,通过调用以下命令从新库中加载本机代码:
System.load(“路径到lib”);
如果您的本机函数已在当前进程中加载,则不必在最后步骤4中执行该操作。那就是如果您要在CPP应用程序中嵌入Java应用程序。在这种情况下,您可能需要查看RegisterNatives。
Java关键字native
的文档:
JNI的文档在这里:
还要查看Java编译器的文档,以了解如何生成本机头。寻找选项-h
:
。
修改
今天,我比昨天更好地理解了您的问题:
好,再加上您已经知道的内容,再加上我上面给出的解释,就可以完成。 实际上,可以用不同的方法再次进行。 我将解释一个简单的例子:
您的C ++应用程序已经知道Java方法完成后需要调用哪个回调。
调用Java方法时,将其回调方式设为key
。
您已经知道如何从C ++调用Java方法。
这个key
可以是任何东西。为简单起见,key
是一个uintptr_t
,它是指针大小的整数。
在这种情况下,我们只需将函数指针作为回调传递给Java方法即可。
但是Java无法通过取消引用整数/指针事物来调用回调。
现在,您将调用本机extern "C"
函数,并将其key
作为参数。
上面我解释了如何从Java调用本机函数。
现在,该本地函数只需要将该整数转换为指针即可:
(reinterpret_cast<>()
)并调用您的回调。
当然,如果有一些数据要传递给回调,则本机函数可以接受除回调{key
之外的其他参数。
我认为现在的想法很明确。
如果您想拥有更多可移植的代码,则不要使用回调的地址作为键。
而不是使用整数或什至字符串,并使用std::map
将该键映射到您的真实回调。
但是,从一个简单的例子开始。而且当它起作用时,很容易对其进行改进。
大多数工作将是设置项目和工具以使其协同工作。
答案 1 :(得分:0)
好的,在这里为以后的读者介绍我是如何做到的。有些地方对我来说似乎不是很干净,如果有人对如何更干净地做有想法,我对此会非常感兴趣。
因此,我在foo包中编写了一个简单的Java类Bar,该类将从C ++中调用,将对函数的引用传递给函数(在下面进行更多介绍),并使用一些硬编码的参数调用该函数。
package foo;
import foo.Functor;
//this is just what we want to call from C++
//for demonstration, expect return type int
public class Bar
{
public static void run(long addr) {
Functor F = new Functor(addr);
//synchronously here, just to prove the concept
F.run(1,2);
}
}
如您所见,我还编写了一个Functor类,它也很简单
package foo;
//we need to write this for every signature of a callback function
//we'll do this as a void foo(int,int), just to demonstrate
//if someone knows how to write this in a general (yet JNI-compatible) way,
//keeping in mind what we are doing in the non-Java part, feel free to tell me
public class Functor
{
static {
System.loadLibrary("functors");
}
public native void runFunctor(long addr,int a,int b);
long address;
public Functor(long addr)
{
address = addr;
}
public void run(int a, int b) {
runFunctor(address,a,b);
}
}
这取决于我称为函子的共享库。实施起来非常简单。这个想法是将实际逻辑分开,只在共享库中提供接口。如前所述,主要缺点是我必须为每个签名编写它,但我看不出如何对此模板化。
为了完整起见,这是共享对象的实现:
#include <functional>
#include "include/foo_Functor.h"
JNIEXPORT void JNICALL Java_foo_Functor_runFunctor
(JNIEnv *env, jobject obj, jlong address, jint a, jint b)
{
//make sure long is the right size
static_assert(sizeof(jlong)==sizeof(std::function<void(int,int)>*),"Pointer size doesn't match");
//this is ugly, if someone has a better idea...
(*reinterpret_cast<std::function<void(int,int)>*>(address))(static_cast<int>(a),static_cast<int>(b));
}
最后,这是我在C ++中的调用方式,它在运行时在共享库之外定义了回调函数:
#include <iostream>
#include <string>
#include <jni.h>
#include <functional>
int main()
{
//this is from some tutorial, nothing special
JavaVM *jvm;
JNIEnv *env;
JavaVMInitArgs vm_args;
JavaVMOption* options = new JavaVMOption[1];
options[0].optionString = "-Djava.class.path=."; //this is actually annoying, JNI has this as char* without const, resulting in a warning since this is illegal in C++ (from C++11)
vm_args.version = JNI_VERSION_1_6;
vm_args.nOptions = 1;
vm_args.options = options;
vm_args.ignoreUnrecognized = false;
jint rc = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);
delete[] options;
if (rc != JNI_OK)
return EXIT_FAILURE;
jclass cls = env->FindClass("foo/Bar");
jmethodID mid = env->GetStaticMethodID(cls, "run", "(J)V");
//the main drawback of this approach is that this std::function object must never go out of scope as long as the callback could be fired
std::function<void(int,int)> F([](int a, int b){std::cout << a+b << std::endl;});
//this is a brutal cast, is there any better option?
long address = reinterpret_cast<long>(&F);
env->CallStaticVoidMethod(cls,mid,static_cast<jlong>(address));
if (env->ExceptionOccurred())
env->ExceptionDescribe();
jvm->DestroyJavaVM();
return EXIT_SUCCESS;
}
这很好用,我可以处理。不过,有些事情仍然困扰着我:
reinterpret_cast
来将指向std::function
的指针转换为整数并返回。但这是我想到的将对函数的引用(可能仅在运行时存在)传递给Java的最佳方式... char *
(此处应为const
)。这看起来很无辜,但发出了编译器警告,因为它在C ++中是非法的(在C中是合法的,这就是JNI开发人员可能不在乎的原因)。我没有找到解决这个问题的优雅方法。我知道如何使其合法,但是我真的不想为此写几行代码(或丢掉const_cast
s),所以我决定为此而忍受警告。