首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >C++对C++回调的Java回调

C++对C++回调的Java回调
EN

Stack Overflow用户
提问于 2018-10-15 23:57:13
回答 2查看 1.6K关注 0票数 2

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

现在,我找不到任何关于以下内容的信息:

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

现在我想从C++程序调用这个函数,一旦回调函数被调用,我就想要调用一个C++回调函数。谁能告诉我如何做到这一点的信息来源?

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

EN

回答 2

Stack Overflow用户

发布于 2018-10-16 02:23:54

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

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

  1. 创建本机接口的Java视图。也就是说,创建一个Java类并声明本机方法。有一个关键字native可以用于此目的。您不需要提供任何实现,只需声明它即可。
  2. 使用javac -h生成本机头文件。阅读该工具的文档。在Java7中,有一个独立的工具,称为javah。但是在当前的Java11中,您应该使用javac.
  3. Use C或C++来为生成的头文件中声明的函数提供实现。编译它们并将它们链接到共享对象( java应用程序的*.so*.dll).
  4. At运行时通过调用以下命令从新库中加载本机代码:

System.load("path-to-lib");

如果您的本机函数已经加载到当前进程中,则不必执行最后一步4。如果您要将Java应用程序嵌入到CPP应用程序中,就会出现这种情况。在这种情况下,您可能需要查看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将该键映射到真正的回调函数。但就从这个简单的例子开始吧。当它起作用的时候,很容易改进它。大多数工作将是设置项目和工具一起工作。

票数 3
EN

Stack Overflow用户

发布于 2018-10-17 19:11:03

好的,这里是给未来的读者我是如何做到的。有几点对我来说似乎不是完全干净的,如果有人有关于如何更干净地做它的想法,我会对此非常感兴趣。

因此,我在包foo中编写了一个简单的Java class Bar,它将从C++调用,传递对一个函数的引用(下面将详细介绍),并使用一些硬编码的参数调用该函数。

代码语言:javascript
复制
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;

代码语言:javascript
复制
//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的共享库。实现起来非常简单。其思想是保持实际的逻辑分离,并且只在共享对象中提供接口。主要的缺点,正如前面提到的,是我必须为每个签名写它,我看不到这个模板的方法。

为了完整起见,下面是共享对象的实现:

代码语言:javascript
复制
#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++中调用它,在运行时定义共享对象外部的回调函数:

代码语言:javascript
复制
#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)不够灵活,我不喜欢做

  1. reinterpret_caststd::function之间的转换。但这是我能想到的最好的方法,将一个函数的引用(可能只在运行时存在)传递给Java...
  2. 在C++中初始化Java时,我设置了一个选项,这个选项在C++接口中被定义为char * (这里应该有一个const )。这行代码看起来非常简单,但却给出了编译器警告,因为它在C++中是非法的(在C中是合法的,这就是为什么JNI开发人员不关心它的原因)。我找不到一种优雅的方法来解决这个问题。我知道如何使其合法化,但我真的不想只为此编写几行代码(或抛出const_cast),因此我决定,为此,只接受警告。
票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/52820549

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档