代码之家  ›  专栏  ›  技术社区  ›  Demosthenes

从C++ C++ java回调到C++回调

  •  0
  • Demosthenes  · 技术社区  · 6 年前

    关于如何用JNI调用C++的java代码,我可以做很多文章,也可以用C++调用java函数。

    现在我找不到以下信息:

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

    现在我想从C++程序中调用这个函数,一旦调用回调函数,我就希望调用C++回调函数。有谁能给我指一个关于如何做到这一点的信息来源吗?

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

    2 回复  |  直到 6 年前
        1
  •  3
  •   David    6 年前

    你说得对,不知何故,这方面的文档并不容易找到。 但我仍然记得以前的一个项目中我做这件事的方式。 你必须阅读一些免费的在线文档,因为我可能会错过一些细节。我会在这篇文章的结尾给你链接。

    因此,如果我理解您的正确性,您希望从java调用一个本地C++函数。 首先,记住java本机接口不是C++,而是C。 这就像大多数高级编程语言的本机接口一样(我到目前为止所见过的)。

    1. 创建本机接口的Java视图。这就是创建一个Java类并声明本机方法。这就是关键词 native 你可以用它。您不提供任何实现,只需声明即可。

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

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

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

      系统加载(“到库的路径”);

    如果本机函数已在当前进程中加载,则不必执行最后一步4。如果你在CPP应用程序中嵌入Java应用程序。在这种情况下,你可能想看看 RegisterNatives .

    Java关键字的文档 出生地的 :

    JNI的文档如下:

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

    .

    编辑

    不知怎的,我今天比昨天更理解你的问题:

    • 你有一个C++应用程序嵌入了一个java应用程序。
    • 从C++中,你想调用java方法。
    • 调用时,希望传递回调方法。
    • 当Java方法完成时,它必须调用之前传递的回调方法。

    好的,加上你已经知道的,再加上我在上面给出的解释,这是可以做到的。 实际上,它可以用不同的方式再次实现。 我将解释一个简单的例子:

    您的C++应用程序已经知道在java方法完成时需要调用哪个回调。 当调用Java方法时,将回调作为 key . 您已经知道如何从C++调用java方法。 这 钥匙 什么都可以。保持简单 钥匙 是一个 uintptr_t ,指针大小的整数。 在这种情况下,我们只需将函数指针作为回调传递给Java方法。

    但是Java不能通过取消对整数/指针的引用来调用回调。 现在你调用一个本地人 extern "C" 函数并赋予它 钥匙 作为参数。 我在上面解释了如何从Java调用本机函数。 该本机函数现在只需要将该整数转换回指针: ( reinterpret_cast<>() )然后调用你的回调。 当然,本机函数可以接受比 钥匙 如果有一些数据要传递给回调函数,则返回回调函数。

    我认为这个想法现在已经非常清楚了。

    如果你想有更多的可移植代码,那么不要使用回调地址作为键。 而是使用整数甚至字符串,并使用 std::map 将该键映射到真正的回调。 但就从这个简单的例子开始吧。当这起作用时,很容易改进它。 大多数工作将是设置项目和工具,以便协同工作。

        2
  •  2
  •   Demosthenes    6 年前

    好了,我来告诉未来的读者我是如何做到的。有几点对我来说似乎不太干净,如果有人对如何更干净地做这件事有想法,我会对此非常感兴趣。

    所以,我在包FO中编写了一个简单的java类条,它将从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);
        }
    }
    

    如你所见,我还编写了一个类函子,这也很简单

    包福;

    //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) 所以我决定,为了这个,接受这个警告。