Эта страница содержит информацию о ведении журнала 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
один раз (при необходимости вы можете вызвать его несколько раз) и зарегистрируйте сообщения, используя предоставленные макросы. Список возможных опций конфигурации см. в ящике регистратора .
Крейт регистратора предоставляет API для определения того, что вы хотите регистрировать. В зависимости от того, выполняется ли код на устройстве или на хосте (например, в рамках теста на стороне хоста), сообщения регистрируются с помощью android_logger или env_logger .
Пример AIDL на Rust
В этом разделе представлен пример использования AIDL с Rust в стиле Hello World.
Используя раздел «Обзор AIDL Руководства разработчика 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);
}
Затем в файле external/rust/binder_example/aidl/Android.bp
определите модуль aidl_interface
. Вы должны явно включить серверную часть 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
В этом разделе представлен пример использования AIDL с асинхронным Rust в стиле Hello World.
Продолжая пример 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"],
}
Наконец, выполните сборку, вызвав m c_hello_rust
.
Взаимодействие Rust-Java
Крейт jni
обеспечивает совместимость Rust с Java через собственный интерфейс Java (JNI). Он определяет необходимые определения типов для Rust для создания библиотеки Rust cdylib
, которая подключается непосредственно к Java JNI ( JNIEnv
, JClass
, JString
и т. д.). В отличие от привязок C++, которые выполняют генерацию кода через cxx
, совместимость Java через JNI не требует этапа генерации кода во время сборки. Поэтому ему не требуется специальная поддержка системы сборки. Код Java загружает предоставленную Rust cdylib
как и любую другую собственную библиотеку.
Использование
Использование как в коде Rust, так и в Java описано в документации jni
crate . Пожалуйста, следуйте приведенному там примеру «Начало работы» . После написания src/lib.rs
вернитесь на эту страницу, чтобы узнать, как собрать библиотеку с помощью системы сборки Android.
Определение сборки
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, даже если это не зависимость во время сборки:
java_library {
name: "libhelloworld",
[...]
required: ["libhellorust"]
[...]
}
В качестве альтернативы, если вам необходимо включить библиотеку Rust в файл AndroidManifest.xml
, добавьте библиотеку в uses_libs
следующим образом:
java_library {
name: "libhelloworld",
[...]
uses_libs: ["libhellorust"]
[...]
}
Взаимодействие Rust–C++ с использованием CXX
Крейт CXX обеспечивает безопасный FFI между Rust и подмножеством C++. Документация CXX дает хорошие примеры того, как она работает в целом, и мы предлагаем сначала прочитать ее, чтобы ознакомиться с библиотекой и тем, как она объединяет C++ и Rust. В следующем примере показано, как использовать его в Android.
Чтобы CXX генерировал код C++, который вызывает Rust, определите genrule
для вызова CXX и cc_library_static
для объединения его в библиотеку. Если вы планируете использовать C++ для вызова кода Rust или использовать типы, общие для C++ и Rust, определите второе правило (для создания заголовка 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++. Далее в качестве зависимости для нашего исполняемого файла Rust используется статическая библиотека libcxx_test_cpp
:
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;
}