Esta página contém informações sobre a geração de registros do Android, fornece um exemplo da AIDL da biblioteca Rust, informa como chamar a Rust no código C, e fornece instruções para Interoperabilidade Rust/C++ usando CXX.
Geração de registros do Android
O exemplo a seguir mostra como registrar mensagens no logcat
(no dispositivo) ou
stdout
(no host).
No módulo Android.bp
, adicione liblogger
e liblog_rust
como dependências:
rust_binary {
name: "logging_test",
srcs: ["src/main.rs"],
rustlibs: [
"liblogger",
"liblog_rust",
],
}
Em seguida, na sua origem do Rust, adicione este código:
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!");
}
Ou seja, adicione as duas dependências mostradas acima (liblogger
e liblog_rust
),
chame o método init
uma vez (você pode o chamar mais de uma vez, se necessário) e
registre as mensagens usando as macros fornecidas. Consulte a
caixa de geração de registros
para ver uma lista das possíveis opções de configuração.
A caixa de geração de registro fornece uma API para definir o que você quer registrar. Dependendo se o código está sendo executado no dispositivo ou no host (como parte de um teste do lado do host), as mensagens são registradas usando o android_logger ou o env_logger (links em inglês).
Exemplo de AIDL do Rust
Veja um exemplo no estilo "Hello World" de uso da AIDL com o Rust nesta seção.
Usando a seção Visão geral da AIDL
do Guia para desenvolvedores Android como ponto de partida, crie external/rust/binder_example/aidl/com/example/android/IRemoteService.aidl
com o conteúdo abaixo no arquivo 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);
}
Em seguida, no arquivo external/rust/binder_example/aidl/Android.bp
, defina o módulo
aidl_interface
. Você precisa ativar explicitamente o back-end do Rust porque ele
não é ativado por padrão.
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,
},
},
}
O back-end da AIDL é um gerador de origem Rust. Por isso, ele opera como outros geradores de mesma
origem e produz uma biblioteca Rust. O módulo produzido dessa biblioteca pode ser
usado por outros módulos Rust como uma dependência. Como exemplo de uso da biblioteca produzida
como uma dependência, uma rust_library
pode ser definida desta maneira em
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",
],
}
O formato do nome do módulo para a biblioteca gerada pela AIDL usada em rustlibs
é o nome do módulo aidl_interface
seguido por -rust
. Neste caso,
com.example.android.remoteservice-rust
.
A interface da AIDL pode ser referenciada em src/lib.rs
desta maneira:
// 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(())
}
}
Por fim, inicie o serviço em um binário Rust, conforme mostrado abaixo:
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()
}
Exemplo de AIDL do Rust assíncrono
Nesta seção, incluímos um exemplo no estilo "Hello World" de uso da AIDL com o Rust assíncrono.
Continuando com o exemplo de RemoteService
, a biblioteca de back-end gerada por AIDL
inclui interfaces assíncronas que podem ser usadas para implementar um servidor assíncrono
para a interface de AIDL RemoteService
.
A interface IRemoteServiceAsyncServer
do servidor assíncrono gerada pode ser
implementada da seguinte maneira:
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(())
}
}
A implementação do servidor assíncrono pode começar assim:
#[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();
});
}
Observe que o
block_in_place
é necessário para sair do contexto assíncrono que permite que join_thread_pool
use
block_on (links em inglês) internamente. Isso ocorre porque #[tokio::main]
envolve o código
em uma chamada para block_on
, e join_thread_pool
pode chamar
block_on
ao processar uma transação recebida. Chamar um
block_on
de dentro de block_on
gera pânico. Isso também pode
ser evitado criando o ambiente de execução "tokio"
manualmente (link em inglês)
em vez de usar #[tokio::main]
e chamar join_thread_pool
fora do método block_on
.
Além disso, a biblioteca de back-end gerada por Rust inclui uma interface que permite
implementar um cliente assíncrono IRemoteServiceAsync
para RemoteService
que pode
ser realizado assim:
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),
}
}
Chamar o Rust no código C
Este exemplo mostra como chamar o Rust do código C.
Biblioteca de exemplo do Rust
Defina o arquivo libsimple_printer
em external/rust/simple_printer/libsimple_printer.rs
desta maneira:
//! 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!");
}
A biblioteca Rust precisa definir cabeçalhos que os módulos C dependentes podem extrair,
portanto, defina o cabeçalho external/rust/simple_printer/simple_printer.h
desta maneira:
#ifndef SIMPLE_PRINTER_H
#define SIMPLE_PRINTER_H
void print_c_hello_rust();
#endif
Defina external/rust/simple_printer/Android.bp
como mostrado abaixo:
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: ["."],
}
Exemplo de binário C
Defina o external/rust/c_hello_rust/main.c
desta maneira:
#include "simple_printer.h"
int main() {
print_c_hello_rust();
return 0;
}
Defina o external/rust/c_hello_rust/Android.bp
desta maneira:
cc_binary {
name: "c_hello_rust",
srcs: ["main.c"],
shared_libs: ["libsimple_c_printer"],
}
Por fim, chame m c_hello_rust
para criar.
Interoperabilidade entre Rust e Java
A caixa jni
fornece interoperabilidade entre Rust e Java na Java Native
Interface (JNI). Ela configura as definições de tipo necessárias para que o Rust produza
uma biblioteca cdylib
que se conecta diretamente à JNI do Java (JNIEnv
, JClass
,
JString
e assim por diante). Ao contrário das vinculações em C++, que executam o codegen na caixa cxx
,
a interoperabilidade do Java na JNI não requer uma etapa de geração de código
durante uma criação. Por isso, ela não precisa de suporte especial para o sistema de build. O código
Java carrega a cdylib
fornecida pelo Rust como qualquer outra biblioteca nativa.
Uso
O uso nos códigos Rust e Java é abordado na
documentação da caixa jni
. Siga
o exemplo de Como começar (links em inglês)
disponível nela. Depois de programar src/lib.rs
, volte a esta página para
aprender a criar a biblioteca com o sistema de build do Android.
Definição do build
O Java exige que a biblioteca do Rust seja fornecida como uma cdylib
para que possa ser
carregada dinamicamente. Veja a definição da biblioteca do Rust no Soong:
rust_ffi_shared {
name: "libhello_jni",
crate_name: "hello_jni",
srcs: ["src/lib.rs"],
// The jni crate is required
rustlibs: ["libjni"],
}
A biblioteca Java lista a do Rust como uma dependência required
.
Isso garante que ela seja instalada no dispositivo junto à biblioteca Java, mesmo que
não seja uma dependência do tempo de build.
java_library {
name: "libhelloworld",
[...]
required: ["libhellorust"]
[...]
}
Como alternativa, se você precisar incluir a biblioteca do Rust em um arquivo
AndroidManifest.xml
, adicione-a a uses_libs
desta maneira:
java_library {
name: "libhelloworld",
[...]
uses_libs: ["libhellorust"]
[...]
}
Interoperabilidade entre Rust e C++ usando CXX
A caixa CXX fornece uma FFI segura entre o Rust e um subconjunto de C++. A documentação da CXX (links em inglês) dá bons exemplos de como ela funciona no geral, e sugerimos a leitura para entender a biblioteca e a maneira como ela faz a ponte entre o C++ e o Rust. O exemplo abaixo mostra o uso no Android.
Para que a CXX gere o código C++ chamado pelo Rust, defina uma genrule
para
invocar a CXX e uma cc_library_static
para inclusão em uma biblioteca. Se você planeja
usar C++ para chamar um código Rust ou se usa tipos compartilhados entre C++ e Rust, defina uma
segunda genrule para gerar um cabeçalho C++ com as vinculações do 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"],
}
A ferramenta cxxbridge
(link em inglês)
é usada acima para gerar o lado C++ da ponte. A biblioteca estática libcxx_test_cpp
é usada abaixo como uma dependência do executável Rust:
rust_binary {
name: "cxx_test",
srcs: ["lib.rs"],
rustlibs: ["libcxx"],
static_libs: ["libcxx_test_cpp"],
}
Nos arquivos .cpp
e .hpp
, defina as funções C++ como quiser,
usando os tipos de wrapper CXX (link em inglês) como preferir.
Por exemplo, uma definição cxx_test.hpp
contém o seguinte:
#pragma once
#include "rust/cxx.h"
#include "lib.rs.h"
int greet(rust::Str greetee);
Enquanto cxx_test.cpp
contém:
#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 essas definições com o Rust, defina uma ponte CXX como mostrado abaixo 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;
}