From c28c41d3b461d207a99c08dce542dfb7dd7c24c4 Mon Sep 17 00:00:00 2001 From: Bjorn Beishline <75190918+BjornTheProgrammer@users.noreply.github.com> Date: Fri, 30 Jan 2026 17:48:53 -0700 Subject: [PATCH 1/5] Add in dynamic registration of native functions --- rust/src/api/mod.rs | 96 ++++++++++++++++++- .../j4rs/tests/TestDynamicRegister.java | 5 + 2 files changed, 98 insertions(+), 3 deletions(-) create mode 100644 test-resources/java/src/main/java/org/astonbitecode/j4rs/tests/TestDynamicRegister.java diff --git a/rust/src/api/mod.rs b/rust/src/api/mod.rs index a90d251..8cd6107 100644 --- a/rust/src/api/mod.rs +++ b/rust/src/api/mod.rs @@ -16,18 +16,18 @@ use std::any::{Any, TypeId}; use std::collections::HashMap; use std::convert::TryFrom; use std::env; +use std::ffi::CString; use std::ops::Drop; use std::os::raw::c_void; use std::path::{Path, PathBuf}; use std::ptr; +use std::str::FromStr; use std::sync::mpsc::channel; use std::{fs, thread, time}; use std::borrow::Borrow; use jni_sys::{ - self, jint, jobject, jsize, jstring, JNIEnv, JavaVM, JavaVMInitArgs, JavaVMOption, - JNI_EDETACHED, JNI_EEXIST, JNI_EINVAL, JNI_ENOMEM, JNI_ERR, JNI_EVERSION, JNI_OK, JNI_TRUE, - JNI_VERSION_1_6, + self, JNI_EDETACHED, JNI_EEXIST, JNI_EINVAL, JNI_ENOMEM, JNI_ERR, JNI_EVERSION, JNI_OK, JNI_TRUE, JNI_VERSION_1_6, JNIEnv, JNINativeMethod, JavaVM, JavaVMInitArgs, JavaVMOption, jint, jobject, jsize, jstring }; use libc::c_char; use serde::de::DeserializeOwned; @@ -1659,6 +1659,65 @@ impl Jvm { thread::yield_now(); } } + + + /// Dynamically registers native methods for a given class. + pub fn register_natives( + &self, + class_name: &str, + methods: Vec, + ) -> Result<(), J4RsError> { + if self.jni_env.is_null() { + Err(J4RsError::RustError( + "Cannot register natives without jni environment".to_string(), + ))? + } + + let methods: Vec<_> = methods + .into_iter() + .map(|method| JNINativeMethod { + name: CString::from_str(&method.name).unwrap().into_raw(), + signature: CString::from_str(&method.signature).unwrap().into_raw(), + fnPtr: method.fn_ptr, + }) + .collect(); + + unsafe { + let class = crate::api_tweaks::find_class(self.jni_env, class_name)?; + let register_natives = (**self.jni_env).v1_6.RegisterNatives; + let result = + (register_natives)(self.jni_env, class, methods.as_ptr(), methods.len() as i32); + + if result != 0 { + Err(J4RsError::RustError( + "Failed to register natives".to_string(), + ))? + } + } + + Ok(()) + } +} + +#[derive(Debug, Clone)] +pub struct NativeMethod { + name: String, + signature: String, + fn_ptr: *mut c_void, +} + +impl NativeMethod { + pub fn new, S2: Into>( + name: S1, + signature: S2, + fn_ptr: *mut c_void, + ) -> Self { + NativeMethod { + name: name.into(), + signature: signature.into(), + fn_ptr, + } + } } impl Drop for Jvm { @@ -2447,4 +2506,35 @@ mod api_unit_tests { Ok(()) } + + #[test] + fn test_dynamic_register() -> errors::Result<()> { + let jvm = create_tests_jvm()?; + + extern "C" fn hello(jni_env: *mut JNIEnv, _this: jobject) -> jstring { + unsafe { + let cstring = std::ffi::CString::new("Hello from Rust!").unwrap(); + ((**jni_env).v1_6.NewStringUTF)(jni_env, cstring.as_ptr()) + } + } + + jvm.register_natives( + "org/astonbitecode/j4rs/tests/TestDynamicRegister", + vec![NativeMethod::new( + "sayHello", + "()Ljava/lang/String;", + hello as *mut c_void, + )], + )?; + + let instance = jvm.create_instance( + "org.astonbitecode.j4rs.tests.TestDynamicRegister", + InvocationArg::empty(), + )?; + let result: String = + jvm.to_rust(jvm.invoke(&instance, "sayHello", InvocationArg::empty())?)?; + + assert_eq!(result, "Hello from Rust!"); + Ok(()) + } } diff --git a/test-resources/java/src/main/java/org/astonbitecode/j4rs/tests/TestDynamicRegister.java b/test-resources/java/src/main/java/org/astonbitecode/j4rs/tests/TestDynamicRegister.java new file mode 100644 index 0000000..fa2a461 --- /dev/null +++ b/test-resources/java/src/main/java/org/astonbitecode/j4rs/tests/TestDynamicRegister.java @@ -0,0 +1,5 @@ +package org.astonbitecode.j4rs.tests; + +public class TestDynamicRegister { + public native String sayHello(); +} From 7e0da7f0e64c835bd230f3761f57f236a5ec332b Mon Sep 17 00:00:00 2001 From: Bjorn Beishline <75190918+BjornTheProgrammer@users.noreply.github.com> Date: Fri, 30 Jan 2026 17:55:19 -0700 Subject: [PATCH 2/5] Add unregister function --- rust/src/api/mod.rs | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/rust/src/api/mod.rs b/rust/src/api/mod.rs index 8cd6107..310a325 100644 --- a/rust/src/api/mod.rs +++ b/rust/src/api/mod.rs @@ -1666,7 +1666,7 @@ impl Jvm { &self, class_name: &str, methods: Vec, - ) -> Result<(), J4RsError> { + ) -> errors::Result<()> { if self.jni_env.is_null() { Err(J4RsError::RustError( "Cannot register natives without jni environment".to_string(), @@ -1697,6 +1697,23 @@ impl Jvm { Ok(()) } + + pub fn unregister_native(&self, class_name: &str) -> errors::Result<()> { + unsafe { + let class = crate::api_tweaks::find_class(self.jni_env, class_name)?; + let unregister_natives = (**self.jni_env).v1_6.UnregisterNatives; + let result = + (unregister_natives)(self.jni_env, class); + + if result != 0 { + Err(J4RsError::RustError( + "Failed to unregister natives".to_string(), + ))? + } + + Ok(()) + } + } } #[derive(Debug, Clone)] @@ -2535,6 +2552,16 @@ mod api_unit_tests { jvm.to_rust(jvm.invoke(&instance, "sayHello", InvocationArg::empty())?)?; assert_eq!(result, "Hello from Rust!"); + + let result = jvm.unregister_native("org/astonbitecode/j4rs/tests/TestDynamicRegister"); + assert_eq!(result, Ok(())); + + let result = jvm.invoke(&instance, "sayHello", InvocationArg::empty()); + + assert!(result.is_err()); + let exception_string = format!("{}",result.err().unwrap()); + assert!(exception_string.contains("java.lang.UnsatisfiedLinkError")); + Ok(()) } } From 19a0b47cc45f19b93679d974ae8ce4ab78d3741a Mon Sep 17 00:00:00 2001 From: Bjorn Beishline <75190918+BjornTheProgrammer@users.noreply.github.com> Date: Fri, 30 Jan 2026 17:59:31 -0700 Subject: [PATCH 3/5] Add better documentation --- rust/src/api/mod.rs | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/rust/src/api/mod.rs b/rust/src/api/mod.rs index 310a325..4f25c18 100644 --- a/rust/src/api/mod.rs +++ b/rust/src/api/mod.rs @@ -1662,6 +1662,26 @@ impl Jvm { /// Dynamically registers native methods for a given class. + /// + /// # Example + /// + /// ```rust + /// extern "C" fn hello(jni_env: *mut jni_sys::JNIEnv, _this: jobject) -> jstring { + /// unsafe { + /// let cstring = std::ffi::CString::new("Hello from Rust!").unwrap(); + /// ((**jni_env).v1_6.NewStringUTF)(jni_env, cstring.as_ptr()) + /// } + /// } + /// + /// jvm.register_natives( + /// "org/astonbitecode/j4rs/tests/TestDynamicRegister", + /// vec![NativeMethod::new( + /// "sayHello", + /// "()Ljava/lang/String;", + /// hello as *mut c_void, + /// )], + /// )?; + /// ``` pub fn register_natives( &self, class_name: &str, @@ -1698,6 +1718,15 @@ impl Jvm { Ok(()) } + /// Unregisters native methods for a given class. + /// + /// # Example + /// + /// ```rust + /// jvm.unregister_native( + /// "org/astonbitecode/j4rs/tests/TestDynamicRegister", + /// )?; + /// ``` pub fn unregister_native(&self, class_name: &str) -> errors::Result<()> { unsafe { let class = crate::api_tweaks::find_class(self.jni_env, class_name)?; From c8245205b1c6c1dee8390b2da1d07406a055b767 Mon Sep 17 00:00:00 2001 From: Bjorn Beishline <75190918+BjornTheProgrammer@users.noreply.github.com> Date: Fri, 30 Jan 2026 18:11:00 -0700 Subject: [PATCH 4/5] Make examples no_run --- rust/src/api/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rust/src/api/mod.rs b/rust/src/api/mod.rs index 4f25c18..7f03d39 100644 --- a/rust/src/api/mod.rs +++ b/rust/src/api/mod.rs @@ -1665,7 +1665,7 @@ impl Jvm { /// /// # Example /// - /// ```rust + /// ```no_run /// extern "C" fn hello(jni_env: *mut jni_sys::JNIEnv, _this: jobject) -> jstring { /// unsafe { /// let cstring = std::ffi::CString::new("Hello from Rust!").unwrap(); @@ -1722,7 +1722,7 @@ impl Jvm { /// /// # Example /// - /// ```rust + /// ```no_run /// jvm.unregister_native( /// "org/astonbitecode/j4rs/tests/TestDynamicRegister", /// )?; From 87d705b81881d453facaa3bf50858feaa3222d48 Mon Sep 17 00:00:00 2001 From: Bjorn Beishline <75190918+BjornTheProgrammer@users.noreply.github.com> Date: Fri, 30 Jan 2026 18:20:21 -0700 Subject: [PATCH 5/5] Fix rust doc issues --- rust/src/api/mod.rs | 15 +++++++++++---- rust/src/lib.rs | 1 + 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/rust/src/api/mod.rs b/rust/src/api/mod.rs index 7f03d39..04955a0 100644 --- a/rust/src/api/mod.rs +++ b/rust/src/api/mod.rs @@ -1666,21 +1666,25 @@ impl Jvm { /// # Example /// /// ```no_run - /// extern "C" fn hello(jni_env: *mut jni_sys::JNIEnv, _this: jobject) -> jstring { + /// use j4rs::prelude::*; + /// use j4rs::{NativeMethod, JvmBuilder}; + /// + /// extern "C" fn hello(jni_env: *mut JNIEnv, _this: jobject) -> jobject { /// unsafe { /// let cstring = std::ffi::CString::new("Hello from Rust!").unwrap(); /// ((**jni_env).v1_6.NewStringUTF)(jni_env, cstring.as_ptr()) /// } /// } /// + /// let jvm = JvmBuilder::new().build().unwrap(); /// jvm.register_natives( /// "org/astonbitecode/j4rs/tests/TestDynamicRegister", /// vec![NativeMethod::new( /// "sayHello", /// "()Ljava/lang/String;", - /// hello as *mut c_void, + /// hello as *mut core::ffi::c_void, /// )], - /// )?; + /// ).unwrap(); /// ``` pub fn register_natives( &self, @@ -1723,9 +1727,12 @@ impl Jvm { /// # Example /// /// ```no_run + /// use j4rs::JvmBuilder; + /// + /// let jvm = JvmBuilder::new().build().unwrap(); /// jvm.unregister_native( /// "org/astonbitecode/j4rs/tests/TestDynamicRegister", - /// )?; + /// ).unwrap(); /// ``` pub fn unregister_native(&self, class_name: &str) -> errors::Result<()> { unsafe { diff --git a/rust/src/lib.rs b/rust/src/lib.rs index ac0a1b4..063f2c9 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -45,6 +45,7 @@ pub use self::api::JavaClass; pub use self::api::JavaOpt; pub use self::api::Jvm; pub use self::api::JvmBuilder; +pub use self::api::NativeMethod; pub use self::api::Null; pub use self::api_tweaks::{get_created_java_vms, set_java_vm}; pub use self::jni_utils::jstring_to_rust_string;