From 317d0604e07a1ad9f40d898465fadb0ff003a40b Mon Sep 17 00:00:00 2001 From: Ben Clarke Date: Thu, 6 Nov 2025 21:40:41 -0500 Subject: [PATCH 1/5] Add section on JNI / FFI, Android 16KB memory alignment --- .../docs/develop/Plugins/develop-mobile.mdx | 124 ++++++++++++++++++ 1 file changed, 124 insertions(+) diff --git a/src/content/docs/develop/Plugins/develop-mobile.mdx b/src/content/docs/develop/Plugins/develop-mobile.mdx index a9c9d1bded..02a352e206 100644 --- a/src/content/docs/develop/Plugins/develop-mobile.mdx +++ b/src/content/docs/develop/Plugins/develop-mobile.mdx @@ -355,6 +355,130 @@ Use a nullable type and set the default value on the command function instead. Required arguments are defined as `let : Type` ::: +## Calling Rust From Mobile Plugins + +It is often preferable to write plugin code in Rust, for performance and reusability. While Tauri doesn't directly provide a mechanism to call Rust from your plugin code, using JNI on Android and FFI on iOS allows plugins to call shared code, even when the application WebView is suspended. + +### Android + +In your plugin's `Cargo.toml`, add the jni crate as a dependency: + +```toml +[target.'cfg(target_os = "android")'.dependencies] +jni = "0.21" +``` + +Load the application library statically and define native functions in your Kotlin code: + +```kotlin +private const val TAG = "MyPlugin" + +init { + try { + // Load the native library (libapp_lib.so) + // This is the shared library built by Cargo with crate-type = ["cdylib"] + System.loadLibrary("app_lib") + Log.d(TAG, "Successfully loaded libapp_lib.so") + } catch (e: UnsatisfiedLinkError) { + Log.e(TAG, "Failed to load libapp_lib.so", e) + throw e + } +} + +external fun helloWorld(name: String): String? +``` + +Then in your plugin's Rust code, define the function JNI will look for: + +```rust +#[cfg(target_os = "android")] +#[no_mangle] +pub extern "system" fn Java_com_example_HelloWorld_helloWorld( + mut env: JNIEnv, + _class: JClass, + name: JString, +) -> jstring { + log::debug!("Calling JNI Hello World!"); + let result = format!("Hello, {}!", name); + + match env.new_string(result) { + Ok(jstr) => jstr.into_raw(), + Err(e) => { + log::error!("Failed to create JString: {}", e); + std::ptr::null_mut() + } + } +} +``` + +### iOS + +iOS only uses standard C FFI, so doesn't need any new dependencies. Add the hook in your Swift code, as well as any necessary cleanup: + +```swift +@_silgen_name("hello_world_ffi") +private static func helloWorldFFI(_ name: UnsafePointer) -> UnsafeMutablePointer? + +@_silgen_name("free_hello_result_ffi") +private static func freeHelloResult(_ result: UnsafeMutablePointer) + +static func helloWorld(name: String) -> String? { + // Call Rust FFI + let resultPtr = name.withCString({ helloWorldFFI($0) }) + + // Convert C string to Swift String + let result = String(cString: resultPtr) + + // Free the C string + freeHelloResult(resultPtr) + + return result +} + +``` + +Then, implement the Rust side: + +```rust +#[no_mangle] +pub unsafe extern "C" fn hello_world_ffi(c_name: *const c_char) -> *mut c_char { + let name = match CStr::from_ptr(c_name).to_str() { + Ok(s) => s, + Err(e) => { + log::error!("[iOS FFI] Failed to convert C string: {}", e); + return std::ptr::null_mut(); + } + }; + + let result = format!("Hello, {}!", name); + + match CString::new(result) { + Ok(c_str) => c_str.into_raw(), + Err(e) => { + log::error!("[iOS FFI] Failed to create C string: {}", e); + std::ptr::null_mut() + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn free_hello_result_ffi(result: *mut c_char) { + if !result.is_null() { + drop(CString::from_raw(result)); + } +} +``` + +## Android 16KB Memory Pages + +Google is moving to make 16KB memory pages a requirement in all new Android app submissions. In order to build Tauri apps that meet this requirement, add the following to `.cargo/config.toml`: + +```toml +[target.aarch64-linux-android] +rustflags = ["-C", "link-arg=-Wl,-z,max-page-size=16384"] +``` + + ## Permissions If a plugin requires permissions from the end user, Tauri simplifies the process of checking and requesting permissions. From b0eb8ab87048e11c76ffa8bcad032c88db1dec56 Mon Sep 17 00:00:00 2001 From: Ben Clarke Date: Thu, 6 Nov 2025 22:41:26 -0500 Subject: [PATCH 2/5] Fix prettier --- src/content/docs/develop/Plugins/develop-mobile.mdx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/content/docs/develop/Plugins/develop-mobile.mdx b/src/content/docs/develop/Plugins/develop-mobile.mdx index 02a352e206..328e2f81f6 100644 --- a/src/content/docs/develop/Plugins/develop-mobile.mdx +++ b/src/content/docs/develop/Plugins/develop-mobile.mdx @@ -478,7 +478,6 @@ Google is moving to make 16KB memory pages a requirement in all new Android app rustflags = ["-C", "link-arg=-Wl,-z,max-page-size=16384"] ``` - ## Permissions If a plugin requires permissions from the end user, Tauri simplifies the process of checking and requesting permissions. From caa59df0a682a23af87a56f886b7583d3471239c Mon Sep 17 00:00:00 2001 From: Ben Clarke Date: Thu, 13 Nov 2025 07:25:03 -0500 Subject: [PATCH 3/5] Clarify naming conventions --- src/content/docs/develop/Plugins/develop-mobile.mdx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/content/docs/develop/Plugins/develop-mobile.mdx b/src/content/docs/develop/Plugins/develop-mobile.mdx index 328e2f81f6..b8e6c9feeb 100644 --- a/src/content/docs/develop/Plugins/develop-mobile.mdx +++ b/src/content/docs/develop/Plugins/develop-mobile.mdx @@ -368,7 +368,7 @@ In your plugin's `Cargo.toml`, add the jni crate as a dependency: jni = "0.21" ``` -Load the application library statically and define native functions in your Kotlin code: +Load the application library statically and define native functions in your Kotlin code. In this example, the Kotlin class is `com.example.HelloWorld`, we need to reference the full package name from the Rust side. ```kotlin private const val TAG = "MyPlugin" @@ -388,7 +388,7 @@ init { external fun helloWorld(name: String): String? ``` -Then in your plugin's Rust code, define the function JNI will look for: +Then in your plugin's Rust code, define the function JNI will look for. The function format is `Java_package_class_method`, so for our class above this becomes `Java_com_example_HelloWorld_helloWorld` to call our `helloWorld` method: ```rust #[cfg(target_os = "android")] @@ -413,7 +413,7 @@ pub extern "system" fn Java_com_example_HelloWorld_helloWorld( ### iOS -iOS only uses standard C FFI, so doesn't need any new dependencies. Add the hook in your Swift code, as well as any necessary cleanup: +iOS only uses standard C FFI, so doesn't need any new dependencies. Add the hook in your Swift code, as well as any necessary cleanup. These functions can be named anything valid, but must be annotated with `@_silgen_name(FFI_FUNC)`, where FFI_FUNC is a function name to be called from Rust: ```swift @_silgen_name("hello_world_ffi") @@ -437,7 +437,7 @@ static func helloWorld(name: String) -> String? { ``` -Then, implement the Rust side: +Then, implement the Rust side. The `extern` functions here must match the `@_silgen_name` annotations on the Swift side: ```rust #[no_mangle] From e37e3e9c34a2f8586f20fac15f3b26fd9a19576c Mon Sep 17 00:00:00 2001 From: Ben Clarke Date: Thu, 13 Nov 2025 07:28:19 -0500 Subject: [PATCH 4/5] Clarify wording --- src/content/docs/develop/Plugins/develop-mobile.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/docs/develop/Plugins/develop-mobile.mdx b/src/content/docs/develop/Plugins/develop-mobile.mdx index b8e6c9feeb..e2f8621853 100644 --- a/src/content/docs/develop/Plugins/develop-mobile.mdx +++ b/src/content/docs/develop/Plugins/develop-mobile.mdx @@ -388,7 +388,7 @@ init { external fun helloWorld(name: String): String? ``` -Then in your plugin's Rust code, define the function JNI will look for. The function format is `Java_package_class_method`, so for our class above this becomes `Java_com_example_HelloWorld_helloWorld` to call our `helloWorld` method: +Then in your plugin's Rust code, define the function JNI will look for. The function format is `Java_package_class_method`, so for our class above this becomes `Java_com_example_HelloWorld_helloWorld` to get called by our `helloWorld` method: ```rust #[cfg(target_os = "android")] From 3a2f1c73cb8469cc3aa1a0c534503a28d371b521 Mon Sep 17 00:00:00 2001 From: Ben Clarke Date: Thu, 13 Nov 2025 07:52:38 -0500 Subject: [PATCH 5/5] Update phrasing to suggest newer NDK to generate 16kB alignment --- src/content/docs/develop/Plugins/develop-mobile.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/docs/develop/Plugins/develop-mobile.mdx b/src/content/docs/develop/Plugins/develop-mobile.mdx index e2f8621853..86dd938824 100644 --- a/src/content/docs/develop/Plugins/develop-mobile.mdx +++ b/src/content/docs/develop/Plugins/develop-mobile.mdx @@ -471,7 +471,7 @@ pub unsafe extern "C" fn free_hello_result_ffi(result: *mut c_char) { ## Android 16KB Memory Pages -Google is moving to make 16KB memory pages a requirement in all new Android app submissions. In order to build Tauri apps that meet this requirement, add the following to `.cargo/config.toml`: +Google is moving to make 16KB memory pages a requirement in all new Android app submissions. Building with an NDK version 28 or higher should automatically generate bundles that meet this requirement, but in the event an older NDK version must be used or generated files aren't 16KB aligned, the following can be added to `.cargo/config.toml` to flag this to `rustc`: ```toml [target.aarch64-linux-android]