diff --git a/rust/src/api/mod.rs b/rust/src/api/mod.rs index a90d251..04955a0 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,118 @@ impl Jvm { thread::yield_now(); } } + + + /// Dynamically registers native methods for a given class. + /// + /// # Example + /// + /// ```no_run + /// 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 core::ffi::c_void, + /// )], + /// ).unwrap(); + /// ``` + pub fn register_natives( + &self, + class_name: &str, + methods: Vec, + ) -> errors::Result<()> { + 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(()) + } + + /// Unregisters native methods for a given class. + /// + /// # 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 { + 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)] +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 +2559,45 @@ 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!"); + + 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(()) + } } 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; 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(); +}