Esta página contiene información sobre el registro de Android , proporciona un ejemplo de Rust AIDL , le indica cómo llamar a Rust desde C y proporciona instrucciones para la interoperabilidad de Rust/C++ usando CXX .
Registro de Android
El siguiente ejemplo muestra cómo puede registrar mensajes en logcat
(en el dispositivo) o stdout
(en el host).
En su módulo Android.bp
, agregue liblogger
y liblog_rust
como dependencias:
rust_binary {
name: "logging_test",
srcs: ["src/main.rs"],
rustlibs: [
"liblogger",
"liblog_rust",
],
}
A continuación, en su fuente de Rust agregue este código:
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!");
}
Es decir, agregue las dos dependencias que se muestran arriba ( liblogger
y liblog_rust
), llame al método init
una vez (puede llamarlo más de una vez si es necesario) y registre mensajes usando las macros proporcionadas. Consulte la caja del registrador para obtener una lista de posibles opciones de configuración.
La caja del registrador proporciona una API para definir lo que desea registrar. Dependiendo de si el código se ejecuta en el dispositivo o en el host (como parte de una prueba del lado del host), los mensajes se registran mediante android_logger o env_logger .
Ejemplo de Rust AIDL
Esta sección proporciona un ejemplo al estilo Hello World sobre el uso de AIDL con Rust.
Utilizando la sección Descripción general de AIDL de la Guía para desarrolladores de Android como punto de partida, cree external/rust/binder_example/aidl/com/example/android/IRemoteService.aidl
con el siguiente contenido en el archivo 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);
}
Luego, dentro del archivo external/rust/binder_example/aidl/Android.bp
, defina el módulo aidl_interface
. Debes habilitar explícitamente el backend de Rust porque no está habilitado de forma predeterminada.
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,
},
},
}
El backend de AIDL es un generador de fuentes de Rust, por lo que funciona como otros generadores de fuentes de Rust y produce una biblioteca de Rust. El módulo de biblioteca Rust producido puede ser utilizado por otros módulos Rust como una dependencia. Como ejemplo del uso de la biblioteca producida como dependencia, se puede definir una rust_library
de la siguiente manera en 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",
],
}
Tenga en cuenta que el formato del nombre del módulo para la biblioteca generada por AIDL utilizada en rustlibs
es el nombre del módulo aidl_interface
seguido de -rust
; en este caso, com.example.android.remoteservice-rust
.
Luego se puede hacer referencia a la interfaz AIDL en src/lib.rs
de la siguiente manera:
// 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(())
}
}
Finalmente, inicie el servicio en un binario de Rust como se muestra a continuación:
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()
}
Ejemplo asíncrono de Rust AIDL
Esta sección proporciona un ejemplo estilo Hello World sobre el uso de AIDL con Rust asíncrono.
Continuando con el ejemplo RemoteService
, la biblioteca backend AIDL generada incluye interfaces asíncronas que se pueden usar para implementar una implementación de servidor asíncrono para la interfaz AIDL RemoteService
.
La interfaz del servidor asíncrono generada IRemoteServiceAsyncServer
se puede implementar de la siguiente manera:
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(())
}
}
La implementación del servidor asíncrono se puede iniciar de la siguiente manera:
#[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();
});
}
Tenga en cuenta que block_in_place es necesario para salir del contexto asíncrono que permite join_thread_pool
usar block_on internamente. Esto se debe a que #[tokio::main]
envuelve el código en una llamada a block_on
y join_thread_pool
podría llamar a block_on
cuando maneja una transacción entrante. Llamar a un block_on
desde dentro de un block_on
genera pánico. Esto también podría evitarse construyendo el tiempo de ejecución de Tokio manualmente en lugar de usar #[tokio::main]
y luego llamar a join_thread_pool
fuera del método block_on
.
Además, la biblioteca generada por el backend de Rust incluye una interfaz que permite implementar un cliente asíncrono IRemoteServiceAsync
para RemoteService
que se puede implementar de la siguiente manera:
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::get_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),
}
}
Llamar a Rust desde C
Este ejemplo muestra cómo llamar a Rust desde C.
Ejemplo de biblioteca Rust
Defina el archivo libsimple_printer
en external/rust/simple_printer/libsimple_printer.rs
de la siguiente manera:
//! 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 biblioteca Rust debe definir encabezados que los módulos C dependientes pueden extraer, así que defina el encabezado external/rust/simple_printer/simple_printer.h
de la siguiente manera:
#ifndef SIMPLE_PRINTER_H
#define SIMPLE_PRINTER_H
void print_c_hello_rust();
#endif
Defina external/rust/simple_printer/Android.bp
como ve aquí:
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: ["."],
}
Ejemplo C binario
Defina external/rust/c_hello_rust/main.c
de la siguiente manera:
#include "simple_printer.h"
int main() {
print_c_hello_rust();
return 0;
}
Defina external/rust/c_hello_rust/Android.bp
de la siguiente manera:
cc_binary {
name: "c_hello_rust",
srcs: ["main.c"],
shared_libs: ["libsimple_c_printer"],
}
Finalmente, compila llamando m c_hello_rust
.
Interoperabilidad Rust-Java
jni
crate proporciona interoperabilidad de Rust con Java a través de la interfaz nativa de Java (JNI). Define las definiciones de tipos necesarias para que Rust produzca una biblioteca cdylib
de Rust que se conecte directamente al JNI de Java ( JNIEnv
, JClass
, JString
, etc.). A diferencia de los enlaces de C++ que realizan codegen a través de cxx
, la interoperabilidad de Java a través de JNI no requiere un paso de generación de código durante una compilación. Por lo tanto, no necesita soporte especial del sistema de compilación. El código Java carga el cdylib
proporcionado por Rust como cualquier otra biblioteca nativa.
Uso
El uso en código Rust y Java se trata en la documentación jni
crate . Siga el ejemplo de introducción que se proporciona allí. Después de escribir src/lib.rs
, regrese a esta página para aprender cómo crear la biblioteca con el sistema de compilación de Android.
Definición de compilación
Java requiere que la biblioteca Rust se proporcione como cdylib
para que pueda cargarse dinámicamente. La definición de la biblioteca Rust en Soong es la siguiente:
rust_ffi_shared {
name: "libhello_jni",
crate_name: "hello_jni",
srcs: ["src/lib.rs"],
// The jni crate is required
rustlibs: ["libjni"],
}
La biblioteca Java enumera la biblioteca Rust como una dependencia required
; esto garantiza que se instale en el dispositivo junto con la biblioteca de Java, aunque no sea una dependencia en el momento de la compilación:
java_library {
name: "libhelloworld",
[...]
required: ["libhellorust"]
[...]
}
Alternativamente, si debe incluir la biblioteca Rust en un archivo AndroidManifest.xml
, agregue la biblioteca a uses_libs
de la siguiente manera:
java_library {
name: "libhelloworld",
[...]
uses_libs: ["libhellorust"]
[...]
}
Interoperabilidad Rust-C++ usando CXX
La caja CXX proporciona FFI segura entre Rust y un subconjunto de C++. La documentación de CXX brinda buenos ejemplos de cómo funciona en general y sugerimos leerla primero para familiarizarse con la biblioteca y la forma en que une C++ y Rust. El siguiente ejemplo muestra cómo usarlo en Android.
Para que CXX genere el código C++ al que llama Rust, defina una genrule
para invocar a CXX y un cc_library_static
para agruparlo en una biblioteca. Si planea que C++ llame al código de Rust, o use tipos compartidos entre C++ y Rust, defina una segunda regla genérica (para generar un encabezado de C++ que contenga los enlaces de 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"],
}
La herramienta cxxbridge
se utiliza arriba para generar el lado C++ del puente. La biblioteca estática libcxx_test_cpp
se usa a continuación como dependencia para nuestro ejecutable de Rust:
rust_binary {
name: "cxx_test",
srcs: ["lib.rs"],
rustlibs: ["libcxx"],
static_libs: ["libcxx_test_cpp"],
}
En los archivos .cpp
y .hpp
, defina las funciones de C++ como desee, utilizando los tipos de contenedor CXX como desee. Por ejemplo, una definición cxx_test.hpp
contiene lo siguiente:
#pragma once
#include "rust/cxx.h"
#include "lib.rs.h"
int greet(rust::Str greetee);
Mientras que cxx_test.cpp
contiene
#include "cxx_test.hpp"
#include "lib.rs.h"
#include <iostream>
int greet(rust::Str greetee) {
std::cout << "Hello, " << greetee << std::endl;
return get_num();
}
Para usar esto desde Rust, defina un puente CXX como se muestra a continuación en 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;
}