有无数关于如何使用JNI从C++调用Java代码的文章和问题,我可以做到这一点,我可以从C++调用一些Java函数。
现在,我找不到任何关于以下内容的信息:
假设我有一个Java函数,它需要一个回调函数传递给它。此回调函数在稍后的某个时间点从不同的线程调用。
现在我想从C++程序调用这个函数,一旦回调函数被调用,我就想要调用一个C++回调函数。谁能告诉我如何做到这一点的信息来源?
背景是我想在现有的C++项目中使用Java库(都在Linux上,尽管我怀疑这是否相关)。通过JNI调用Java函数的开销在这里不是问题。
发布于 2018-10-16 02:23:54
您是对的,不知何故,这方面的文档并不容易找到。但我仍然记得以前的一个项目中我做这件事的方式。你必须通过阅读一些免费的在线文档来完成你的一部分,因为我可能会错过一些细节。我会在这篇文章的末尾给你链接。
所以如果我没理解错的话,您想从Java中调用一个本机C++函数。首先,请记住,Java本机接口不是C++,而是C。这就像大多数高级编程语言的本机接口(我到目前为止所见过的所有接口)。
native
可以用于此目的。您不需要提供任何实现,只需声明它即可。javac -h
生成本机头文件。阅读该工具的文档。在Java7中,有一个独立的工具,称为javah
。但是在当前的Java11中,您应该使用javac
.*.so
或*.dll
).System.load("path-to-lib");
如果您的本机函数已经加载到当前进程中,则不必执行最后一步4。如果您要将Java应用程序嵌入到CPP应用程序中,就会出现这种情况。在这种情况下,您可能需要查看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
将该键映射到真正的回调函数。但就从这个简单的例子开始吧。当它起作用的时候,很容易改进它。大多数工作将是设置项目和工具一起工作。
发布于 2018-10-17 19:11:03
好的,这里是给未来的读者我是如何做到的。有几点对我来说似乎不是完全干净的,如果有人有关于如何更干净地做它的想法,我会对此非常感兴趣。
因此,我在包foo中编写了一个简单的Java class 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);
}
}
如您所见,我还编写了一个类函数器,它也很简单
包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);
}
}
这依赖于一个我称为functors的共享库。实现起来非常简单。其思想是保持实际的逻辑分离,并且只在共享对象中提供接口。主要的缺点,正如前面提到的,是我必须为每个签名写它,我看不到这个模板的方法。
为了完整起见,下面是共享对象的实现:
#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
),因此我决定,为此,只接受警告。https://stackoverflow.com/questions/52820549
复制相似问题