JNI – вызов метода Java с параметром функционального интерфейса из cpp

Мне хотелось бы знать, есть ли способ вызвать метод Java из cpp с функциональным интерфейсом и использовать этот обратный вызов внутри моего Java-кода.

Это мой код:

Main.java

import java.util.function.Consumer;

public class Main {
  static {
    System.loadLibrary("Main");
  }

  public static void main(String[] args) {
    Main m = new Main();
    m.nativeMethod();
  }

  public void test() {
      System.out.println("test");
  }

  public void getCallback(Consumer<String> consumer) {
        consumer.accept("Data from Java to cpp");
  }

  public native void nativeMethod();
}

Main.cpp

#include <iostream>
#include <string>
#include "Main.h"

extern "C"
{

    JNIEXPORT void JNICALL myCallback(JNIEnv *env, jobject obj, jstring data)
    {
        jboolean isCopy;
        const char *cData = env->GetStringUTFChars(data, &isCopy);
        std::string cppData(cData);
        env->ReleaseStringUTFChars(data, cData);

        std::cout << cppData << '\n';
    }

    JNIEXPORT void JNICALL Java_Main_nativeMethod(JNIEnv *env, jobject obj)
    {

        std::cout << "Hello from Java_Main_nativeMethod" << '\n';

        jmethodID jTest = env->GetMethodID(
            env->GetObjectClass(obj),
            "test",
            "()V");
        env->CallVoidMethod(obj, jTest);

        jmethodID jFunction = env->GetMethodID(
            env->GetObjectClass(obj),
            "getCallback",
            "(Ljava/util.function/Consumer;)V");
        env->CallVoidMethod(obj, jFunction, myCallback);
    }
}

compile.bash

#!/bin/bash
cd "$(dirname "$0")"

javac -h . Main.java  # generates header
javac Main.java
gcc -lstdc++ -shared -I /Library/Java/JavaVirtualMachines/temurin-20.jdk/Contents/Home/include -o libMain.jnilib Main.cpp
java Main

вывод $ bash compile.bash

Hello from Java_Main_nativeMethod
test
#
# A fatal error has been detected by the Java Runtime Environment:
#
#  SIGSEGV (0xb) at pc=0x0000000108554b14, pid=72842, tid=10499
#
# JRE version: OpenJDK Runtime Environment Temurin-20.0.2+9 (20.0.2+9) (build 20.0.2+9)
# Java VM: OpenJDK 64-Bit Server VM Temurin-20.0.2+9 (20.0.2+9, mixed mode, tiered, compressed oops, compressed class ptrs, g1 gc, bsd-aarch64)
# Problematic frame:
# V  [libjvm.dylib+0x514b14]  jni_CallVoidMethodV+0xe0
#
# No core dump will be written. Core dumps have been disabled. To enable core dumping, try "ulimit -c unlimited" before starting Java again
#
# An error report file with more information is saved as:
# hs_err_pid72842.log
#
# If you would like to submit a bug report, please visit:
#   https://github.com/adoptium/adoptium-support/issues
#
zsh: abort      java Main

Как я могу заставить это работать?

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
0
55
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Конечно, это возможно, но для этого потребуется преодолеть еще несколько препятствий, чем вы, возможно, надеялись.

Во-первых, предоставьте конкретную реализацию Java для интерфейса Consumer, которая может содержать своего рода ссылку на свой аналог на C++:

package com.example.consumertest;

import java.util.function.Consumer;

public class NativeStringConsumer implements Consumer<String> {
    // Holds a pointer to the native counterpart
    private final long nativeThis;

    private NativeStringConsumer(final long nativeThis) {
        this.nativeThis = nativeThis;
    }

    @Override
    public void accept(String s) {
        // Call the native counterpart with the provided string
        accept(nativeThis, s);
    }

    public void delete() {
        // Free the memory used by the native counterpart
        delete(nativeThis);
    }

    public static native NativeStringConsumer create();

    private static native void accept(final long nativeThis, final String s);

    private static native void delete(final long nativeThis);

    static {
        System.loadLibrary("native-lib");
    }
}

Далее реализуем собственные методы:

using StringConsumer = std::function<void(const std::string&)>;

extern "C"
JNIEXPORT jobject JNICALL Java_com_example_consumertest_NativeStringConsumer_create(
        JNIEnv *env,
        jclass clazz)
{
    auto consumer = new StringConsumer([] (const std::string &str) {
        // I'm testing this on Android, hence __android_log_write instead of std::cout
        __android_log_write(ANDROID_LOG_DEBUG, "NativeStringConsumer", str.c_str());
    });

    auto ctor = env->GetMethodID(clazz, "<init>", "(J)V");
    return env->NewObject(clazz, ctor, reinterpret_cast<jlong>(consumer));
}

extern "C"
JNIEXPORT void JNICALL Java_com_example_consumertest_NativeStringConsumer_accept(
        JNIEnv *env,
        jclass clazz,
        jlong native_this,
        jstring s)
{
    const char *chars = env->GetStringUTFChars(s, nullptr);
    std::string str(chars, env->GetStringLength(s));
    env->ReleaseStringUTFChars(s, chars);

    auto consumer = reinterpret_cast<StringConsumer*>(native_this);
    (*consumer)(str);
}

extern "C"
JNIEXPORT void JNICALL Java_com_example_consumertest_NativeStringConsumer_delete(
        JNIEnv *env,
        jclass clazz,
        jlong native_this)
{
    auto consumer = reinterpret_cast<StringConsumer*>(native_this);
    delete consumer;
}

Это просто иллюстративное доказательство концепции, поэтому в нем отсутствует множество проверок ошибок, которые необходимы в реальном коде JNI.

И то, как вы будете использовать все это в своем Java-коде, будет примерно таким:

final NativeStringConsumer consumer = NativeStringConsumer.create();
consumer.accept("Hello world!");

// ... at some point later on when you no longer need the object:
consumer.delete();        

Черт возьми, это работает. Действительно впечатляющее решение. Вы медиум? Потому что моей конечной целью было реализовать это с помощью Android. Как я могу дать вам награду за этот ответ?

justcodin 15.03.2024 20:26

Другие вопросы по теме