I'm currently working with Djinni and would like to call Java methods from C++.
I have the following interface description file:
ExampleSO = interface +j {
PerformAddition(a: i32, b: i32): i32;
}
It generates these files:
src/main/cpp/ExampleSO.hpp
: C++ ExampleSO
class containing a virtual destructor and a virtual PerformAddition
method.src/main/java/com/name/group/ExampleSO.java
: Java ExampleSO
abtract class containing a public abstract PerformAddition
method.src/main/jni/NativeExampleSO.hpp
/.cpp
: JNI bindings.What I want to do is create a new Java class that will extend the ExampleSO
Java class (as specified in the interface description with +j
), and be able to call these methods from a c++ file.
I can see in the JNI bindings that there is a public using CppType = std::shared_ptr<::ExampleSO>;
. Given the name, I assumed that this would be the way to call Java methods through the JNI bridge, but it results in a segfault when I'm trying to do something like :
// SampleClass.hpp
#include "ExampleSO.hpp"
class SampleClass: ExampleSO {
private:
NativeExampleSO::CppType neso;
public:
int32_t PerformAddition(int32_t a, int32_t b) override;
}
// SampleClass.cpp
#include "SampleClass.hpp"
int32_t SampleClass::PerformAddition(int32_t a, int32_t b) {
neso->PerformAddition(a, b); // Crash
}
Do I have to initialize this neso
field in some way?
Thanks in advance.
Edit: Here is the content of NativeExampleSO.hpp
(JNI bridge), it could make answering easier :
// AUTOGENERATED FILE - DO NOT MODIFY!
// This file generated by Djinni from ExampleSO.djinni
#pragma once
#include "ExampleSO.hpp"
#include "djinni_support.hpp"
namespace djinni_generated {
class NativeExampleSO final : ::djinni::JniInterface<::ExampleSO, NativeExampleSO> {
public:
using CppType = std::shared_ptr<::ExampleSO>;
using CppOptType = std::shared_ptr<::ExampleSO>;
using JniType = jobject;
using Boxed = NativeExampleSO;
~NativeExampleSO();
static CppType toCpp(JNIEnv* jniEnv, JniType j) { return ::djinni::JniClass<NativeExampleSO>::get()._fromJava(jniEnv, j); }
static ::djinni::LocalRef<JniType> fromCppOpt(JNIEnv* jniEnv, const CppOptType& c) { return {jniEnv, ::djinni::JniClass<NativeExampleSO>::get()._toJava(jniEnv, c)}; }
static ::djinni::LocalRef<JniType> fromCpp(JNIEnv* jniEnv, const CppType& c) { return fromCppOpt(jniEnv, c); }
private:
NativeExampleSO();
friend ::djinni::JniClass<NativeExampleSO>;
friend ::djinni::JniInterface<::ExampleSO, NativeExampleSO>;
class JavaProxy final : ::djinni::JavaProxyHandle<JavaProxy>, public ::ExampleSO
{
public:
JavaProxy(JniType j);
~JavaProxy();
int32_t PerformAddition(int32_t a, int32_t b) override;
private:
friend ::djinni::JniInterface<::ExampleSO, ::djinni_generated::NativeExampleSO>;
};
const ::djinni::GlobalRef<jclass> clazz { ::djinni::jniFindClass("com/name/group/ExampleSO") };
const jmethodID method_PerformAddition { ::djinni::jniGetMethodID(clazz.get(), "PerformAddition", "(II)I") };
};
} // namespace djinni_generated
As you've noticed, using an object which implements a Djinni interface requires first creating an object, which can only be done in the language which implements the object. Once you've got an object you can pass it between languages, and make calls on it freely from any language. The question is how do you "bootstrap" to get the object you need. In general, bootstrapping always had to start from Java/ObjC.
Djinni doesn't support direct use of constructors, but supports static methods in one direction (Java/ObjC -> C++). You can either make a call from Java to provide the object for C++ to store and use later, or you can do the reverse and use the static method as a factory, letting Java ask C++ to create an object. The former is simpler if you don't mind using global state, or you need to use the object immediately.
interface example_so_setup +c {
set_example_so(obj: example_so)
}
There's an example in the Djinni test suite, where test_helper
is an interface with a method check_client_interface_ascii
which is called from Java here. Java passes aJava object as an argument, which C++ then makes calls on.
If you want to avoid using global state, you can just add an extra step, where Java first calls a static factory method to create some sort of C++ "manager" object, then makes a call on that object to pass the example_so for callbacks. The specifics of how that would happen probably depend on your app's initialization needs.