Skip to content

Commit

Permalink
Add jnat macro
Browse files Browse the repository at this point in the history
  • Loading branch information
shreyasm-dev committed Jul 26, 2023
1 parent e91bb36 commit 917ef57
Show file tree
Hide file tree
Showing 11 changed files with 974 additions and 17 deletions.
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"rust-analyzer.showUnlinkedFileNotification": false
}
5 changes: 3 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "jnat"
version = "0.4.3"
version = "0.5.0"
edition = "2021"
license = "MIT"
description = "A wrapper around the jni crate"
Expand All @@ -11,6 +11,7 @@ documentation = "https://docs.rs/jnat/"

[dependencies]
jni = "0.21.1"
jnat-macros = { path = "jnat-macros", optional = true }

[[test]]
name = "integration"
Expand All @@ -23,5 +24,5 @@ log = "0.4.19"
pretty_env_logger = "0.5.0"

[features]
default = ["jni"]
default = ["jni", "jnat-macros"]
jni = []
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,13 @@ Add the following to `src/lib.rs`:
```rust
use jnat::{
env::{Class, Env},
jnat_macros::jnat,
jni::{objects::JClass, JNIEnv}, // jni crate, re-exported by jnat
signature::{Signature, Type},
};

#[no_mangle]
pub extern "system" fn Java_HelloWorld_caller(env: JNIEnv, class: JClass) {
#[jnat(HelloWorld)]
fn caller(env: JNIEnv, class: JClass) {
let mut env = env;
let mut env = Env::new(&mut env);
let mut class = Class::new(&mut env, class);
Expand Down Expand Up @@ -62,3 +63,4 @@ Compile the java file with `javac -h . HelloWorld.java`. Then, run `java -Djava.
## Notes

- Jnat re-exports jni by default. If you want to use a different version of jni, you can disable either the default features or the `jni` feature.
- Jnat exports a macro, `jnat::jnat_macros::jnat` (seen in the example above), which is used to generate the `Java_HelloWorld_caller` function. This macro can be disabled by disabling either the default features or the `jni-macros` feature. Note that the macro keeps the original function to prevent unintuitive behavior (so you can, in your Rust code, call just `example()` instead of `Java_org_example_Class_example()` while still allowing Java to call it).
884 changes: 884 additions & 0 deletions integration/java/hs_err_pid96124.log

Large diffs are not rendered by default.

7 changes: 4 additions & 3 deletions integration/tests/lib/call_method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,17 @@ extern crate jnat;

use jnat::{
env::Env,
jnat_macros::jnat,
jni::{
objects::{JClass, JObject},
JNIEnv,
},
signature::{Signature, Type},
value::{Value, Object},
value::{Object, Value},
};

#[no_mangle]
pub extern "system" fn Java_CallMethod_caller(env: JNIEnv, _: JClass, instance: JObject) {
#[jnat(CallMethod)]
fn caller(env: JNIEnv, _: JClass, instance: JObject) {
let mut env = env;
let mut env = Env::new(&mut env);

Expand Down
14 changes: 6 additions & 8 deletions integration/tests/lib/call_static_method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,18 @@ extern crate jnat;

use jnat::{
env::Env,
jni::{
objects::JClass,
JNIEnv,
},
jnat_macros::jnat,
jni::{objects::JClass, JNIEnv},
signature::{Signature, Type},
value::{Value, Object},
value::{Object, Value},
};

#[no_mangle]
pub extern "system" fn Java_CallStaticMethod_caller(env: JNIEnv, _: JClass) {
#[jnat(CallStaticMethod)]
fn caller(env: JNIEnv, _: JClass) {
let mut env = env;
let mut env = Env::new(&mut env);

// Alternatively (remember to rename the second parameter of Java_CallStaticMethod_caller to `class`):
// Alternatively (remember to rename the second parameter of caller to `class`):
// let mut class = Class::new(&env, class);
let mut class = env.class("CallStaticMethod").expect("Failed to find class");

Expand Down
5 changes: 3 additions & 2 deletions integration/tests/lib/hello.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@ extern crate jnat;

use jnat::{
env::Env,
jnat_macros::jnat,
jni::{
objects::{JClass, JString},
sys::jstring,
JNIEnv,
},
};

#[no_mangle]
pub extern "system" fn Java_Hello_hello(mut env: JNIEnv, _: JClass, name: JString) -> jstring {
#[jnat(Hello)]
fn hello(mut env: JNIEnv, _: JClass, name: JString) -> jstring {
let mut env = Env::new(&env);

let message: String = env.get_string(&name).expect("Failed to get name").into();
Expand Down
2 changes: 2 additions & 0 deletions jnat-macros/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/target
/Cargo.lock
14 changes: 14 additions & 0 deletions jnat-macros/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[package]
name = "jnat-macros"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
proc-macro2 = "1.0.66"
quote = "1.0.32"
syn = { version = "2.0.27", features = ["full"] }

[lib]
proc-macro = true
48 changes: 48 additions & 0 deletions jnat-macros/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
use proc_macro::TokenStream;
use quote::ToTokens;
use syn::{parse::Parser, parse_macro_input, punctuated::Punctuated, Ident, ItemFn, Path, Token};

#[proc_macro_attribute]
pub fn jnat(args: TokenStream, item: TokenStream) -> TokenStream {
let args = Punctuated::<Path, Token![.]>::parse_terminated
.parse(args)
.unwrap();
let item = parse_macro_input!(item as ItemFn);

let method_name = args
.iter()
.map(|x| x.to_token_stream().to_string())
.collect::<Vec<String>>()
.join("_");
let method_name = format!("Java_{}_{}", method_name, item.sig.ident.to_string());

let item = item.clone();
let mut native_fn = item.clone();

native_fn.sig.ident = Ident::new(&method_name, native_fn.sig.ident.span());
native_fn.sig.abi = Some(syn::parse_quote!(extern "system"));

// Changing visibility and params is probably unexpected behavior, just leaving this here for reference

/* item
.sig
.inputs
.insert(0, syn::parse_quote!(mut env: jnat::jni::JNIEnv));
item
.sig
.inputs
.insert(1, syn::parse_quote!(class: jnat::jni::objects::JClass)); */

/* native_fn.vis = Visibility::Public(Pub {
span: Span::call_site(),
}); */

// We want #[no_mangle] and #[allow(non_snake_case)]

let out = "#[no_mangle]\n#[allow(non_snake_case)]\n".to_string()
+ &native_fn.to_token_stream().to_string()
+ "\n"
+ &item.to_token_stream().to_string();

out.parse().unwrap()
}
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@ mod test;

#[cfg(feature = "jni")]
pub use jni;

#[cfg(feature = "jnat-macros")]
pub use jnat_macros;

0 comments on commit 917ef57

Please sign in to comment.