Cette page contient des informations sur Android Logging , fournit un exemple Rust AIDL , vous indique comment appeler Rust depuis C et fournit des instructions pour Rust/C++ Interop Using CXX .
Journalisation Android
L'exemple suivant montre comment vous pouvez consigner des messages dans logcat
(sur l'appareil) ou stdout
(sur l'hôte).
Dans votre module Android.bp
, ajoutez liblogger
et liblog_rust
comme dépendances :
rust_binary {
name: "logging_test",
srcs: ["src/main.rs"],
rustlibs: [
"liblogger",
"liblog_rust",
],
}
Ensuite, dans votre source Rust, ajoutez ce code :
use log::{debug, error, Level};
fn main() {
let init_success = logger::init(
logger::Config::default()
.with_tag_on_device("mytag")
.with_min_level(Level::Trace),
);
debug!("This is a debug message.");
error!("Something went wrong!");
}
Autrement dit, ajoutez les deux dépendances indiquées ci-dessus ( liblogger
et liblog_rust
), appelez la méthode init
une fois (vous pouvez l'appeler plusieurs fois si nécessaire) et enregistrez les messages à l'aide des macros fournies. Voir la caisse de l'enregistreur pour une liste des options de configuration possibles.
Le logger crate fournit une API pour définir ce que vous voulez enregistrer. Selon que le code s'exécute sur l'appareil ou sur l'hôte (comme dans le cadre d'un test côté hôte), les messages sont enregistrés à l'aide de android_logger ou env_logger .
Exemple de Rust AIDL
Cette section fournit un exemple de style Hello World d'utilisation d'AIDL avec Rust.
En utilisant la section Présentation AIDL du guide du développeur Android comme point de départ, créez external/rust/binder_example/aidl/com/example/android/IRemoteService.aidl
avec le contenu suivant dans le fichier 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);
}
Ensuite, dans le fichier external/rust/binder_example/aidl/Android.bp
, définissez le module aidl_interface
. Vous devez activer explicitement le backend Rust car il n'est pas activé par défaut.
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,
},
},
}
Le backend AIDL est un générateur de source Rust, il fonctionne donc comme les autres générateurs de source Rust et produit une bibliothèque Rust. Le module de bibliothèque Rust produit peut être utilisé par d'autres modules Rust en tant que dépendance. Comme exemple d'utilisation de la bibliothèque produite comme dépendance, une rust_library
peut être définie comme suit dans 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",
],
}
Notez que le format de nom de module pour la bibliothèque générée par AIDL utilisée dans rustlibs
est le nom de module aidl_interface
suivi de -rust
; dans ce cas, com.example.android.remoteservice-rust
.
L'interface AIDL peut alors être référencée dans src/lib.rs
comme suit :
// 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 com_example_android_remoteservice::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(())
}
}
Enfin, démarrez le service dans un binaire Rust comme indiqué ci-dessous :
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()
}
Appeler Rust depuis C
Cet exemple montre comment appeler Rust depuis C.
Exemple de bibliothèque Rust
Définissez le fichier libsimple_printer
dans external/rust/simple_printer/libsimple_printer.rs
comme suit :
//! 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!");
}
La bibliothèque Rust doit définir des en-têtes que les modules C dépendants peuvent extraire, donc définissez l'en-tête external/rust/simple_printer/simple_printer.h
comme suit :
#ifndef SIMPLE_PRINTER_H
#define SIMPLE_PRINTER_H
void print_c_hello_rust();
#endif
Définissez external/rust/simple_printer/Android.bp
comme vous le voyez ici :
rust_ffi {
name: "libsimple_c_printer",
crate_name: "simple_c_printer",
srcs: ["libsimple_c_printer.rs"],
// Define include_dirs so cc_binary knows where the headers are.
include_dirs: ["."],
}
Exemple C binaire
Définissez external/rust/c_hello_rust/main.c
comme suit :
#include "simple_printer.h"
int main() {
print_c_hello_rust();
return 0;
}
Définissez external/rust/c_hello_rust/Android.bp
comme suit :
cc_binary {
name: "c_hello_rust",
srcs: ["main.c"],
shared_libs: ["libsimple_c_printer"],
}
Enfin, construisez en appelant m c_hello_rust
.
Interopérabilité Rust-Java
Le crate jni
fournit l'interopérabilité de Rust avec Java via l'interface native Java (JNI). Il définit les définitions de type nécessaires pour que Rust produise une bibliothèque Rust cdylib
qui se connecte directement au JNI de Java ( JNIEnv
, JClass
, JString
, etc.). Contrairement aux liaisons C++ qui exécutent codegen via cxx
, l'interopérabilité Java via JNI ne nécessite pas d'étape de génération de code lors d'une génération. Par conséquent, il n'a pas besoin d'un support spécial pour le système de construction. Le code Java charge le cdylib
fourni par Rust comme n'importe quelle autre bibliothèque native.
Usage
L'utilisation dans le code Rust et Java est couverte dans la documentation jni
crate . Veuillez suivre l'exemple de démarrage fourni ici. Après avoir écrit src/lib.rs
, revenez à cette page pour apprendre à construire la bibliothèque avec le système de construction d'Android.
Définition de build
Java nécessite que la bibliothèque Rust soit fournie en tant que cdylib
afin qu'elle puisse être chargée dynamiquement. La définition de la bibliothèque Rust dans Soong est la suivante :
rust_ffi_shared {
name: "libhello_jni",
crate_name: "hello_jni",
srcs: ["src/lib.rs"],
// The jni crate is required
rustlibs: ["libjni"],
}
La bibliothèque Java répertorie la bibliothèque Rust en tant que dépendance required
; cela garantit qu'il est installé sur l'appareil avec la bibliothèque Java même s'il ne s'agit pas d'une dépendance au moment de la construction :
java_library {
name: "libhelloworld",
[...]
required: ["libhellorust"]
[...]
}
Sinon, si vous devez inclure la bibliothèque Rust dans un fichier AndroidManifest.xml
, ajoutez la bibliothèque à uses_libs
comme suit :
java_library {
name: "libhelloworld",
[...]
uses_libs: ["libhellorust"]
[...]
}
Interopérabilité Rust–C++ avec CXX
La caisse CXX fournit une FFI sécurisée entre Rust et un sous-ensemble de C++. La documentation CXX donne de bons exemples de son fonctionnement en général et nous vous suggérons de la lire d'abord pour vous familiariser avec la bibliothèque et la façon dont elle relie C++ et Rust. L'exemple suivant montre comment l'utiliser dans Android.
Pour que CXX génère le code C++ auquel Rust appelle, définissez une genrule
pour invoquer CXX et un cc_library_static
pour le regrouper dans une bibliothèque. Si vous prévoyez que C++ appelle du code Rust ou utilise des types partagés entre C++ et Rust, définissez une deuxième règle générale (pour générer un en-tête C++ contenant les liaisons 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"],
}
L' outil cxxbridge
est utilisé ci-dessus pour générer le côté C++ du pont. La bibliothèque statique libcxx_test_cpp
est ensuite utilisée comme dépendance pour notre exécutable Rust :
rust_binary {
name: "cxx_test",
srcs: ["lib.rs"],
rustlibs: ["libcxx"],
static_libs: ["libcxx_test_cpp"],
}
Dans les fichiers .cpp
et .hpp
, définissez les fonctions C++ comme vous le souhaitez, en utilisant les types de wrapper CXX comme vous le souhaitez. Par exemple, une définition cxx_test.hpp
contient les éléments suivants :
#pragma once
#include "rust/cxx.h"
int greet(rust::Str greetee);
Alors que cxx_test.cpp
contient
#include "cxx_test.hpp"
#include "lib.rs.h"
#include <iostream>
int greet(rust::Str greetee) {
std::cout << "Hello, " << greetee << std::endl;
return get_num();
}
Pour l'utiliser depuis Rust, définissez un pont CXX comme ci-dessous dans 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;
}