Diese Seite enthält Informationen zu Android Logging, ein Rust AIDL-Beispiel, das Ihnen zeigt, wie Sie Russ von C anrufen und eine Anleitung geben für Rust/C++ Interop Using CXX.
Android-Logging
Das folgende Beispiel zeigt, wie Sie Nachrichten in logcat
(auf dem Gerät) oder
stdout
(auf Organisator).
Fügen Sie im Modul Android.bp
liblogger
und liblog_rust
als Abhängigkeiten hinzu:
rust_binary {
name: "logging_test",
srcs: ["src/main.rs"],
rustlibs: [
"liblogger",
"liblog_rust",
],
}
Fügen Sie als Nächstes in Ihrer Rust-Quelle diesen Code hinzu:
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!");
}
Fügen Sie also die beiden oben gezeigten Abhängigkeiten (liblogger
und liblog_rust
) hinzu.
Rufen Sie die Methode init
einmal auf (Sie können sie bei Bedarf mehrmals aufrufen) und
Protokollmeldungen mithilfe der bereitgestellten Makros. Weitere Informationen finden Sie in der
Holzkiste
finden Sie eine Liste der möglichen Konfigurationsoptionen.
Die Protokollierungsbox bietet ein API, mit dem Sie definieren können, was protokolliert werden soll. Je nach ob der Code auf dem Gerät oder auf dem Host ausgeführt wird (z. B. Teil eines Host-seitiger Test), werden die Meldungen entweder mit android_logger oder env_logger.
Rust AIDL-Beispiel
Dieser Abschnitt enthält ein Beispiel im Hello World-Stil für die Verwendung von AIDL mit Rust.
Android-Entwicklerleitfaden verwenden AIDL Overview (in englischer Sprache)
Abschnitt als Ausgangspunkt verwenden, erstellen Sie external/rust/binder_example/aidl/com/example/android/IRemoteService.aidl
durch den folgenden Inhalt in der Datei 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);
}
Definieren Sie dann in der Datei external/rust/binder_example/aidl/Android.bp
Modul aidl_interface
. Sie müssen das Rust-Back-End explizit aktivieren, da es
nicht standardmäßig aktiviert ist.
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,
},
},
}
Das AIDL-Back-End ist ein Rust-Quellgenerator und funktioniert daher wie eine andere Rust-Quelle.
Generatoren und produziert eine Rust-Bibliothek. Das erzeugte Rust-Bibliotheksmodul
von anderen Rust-Modulen als Abhängigkeit verwendet. Als Beispiel für die Verwendung des
als Abhängigkeit kann rust_library
folgendermaßen definiert werden:
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",
],
}
Das Format des Modulnamens für die AIDL-generierte Bibliothek, die in rustlibs
verwendet wird
ist der Name des Moduls aidl_interface
gefolgt von -rust
. in diesem Fall
com.example.android.remoteservice-rust
.
Auf die AIDL-Schnittstelle kann dann in src/lib.rs
so verwiesen werden:
// 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(())
}
}
Abschließend starten Sie den Dienst in einem Rust-Binärprogramm, wie unten gezeigt:
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()
}
Beispiel für Async Rust AIDL
Dieser Abschnitt enthält ein Beispiel im Hello World-Stil für die Verwendung von AIDL mit asynchronem Rust.
Fahren wir mit dem Beispiel RemoteService
fort: der generierten AIDL-Back-End-Bibliothek
enthält asynchrone Schnittstellen, mit denen ein asynchroner Server implementiert werden kann.
Implementierung für die AIDL-Schnittstelle RemoteService
.
Die generierte asynchrone Serverschnittstelle IRemoteServiceAsyncServer
kann
wie folgt implementiert:
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(())
}
}
Die asynchrone Serverimplementierung kann so gestartet werden:
#[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();
});
}
Das Feld
an_Ort_blockieren
ist erforderlich, um den asynchronen Kontext zu verlassen, der es join_thread_pool
ermöglicht,
intern block_on. Das liegt daran, dass #[tokio::main]
den Code
in einem Anruf an block_on
und join_thread_pool
ruft möglicherweise
block_on
fest, wenn eine eingehende Transaktion verarbeitet wird. Durch Aufrufen einer
block_on
von einem block_on
-Gerät aus führt zu einer Panik. Dies könnte auch
durch Erstellen der Tokio-Laufzeit
manuell
statt #[tokio::main]
zu verwenden und dann join_thread_pool
aufzurufen
außerhalb der Methode block_on
.
Darüber hinaus enthält die Rust-Back-End-Bibliothek eine Schnittstelle, mit der
Implementieren eines asynchronen Clients IRemoteServiceAsync
für RemoteService
, der
wie folgt implementiert werden:
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 von C anrufen
In diesem Beispiel wird gezeigt, wie Rust aus C aufgerufen wird.
Rust-Beispielbibliothek
Definieren Sie die Datei libsimple_printer
in external/rust/simple_printer/libsimple_printer.rs
wie folgt:
//! 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!");
}
Die Rust-Bibliothek muss Header definieren, die von den abhängigen C-Modulen abgerufen werden können.
Definieren Sie den external/rust/simple_printer/simple_printer.h
-Header daher so:
#ifndef SIMPLE_PRINTER_H
#define SIMPLE_PRINTER_H
void print_c_hello_rust();
#endif
Definieren Sie external/rust/simple_printer/Android.bp
wie hier gezeigt:
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: ["."],
}
Beispiel-C-Binärprogramm
Definieren Sie external/rust/c_hello_rust/main.c
so:
#include "simple_printer.h"
int main() {
print_c_hello_rust();
return 0;
}
Definieren Sie external/rust/c_hello_rust/Android.bp
so:
cc_binary {
name: "c_hello_rust",
srcs: ["main.c"],
shared_libs: ["libsimple_c_printer"],
}
Zuletzt erstellen Sie den Build durch Aufrufen von m c_hello_rust
.
Rust-Java-Interoperabilität
Die Box jni
bietet Rust-Interoperabilität mit Java über die native Java-Umgebung
Schnittstelle (JNI). Es definiert die notwendigen Typdefinitionen, damit Rust
eine Rust-cdylib
-Bibliothek, die direkt in die JNI von Java eingebunden ist (JNIEnv
, JClass
,
JString
usw.). Im Gegensatz zu C++-Bindungen, die Codegen über cxx
ausführen,
Java-Interoperabilität über JNI erfordert keinen Schritt zur Codegenerierung
während eines Build-Vorgangs. Daher ist keine spezielle Unterstützung für das Build-System erforderlich. Java
wird die von Rust bereitgestellte cdylib
wie jede andere native Bibliothek geladen.
Nutzung
Die Verwendung in Rust- und Java-Code wird im
Dokumentation zu jni
-Kisten. Bitte
folgen Sie der Anleitung unter Erste Schritte.
ein entsprechendes Beispiel. Nachdem Sie src/lib.rs
geschrieben haben, kehren Sie zu dieser Seite zurück, um
erfahren Sie, wie Sie die Bibliothek mit dem Build-System von Android erstellen.
Build-Definition
Für Java muss die Rust-Bibliothek als cdylib
bereitgestellt werden, damit sie
dynamisch geladen werden. Die Definition der Rust-Bibliothek in Soong lautet wie folgt:
rust_ffi_shared {
name: "libhello_jni",
crate_name: "hello_jni",
srcs: ["src/lib.rs"],
// The jni crate is required
rustlibs: ["libjni"],
}
Die Java-Bibliothek listet die Rust-Bibliothek als required
-Abhängigkeit auf.
Dadurch wird sichergestellt, dass es zusammen mit der Java-Bibliothek auf dem Gerät installiert wird, obwohl
Es ist keine Abhängigkeit zur Build-Zeit:
java_library {
name: "libhelloworld",
[...]
required: ["libhellorust"]
[...]
}
Alternativ, wenn Sie die Rust-Bibliothek in eine AndroidManifest.xml
aufnehmen müssen
fügen Sie die Bibliothek so zu uses_libs
hinzu:
java_library {
name: "libhelloworld",
[...]
uses_libs: ["libhellorust"]
[...]
}
Rust-C++-Interoperabilität mit CXX
Die CXX-Kiste ermöglicht sicheres FFI zwischen Rust und einer Teilmenge von C++. Die CXX-Dokumentation gibt gute Beispiele für die allgemeine Funktionsweise. Wir empfehlen, es zuerst zu lesen. um sich mit der Bibliothek und ihrer Verbindung zwischen C++ und Rust vertraut zu machen. Die Das folgende Beispiel zeigt, wie sie in Android verwendet werden.
Damit CXX den C++-Code generiert, den Rust aufruft, definieren Sie eine genrule
, um
Rufe CXX und ein cc_library_static
auf, um das in einer Bibliothek zu bündeln. Wenn Sie
Um Rust-Code in C++ aufzurufen oder Typen zu verwenden, die von C++ und Rust gemeinsam genutzt werden, definieren Sie einen
second Genrule (zum Generieren eines C++ Headers, der die Rust-Bindungen enthält)
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"],
}
Das cxxbridge
-Tool
wird oben verwendet, um die C++-Seite der Brücke zu generieren. Das libcxx_test_cpp
Als Nächstes wird die statische Bibliothek als Abhängigkeit für unsere Rust-Programmdatei verwendet:
rust_binary {
name: "cxx_test",
srcs: ["lib.rs"],
rustlibs: ["libcxx"],
static_libs: ["libcxx_test_cpp"],
}
Definieren Sie in den Dateien .cpp
und .hpp
die C++-Funktionen nach Bedarf.
Verwenden Sie nach Bedarf die CXX-Wrapper-Typen.
Eine cxx_test.hpp
-Definition enthält beispielsweise Folgendes:
#pragma once
#include "rust/cxx.h"
#include "lib.rs.h"
int greet(rust::Str greetee);
Während cxx_test.cpp
enthält
#include "cxx_test.hpp"
#include "lib.rs.h"
#include <iostream>
int greet(rust::Str greetee) {
std::cout << "Hello, " << greetee << std::endl;
return get_num();
}
Um diese aus Rust zu verwenden, definieren Sie eine CXX-Bridge wie unten in 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;
}