בדף הזה מפורט מידע על יומני Android, דוגמה ל-Rust AIDL, הוראות לקריאה ל-Rust מ-C והוראות לאינטראקציה בין Rust ל-C++ באמצעות CXX.
רישום ביומן ב-Android
הדוגמה הבאה מראה איך אפשר לתעד הודעות ביומן ב-logcat
(במכשיר) או ב-stdout
(במארח).
במודול Android.bp
, מוסיפים את liblogger
ואת liblog_rust
כיחסי תלות:
rust_binary {
name: "logging_test",
srcs: ["src/main.rs"],
rustlibs: [
"liblogger",
"liblog_rust",
],
}
לאחר מכן, מוסיפים את הקוד הזה למקור ב-Rust:
use log::{debug, error, LevelFilter};
fn main() {
let _init_success = logger::init(
logger::Config::default()
.with_tag_on_device("mytag")
.with_max_level(LevelFilter::Trace),
);
debug!("This is a debug message.");
error!("Something went wrong!");
}
כלומר, מוסיפים את שתי יחסי התלות שמוצגים למעלה (liblogger
ו-liblog_rust
), קוראים לשיטה init
פעם אחת (אפשר לקרוא לה יותר מפעם אחת במקרה הצורך) ומתעדים הודעות ביומן באמצעות המאקרוסים שסופקו. בlogger crate תוכלו למצוא רשימה של אפשרויות ההגדרה האפשריות.
ה-crate של ה-logger מספק ממשק API להגדרת מה שרוצים לתעד ביומן. בהתאם למקום שבו פועל הקוד (במכשיר או במארח, למשל כחלק מבדיקת צד המארח), ההודעות מתועדות ביומן באמצעות android_logger או env_logger.
דוגמה ל-Rust AIDL
בקטע הזה מוצגת דוגמה בסגנון Hello World לשימוש ב-AIDL עם Rust.
כבסיס, משתמשים בקטע AIDL Overview במדריך למפתחי Android, ויוצרים את external/rust/binder_example/aidl/com/example/android/IRemoteService.aidl
עם התוכן הבא בקובץ IRemoteService.aidl
:
// IRemoteService.aidl
package com.example.android;
// Declare any non-default types here with import statements
/** Example service interface */
interface IRemoteService {
/** Request the process ID of this service, to do evil things with it. */
int getPid();
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);
}
לאחר מכן, מגדירים את המודול aidl_interface
בקובץ external/rust/binder_example/aidl/Android.bp
. צריך להפעיל באופן מפורש את הקצה העורפי של Rust כי הוא לא מופעל כברירת מחדל.
aidl_interface {
name: "com.example.android.remoteservice",
srcs: [ "aidl/com/example/android/*.aidl", ],
unstable: true, // Add during development until the interface is stabilized.
backend: {
rust: {
// By default, the Rust backend is not enabled
enabled: true,
},
},
}
הקצה העורפי של AIDL הוא מחולל מקורות של Rust, כך שהוא פועל כמו מחוללי מקורות אחרים של Rust ויוצר ספריית Rust. מודולים אחרים של Rust יכולים להשתמש במודול הספרייה שנוצר ב-Rust כיחס תלות. לדוגמה לשימוש בספרייה שנוצרה כיחס תלות, אפשר להגדיר rust_library
כך ב-external/rust/binder_example/Android.bp
:
rust_library {
name: "libmyservice",
srcs: ["src/lib.rs"],
crate_name: "myservice",
rustlibs: [
"com.example.android.remoteservice-rust",
"libbinder_rs",
],
}
שימו לב שפורמט שם המודול של הספרייה שנוצרה באמצעות AIDL ומשמשת ב-rustlibs
הוא שם המודול aidl_interface
ואחריו -rust
. במקרה הזה, com.example.android.remoteservice-rust
.
לאחר מכן אפשר להפנות לממשק AIDL ב-src/lib.rs
באופן הבא:
// Note carefully the AIDL crates structure:
// * the AIDL module name: "com_example_android_remoteservice"
// * next "::aidl"
// * next the AIDL package name "::com::example::android"
// * the interface: "::IRemoteService"
// * finally, the 'BnRemoteService' and 'IRemoteService' submodules
//! This module implements the IRemoteService AIDL interface
use com_example_android_remoteservice::aidl::com::example::android::{
IRemoteService::{BnRemoteService, IRemoteService}
};
use binder::{
BinderFeatures, Interface, Result as BinderResult, Strong,
};
/// This struct is defined to implement IRemoteService AIDL interface.
pub struct MyService;
impl Interface for MyService {}
impl IRemoteService for MyService {
fn getPid(&self) -> BinderResult<i32> {
Ok(42)
}
fn basicTypes(&self, _: i32, _: i64, _: bool, _: f32, _: f64, _: &str) -> BinderResult<()> {
// Do something interesting...
Ok(())
}
}
לסיום, מפעילים את השירות בקובץ בינארי של Rust, כפי שמוצג בהמשך:
use myservice::MyService;
fn main() {
// [...]
let my_service = MyService;
let my_service_binder = BnRemoteService::new_binder(
my_service,
BinderFeatures::default(),
);
binder::add_service("myservice", my_service_binder.as_binder())
.expect("Failed to register service?");
// Does not return - spawn or perform any work you mean to do before this call.
binder::ProcessState::join_thread_pool()
}
דוגמה ל-Async Rust AIDL
בקטע הזה מוצגת דוגמה בסגנון Hello World לשימוש ב-AIDL עם Rust אסינכרוני.
בהמשך לדוגמה של RemoteService
, ספריית הקצה העורפי של AIDL שנוצרה כוללת ממשקים אסינכרונים שאפשר להשתמש בהם כדי להטמיע הטמעת שרת אסינכרוני לממשק AIDL RemoteService
.
אפשר להטמיע את ממשק השרת האסינכרוני שנוצר, IRemoteServiceAsyncServer
, באופן הבא:
use com_example_android_remoteservice::aidl::com::example::android::IRemoteService::{
BnRemoteService, IRemoteServiceAsyncServer,
};
use binder::{BinderFeatures, Interface, Result as BinderResult};
/// This struct is defined to implement IRemoteServiceAsyncServer AIDL interface.
pub struct MyAsyncService;
impl Interface for MyAsyncService {}
#[async_trait]
impl IRemoteServiceAsyncServer for MyAsyncService {
async fn getPid(&self) -> BinderResult<i32> {
//Do something interesting...
Ok(42)
}
async fn basicTypes(&self, _: i32, _: i64, _: bool, _: f32, _: f64,_: &str,) -> BinderResult<()> {
//Do something interesting...
Ok(())
}
}
אפשר להתחיל את ההטמעה של השרת האסינכרוני באופן הבא:
#[tokio::main(flavor = "multi_thread", worker_threads = 2)]
async fn main() {
binder::ProcessState::start_thread_pool();
let my_service = MyAsyncService;
let my_service_binder = BnRemoteService::new_async_binder(
my_service,
TokioRuntime(Handle::current()),
BinderFeatures::default(),
);
binder::add_service("myservice", my_service_binder.as_binder())
.expect("Failed to register service?");
task::block_in_place(move || {
binder::ProcessState::join_thread_pool();
});
}
הערה: הקריאה block_in_place נדרשת כדי לצאת מההקשר האסינכרוני שמאפשר ל-join_thread_pool
להשתמש ב-block_on באופן פנימי. הסיבה לכך היא ש-#[tokio::main]
עוטף את הקוד בקריאה ל-block_on
, ויכול להיות ש-join_thread_pool
יקרא ל-block_on
בזמן טיפול בעסקה נכנסת. קריאה ל-block_on
מתוך block_on
גורמת למצב חירום. אפשר גם למנוע את הבעיה הזו על ידי פיתוח סביבת זמן הריצה של tokio באופן ידני במקום להשתמש ב-#[tokio::main]
, ואז לבצע קריאה ל-join_thread_pool
מחוץ לשיטה block_on
.
בנוסף, הספרייה שנוצרה לקצה העורפי של Rust כוללת ממשק שמאפשר הטמעה של לקוח IRemoteServiceAsync
אסינכרוני ל-RemoteService
, שאפשר להטמיע באופן הבא:
use com_example_android_remoteservice::aidl::com::example::android::IRemoteService::IRemoteServiceAsync;
use binder_tokio::Tokio;
#[tokio::main(flavor = "current_thread")]
async fn main() {
let binder_service = binder_tokio::wait_for_interface::<dyn IRemoteServiceAsync<Tokio>>("myservice");
let my_client = binder_service.await.expect("Cannot find Remote Service");
let result = my_client.getPid().await;
match result {
Err(err) => panic!("Cannot get the process id from Remote Service {:?}", err),
Ok(p_id) => println!("PID = {}", p_id),
}
}
קריאה ל-Rust מ-C
בדוגמה הזו מוסבר איך לקרוא ל-Rust מ-C.
דוגמה לספריית Rust
מגדירים את הקובץ libsimple_printer
ב-external/rust/simple_printer/libsimple_printer.rs
באופן הבא:
//! A simple hello world example that can be called from C
#[no_mangle]
/// Print "Hello Rust!"
pub extern fn print_c_hello_rust() {
println!("Hello Rust!");
}
ספריית Rust חייבת להגדיר כותרות שמודולי C התלויים יכולים למשוך, ולכן צריך להגדיר את הכותרת external/rust/simple_printer/simple_printer.h
באופן הבא:
#ifndef SIMPLE_PRINTER_H
#define SIMPLE_PRINTER_H
void print_c_hello_rust();
#endif
מגדירים את external/rust/simple_printer/Android.bp
כפי שמופיע כאן:
rust_ffi {
name: "libsimple_c_printer",
crate_name: "simple_c_printer",
srcs: ["libsimple_c_printer.rs"],
// Define export_include_dirs so cc_binary knows where the headers are.
export_include_dirs: ["."],
}
קובץ בינארי לדוגמה ב-C
מגדירים את external/rust/c_hello_rust/main.c
באופן הבא:
#include "simple_printer.h"
int main() {
print_c_hello_rust();
return 0;
}
מגדירים את external/rust/c_hello_rust/Android.bp
באופן הבא:
cc_binary {
name: "c_hello_rust",
srcs: ["main.c"],
shared_libs: ["libsimple_c_printer"],
}
לבסוף, מריצים את ה-build באמצעות m c_hello_rust
.
יכולת פעולה הדדית בין Rust ל-Java
ה-crate jni
מספק יכולת פעולה הדדית של Rust עם Java דרך Java Native Interface (JNI). הוא מגדיר את הגדרות הסוגים הנדרשות ל-Rust כדי ליצור ספריית cdylib
ב-Rust שמתחבר ישירות ל-JNI של Java (JNIEnv
, JClass
, JString
וכו'). בניגוד לקישורים של C++ שמבצעים יצירת קוד דרך cxx
, באינטראקציה בין Java ל-JNI לא נדרש שלב של יצירת קוד במהלך ה-build. לכן אין צורך בתמיכה מיוחדת במערכת ה-build. קוד Java טוען את cdylib
שסופק על ידי Rust כמו כל ספרייה מקומית אחרת.
שימוש
השימוש בקוד Rust ובקוד Java מוסבר במסמכי התיעוד של jni
. יש לפעול לפי הדוגמה שמופיעה בקטע תחילת השימוש. אחרי שכותבים את src/lib.rs
, חוזרים לדף הזה כדי ללמוד איך ליצור את הספרייה באמצעות מערכת ה-build של Android.
הגדרת build
ב-Java, צריך לספק את ספריית Rust כ-cdylib
כדי שאפשר יהיה לטעון אותה באופן דינמי. ההגדרה של ספריית Rust ב-Soong היא:
rust_ffi_shared {
name: "libhello_jni",
crate_name: "hello_jni",
srcs: ["src/lib.rs"],
// The jni crate is required
rustlibs: ["libjni"],
}
ספריית Java כוללת את ספריית Rust בתור תלות required
. כך מוודאים שהיא מותקנת במכשיר לצד ספריית Java, למרות שהיא לא תלות בזמן ה-build:
java_library {
name: "libhelloworld",
[...]
required: ["libhellorust"]
[...]
}
לחלופין, אם אתם צריכים לכלול את ספריית Rust בקובץ AndroidManifest.xml
, מוסיפים את הספרייה ל-uses_libs
באופן הבא:
java_library {
name: "libhelloworld",
[...]
uses_libs: ["libhellorust"]
[...]
}
יכולת פעולה הדדית בין Rust ל-C++ באמצעות CXX
ה-crate CXX מספק FFI בטוח בין Rust לבין קבוצת משנה של C++. במסמכי התיעוד של CXX יש דוגמאות טובות לאופן שבו הוא פועל באופן כללי, ואנחנו ממליצים לקרוא אותם קודם כדי להכיר את הספרייה ואת האופן שבו היא יוצרת גשר בין C++ לבין Rust. בדוגמה הבאה מוסבר איך משתמשים ב-Android.
כדי לגרום ל-CXX ליצור את קוד ה-C++ ש-Rust קוראת אליו, מגדירים genrule
כדי להפעיל את CXX ו-cc_library_static
כדי לארוז את הקוד בספרייה. אם אתם מתכננים לקרוא לקוד Rust מ-C++ או להשתמש בסוגי נתונים שקיימים גם ב-C++ וגם ב-Rust, צריך להגדיר genrule שני (כדי ליצור כותרת C++ שמכילה את קישורי Rust).
cc_library_static {
name: "libcxx_test_cpp",
srcs: ["cxx_test.cpp"],
generated_headers: [
"cxx-bridge-header",
"libcxx_test_bridge_header"
],
generated_sources: ["libcxx_test_bridge_code"],
}
// Generate the C++ code that Rust calls into.
genrule {
name: "libcxx_test_bridge_code",
tools: ["cxxbridge"],
cmd: "$(location cxxbridge) $(in) > $(out)",
srcs: ["lib.rs"],
out: ["libcxx_test_cxx_generated.cc"],
}
// Generate a C++ header containing the C++ bindings
// to the Rust exported functions in lib.rs.
genrule {
name: "libcxx_test_bridge_header",
tools: ["cxxbridge"],
cmd: "$(location cxxbridge) $(in) --header > $(out)",
srcs: ["lib.rs"],
out: ["lib.rs.h"],
}
הכלי cxxbridge
משמש ליצירת הצד של C++ בגשר. לאחר מכן, הספרייה הסטטית libcxx_test_cpp
משמשת כחפיפה לקובץ ההפעלה של Rust:
rust_binary {
name: "cxx_test",
srcs: ["lib.rs"],
rustlibs: ["libcxx"],
static_libs: ["libcxx_test_cpp"],
}
בקבצים .cpp
ו-.hpp
, מגדירים את הפונקציות של C++ כרצונכם, באמצעות סוגי העטיפה של CXX.
לדוגמה, הגדרה של cxx_test.hpp
מכילה את הפרטים הבאים:
#pragma once
#include "rust/cxx.h"
#include "lib.rs.h"
int greet(rust::Str greetee);
בזמן ש-cxx_test.cpp
מכיל
#include "cxx_test.hpp"
#include "lib.rs.h"
#include <iostream>
int greet(rust::Str greetee) {
std::cout << "Hello, " << greetee << std::endl;
return get_num();
}
כדי להשתמש בזה מ-Rust, מגדירים גשר CXX כפי שמתואר בהמשך בקובץ lib.rs
:
#[cxx::bridge]
mod ffi {
unsafe extern "C++" {
include!("cxx_test.hpp");
fn greet(greetee: &str) -> i32;
}
extern "Rust" {
fn get_num() -> i32;
}
}
fn main() {
let result = ffi::greet("world");
println!("C++ returned {}", result);
}
fn get_num() -> i32 {
return 42;
}