Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add java support (using JNA) #857

Open
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

fredszaq
Copy link
Contributor

This builds on #853 and adds a new language backend capable of generating java bindings.

The generated bindings use JNA (I used the last version 5.13.0 while developing but this will probably work with earlier ones as well)

Most of the new code lives in bindgen/language_backend/java_jna.rs and is pretty straightforward generation as most on the complicated things were done in the previous refactoring.

The test suite has been updated to generate and compile the bindings for java when running. On nice thing gained with this is the possibility to put a configuration file specific to a language alongside the standard one and the test will take this one instead of the default one if it exists (quite a few test use the header config to add some more code inside the generated bindings, C or preprocessor code doesn't work well in a java source file :P)

All tests are green, but that doesn't mean all the generated bindings are correct, some cases may fail at runtime as I didn't test all cases in a real program. I did however generate and use the bindings on a non trivial lib I'm developing and they worked as expected.

@fredszaq
Copy link
Contributor Author

fredszaq commented Sep 7, 2023

rebased with the changes from #853

@coolbluewater
Copy link

coolbluewater commented Mar 12, 2024

@fredszaq very cool! Currently I hand-author a Kotlin bridge class, e.g.:

package com.foo.bar
import android.view.Surface
class RustBridge {
    init { System.loadLibrary("mylibrary") }
    external fun enterFrame(rustObj: Long)
}

and use the following Rust code with the 'jni_fn' attribute to ensure the name is correct:

#[no_mangle]
#[jni_fn("com.foo.bar.RustBridge")]
pub fn enterFrame(_env: *mut JNIEnv, _: JClass, obj: jlong) {
    let obj = unsafe { &mut *(obj as *mut WgpuCanvas) };
    obj.enter_frame();
}

Will your backend be able to generate the kotlin bindings for this type? Also, will it handle the rust name conversion without needing the jni_fn crate?
Thanks!

@fredszaq
Copy link
Contributor Author

Hi @coolbluewater ! The generated bindings use JNA. JNA works a bit differently than JNI (which your example uses) even though JNA uses JNI under the hood.

When using JNI, the native code needs to be aware that it is called by a JVM. This is done for rust via the #[jni_fn()] proc macro.

When using JNA, the native code doesn't need to be aware it is called from a JVM, as JNA is able to work with plain C compatible symbols. This means you don't have to have specific symbols for your lib for it to be able to be loaded from the jvm.

This means the rust code would probably look something like that

#[no_mangle]
pub fn enterFrame(obj: *mut WgpuCanvas) {
   unsafe{ obj.enterFrame}
}

and the koltin code would look like that

class RustBridge {
    fun enterFrame(rustObj: MyLib.WgpuCanvasByReference) {
         MyLib.INSTANCE.entrerFrame(rustObj)
    }
}

With the code for the class MyLib beeing generated.

You can configure the name and the package of the generated class (actually the public api is an interface) in cbingen's config

[java_jna]
package = "my.package"
interface_name = "MyLib"

@coolbluewater
Copy link

@fredszaq thanks for your response!
I need the JNIEnv and also need to pass jobjects from Kotlin to Rust.
Is this possible in some way?

@fredszaq
Copy link
Contributor Author

Not sure you want to mix JNI and JNA as the approaches are different and I'm not sure you'll be able to get access to the JNIEnv via JNA.

Writing a java-jni language backend that expects the rust code to be JNI aware could be a solution if you really need to access the JNIEnv/jobjects from the rust code. The the java-jna language backend in this PR kinda expects you are using plain old C compatible symbols.

@fredszaq
Copy link
Contributor Author

@emilio I rebased this with the latest master, following the merge of #942

src/bindgen/config.rs Outdated Show resolved Hide resolved
src/bindgen/config.rs Outdated Show resolved Hide resolved
deserialize_enum_str!(Language);

impl Language {
pub(crate) fn typedef(self) -> &'static str {
match self {
Language::Cxx | Language::C => "typedef",
Language::Cython => "ctypedef",
_ => unimplemented!(),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's list JNA explicitly. But why can't it hit it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this typedef function is only used by the enum code

@@ -721,6 +721,7 @@ impl Enum {
write!(out, "{}enum {}", config.style.cython_def(), tag_name);
}
}
_ => unimplemented!(),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same, let's at least keep the language listed. But why can't it be reached? Worth a comment.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code is really C (and derivatives) oriented, even though it lives in the IR, the impl of enums for java ends up not using this at all

src/bindgen/language_backend/java_jna.rs Outdated Show resolved Hide resolved
src/bindgen/language_backend/java_jna.rs Outdated Show resolved Hide resolved
src/bindgen/language_backend/java_jna.rs Outdated Show resolved Hide resolved
IntKind::B8 => "byte",
IntKind::B16 => "short",
IntKind::B32 => "int",
IntKind::B64 => "long",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also not right, this doesn't seem guaranteed to be 64 bytes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

long is 64 bytes in java

IntKind::B8 => JnaIntegerType::Byte,
IntKind::B16 => JnaIntegerType::Short,
IntKind::B32 => JnaIntegerType::Int,
IntKind::B64 => JnaIntegerType::Long,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also not right.

IntKind::Short => JnaIntegerType::Short,
IntKind::Int => JnaIntegerType::Int,
IntKind::Long => JnaIntegerType::NativeLong,
IntKind::LongLong => JnaIntegerType::Long,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LongLong is not Long, for sure.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Long is 64 bytes in java (the C long is NativeLong)

@fredszaq fredszaq requested a review from emilio April 24, 2024 20:51
@ZhaoXiangXML
Copy link

I'm looking forward for this one to be merged. I'm working on a C# backend that has some common ground with Java.

This builds on mozilla#853 and adds a new language backend capable of
generating java bindings.

The generated bindings use [JNA](https://github.com/java-native-access/jna)
(I used the last version 5.13.0 while developping but this will probably
work with earlier ones as well)

Most of the new code lives in `bindgen/language_backend/java_jna.rs` and
is pretty straightfowards generation as most on the complicated things were done
in the previous refactoring.

The test suite has been updated to generate and compile the bindings for
java when running. On nice thing gained with this is the possibilibty to
put a configuration file specific to a language alongside the stardard
one and the test will take this one instead of the default one if it
exists (quite a few test use the `header` config to add some more code
inside the generated bindings, C or preprocessor code doesn't work well
in a java source file :P)

All tests are green, but that doesn't mean all the generated bindings
are correct, some cases may fail at runtime as I didn't test all
cases in a real program. I did however generate and use the bindings on a
non trivial lib I'm developping and they worked as expected.
@fredszaq
Copy link
Contributor Author

fredszaq commented Aug 30, 2024

Hi @emilio, I just rebased this on the latest master as there were some conflicts. It would be great if you have time to review again (I know this is a big piece). Regading length of various java types, a good documentation can be found here https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html
We've been using bindings generated by this for a while at work and didn't have any problems with them

@Larkooo
Copy link

Larkooo commented Sep 16, 2024

It would be great if we could have typed enums support.
We can use unions from the JNA API for that; https://java-native-access.github.io/jna/4.2.1/com/sun/jna/Union.html.

@Larkooo
Copy link

Larkooo commented Sep 16, 2024

This is very rough: https://github.com/Larkooo/cbindgen/tree/java-jna-backend but this should handle typed enums with unions and decorates them with a tag which is the original generated enum

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants