Java回调到C ++从C ++回调

时间:2018-10-15 15:57:13

标签: java c++ java-native-interface

关于如何使用JNI从C ++调用Java代码的文章和问题不计其数,我可以做到,我可以从C ++调用一些Java函数。

现在我找不到以下任何信息:

假设我有一个Java函数,需要将回调函数传递给它。此回调函数在稍后的某个时间从另一个线程调用。

现在,我想从C ++程序调用此函数,并且一旦调用了回调函数,就希望调用C ++回调。有人可以指出我的信息来源吗?

背景是我想在现有C ++项目中使用Java库(全部在Linux上,尽管我怀疑这是否相关)。通过JNI调用Java函数的开销在这里不是问题。

2 个答案:

答案 0 :(得分:2)

您是正确的,以某种方式很难找到此文档。 但是我仍然记得以前的项目是我做这件事的方式。 您必须阅读一些免费的在线文档,以尽自己的一份力量,因为我可能会错过一些细节。我将在本文末尾给您链接。

因此,如果我了解您的话,您想从Java调用本机C ++函数。 首先请记住,Java本机接口不是C ++,而是C。 就像大多数高级编程语言的本机接口(到目前为止,我所见过的一样)。

  1. 创建本机接口的Java视图。那就是创建一个Java类并声明本机方法。您可以使用关键字native。您无需声明任何实现即可。

  2. 使用javac -h生成本机头文件。阅读该工具的文档。在Java 7中,有一个名为javah的单独工具。但是对于当前的Java 11,您应该使用javac

  3. 使用C或C ++为生成的标头中声明的函数提供实现。编译并将它们链接到共享对象(*.so*.dll)。

  4. 在Java应用程序运行时,通过调用以下命令从新库中加载本机代码:

    System.load(“路径到lib”);

如果您的本机函数已在当前进程中加载​​,则不必在最后步骤4中执行该操作。那就是如果您要在CPP应用程序中嵌入Java应用程序。在这种情况下,您可能需要查看RegisterNatives

Java关键字native的文档:

JNI的文档在这里:

还要查看Java编译器的文档,以了解如何生成本机头。寻找选项-h

修改

今天,我比昨天更好地理解了您的问题:

  • 您有一个嵌入Java应用程序的C ++应用程序。
  • 您想从C ++调用Java方法。
  • 调用时,您想传递一个回调方法。
  • Java方法完成后,必须调用您之前传递的回调方法。

好,再加上您已经知道的内容,再加上我上面给出的解释,就可以完成。 实际上,可以用不同的方法再次进行。 我将解释一个简单的例子:

您的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;
}

这很好用,我可以处理。不过,有些事情仍然困扰着我:

  1. 我必须为我要传递的每个函数签名编写接口(Java类和相应的C ++实现)。可以用更一般的方式完成吗?我的感觉是Java(尤其是JNI)不够灵活。
  2. 我不喜欢用reinterpret_cast来将指向std::function的指针转换为整数并返回。但这是我想到的将对函数的引用(可能仅在运行时存在)传递给Java的最佳方式...
  3. 在C ++中初始化Java VM时,我设置了一个选项,该选项在JVM接口中定义为char *(此处应为const)。这看起来很无辜,但发出了编译器警告,因为它在C ++中是非法的(在C中是合法的,这就是JNI开发人员可能不在乎的原因)。我没有找到解决这个问题的优雅方法。我知道如何使其合法,但是我真的不想为此写几行代码(或丢掉const_cast s),所以我决定为此而忍受警告。