Skip to content

Commit 2bba966

Browse files
authored
Add section on JNI / FFI, Android 16KB memory alignment (#3575)
1 parent 689317d commit 2bba966

File tree

1 file changed

+123
-0
lines changed

1 file changed

+123
-0
lines changed

src/content/docs/develop/Plugins/develop-mobile.mdx

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,129 @@ Use a nullable type and set the default value on the command function instead.
355355
Required arguments are defined as `let <argumentName>: Type`
356356
:::
357357

358+
## Calling Rust From Mobile Plugins
359+
360+
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.
361+
362+
### Android
363+
364+
In your plugin's `Cargo.toml`, add the jni crate as a dependency:
365+
366+
```toml
367+
[target.'cfg(target_os = "android")'.dependencies]
368+
jni = "0.21"
369+
```
370+
371+
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.
372+
373+
```kotlin
374+
private const val TAG = "MyPlugin"
375+
376+
init {
377+
try {
378+
// Load the native library (libapp_lib.so)
379+
// This is the shared library built by Cargo with crate-type = ["cdylib"]
380+
System.loadLibrary("app_lib")
381+
Log.d(TAG, "Successfully loaded libapp_lib.so")
382+
} catch (e: UnsatisfiedLinkError) {
383+
Log.e(TAG, "Failed to load libapp_lib.so", e)
384+
throw e
385+
}
386+
}
387+
388+
external fun helloWorld(name: String): String?
389+
```
390+
391+
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:
392+
393+
```rust
394+
#[cfg(target_os = "android")]
395+
#[no_mangle]
396+
pub extern "system" fn Java_com_example_HelloWorld_helloWorld(
397+
mut env: JNIEnv,
398+
_class: JClass,
399+
name: JString,
400+
) -> jstring {
401+
log::debug!("Calling JNI Hello World!");
402+
let result = format!("Hello, {}!", name);
403+
404+
match env.new_string(result) {
405+
Ok(jstr) => jstr.into_raw(),
406+
Err(e) => {
407+
log::error!("Failed to create JString: {}", e);
408+
std::ptr::null_mut()
409+
}
410+
}
411+
}
412+
```
413+
414+
### iOS
415+
416+
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:
417+
418+
```swift
419+
@_silgen_name("hello_world_ffi")
420+
private static func helloWorldFFI(_ name: UnsafePointer<CChar>) -> UnsafeMutablePointer<CChar>?
421+
422+
@_silgen_name("free_hello_result_ffi")
423+
private static func freeHelloResult(_ result: UnsafeMutablePointer<CChar>)
424+
425+
static func helloWorld(name: String) -> String? {
426+
// Call Rust FFI
427+
let resultPtr = name.withCString({ helloWorldFFI($0) })
428+
429+
// Convert C string to Swift String
430+
let result = String(cString: resultPtr)
431+
432+
// Free the C string
433+
freeHelloResult(resultPtr)
434+
435+
return result
436+
}
437+
438+
```
439+
440+
Then, implement the Rust side. The `extern` functions here must match the `@_silgen_name` annotations on the Swift side:
441+
442+
```rust
443+
#[no_mangle]
444+
pub unsafe extern "C" fn hello_world_ffi(c_name: *const c_char) -> *mut c_char {
445+
let name = match CStr::from_ptr(c_name).to_str() {
446+
Ok(s) => s,
447+
Err(e) => {
448+
log::error!("[iOS FFI] Failed to convert C string: {}", e);
449+
return std::ptr::null_mut();
450+
}
451+
};
452+
453+
let result = format!("Hello, {}!", name);
454+
455+
match CString::new(result) {
456+
Ok(c_str) => c_str.into_raw(),
457+
Err(e) => {
458+
log::error!("[iOS FFI] Failed to create C string: {}", e);
459+
std::ptr::null_mut()
460+
}
461+
}
462+
}
463+
464+
#[no_mangle]
465+
pub unsafe extern "C" fn free_hello_result_ffi(result: *mut c_char) {
466+
if !result.is_null() {
467+
drop(CString::from_raw(result));
468+
}
469+
}
470+
```
471+
472+
## Android 16KB Memory Pages
473+
474+
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`:
475+
476+
```toml
477+
[target.aarch64-linux-android]
478+
rustflags = ["-C", "link-arg=-Wl,-z,max-page-size=16384"]
479+
```
480+
358481
## Permissions
359482

360483
If a plugin requires permissions from the end user, Tauri simplifies the process of checking and requesting permissions.

0 commit comments

Comments
 (0)