Паттерны ржавчины Android

Эта страница содержит информацию о ведении журнала 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!");
}

That is, add the two dependencies shown above ( liblogger and liblog_rust ), call the init method once (you can call it more than once if necessary), and log messages using the provided macros. See the logger crate for a list of possible configuration options.

Крейт регистратора предоставляет API для определения того, что вы хотите регистрировать. В зависимости от того, выполняется ли код на устройстве или на хосте (например, в рамках теста на стороне хоста), сообщения регистрируются с помощью android_logger или env_logger .

Пример AIDL на Rust

В этом разделе представлен пример использования AIDL с Rust в стиле Hello World.

Using the Android Developer Guide AIDL Overview section as a starting point, create external/rust/binder_example/aidl/com/example/android/IRemoteService.aidl with the following contents in the IRemoteService.aidl file:

// 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);
}

Then, within the external/rust/binder_example/aidl/Android.bp file, define the aidl_interface module. You must explicitly enable the Rust backend because it isn't enabled by default.

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",
    ],
}

Note that the module name format for the AIDL-generated library used in rustlibs is the aidl_interface module name followed by -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.

Continuing on the RemoteService example, the generated AIDL backend library includes async interfaces that can be used to implement an async server implementation for the AIDL interface RemoteService .

The generated async server interface IRemoteServiceAsyncServer can be implemented as follows:

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 внутри страны. This is because #[tokio::main] wraps the code in a call to block_on , and join_thread_pool might call block_on when handling an incoming transaction. Вызов block_on из block_on приводит к панике. This could also be avoided by building the tokio runtime manually instead of using #[tokio::main] and then call join_thread_pool outside of the block_on method.

Further, the rust backend generated library includes an interface that allows implementing an async client IRemoteServiceAsync for RemoteService which can be implemented as follows:

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!");
}

The Rust library must define headers which the dependent C modules can pull in, so define the external/rust/simple_printer/simple_printer.h header as follows:

#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

Define external/rust/c_hello_rust/main.c as follows:

#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"],
}

Finally, build by calling m c_hello_rust .

Взаимодействие Rust-Java

Крейт jni обеспечивает совместимость Rust с Java через собственный интерфейс Java (JNI). It defines the necessary type definitions for Rust to produce a Rust cdylib library that plugs directly into Java's JNI ( JNIEnv , JClass , JString , and so on). Unlike C++ bindings that perform codegen through cxx , Java interoperability through the JNI doesn't require a code-generation step during a build. Поэтому ему не требуется специальная поддержка системы сборки. Код Java загружает предоставленную Rust cdylib как и любую другую собственную библиотеку.

Использование

Usage in both Rust and Java code is covered in the jni crate documentation . Пожалуйста, следуйте приведенному там примеру «Начало работы» . После написания 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

The CXX crate provides safe FFI between Rust and a subset of C++. The CXX documentation gives good examples of how it works in general and we suggest reading it first to become familiar with the library and the way it bridges C++ and Rust. В следующем примере показано, как использовать его в Android.

To have CXX generate the C++ code that Rust calls into, define a genrule to invoke CXX and a cc_library_static to bundle that into a library. Если вы планируете использовать 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"],
}

The cxxbridge tool is used above to generate the C++ side of the bridge. Статическая библиотека libcxx_test_cpp используется далее в качестве зависимости для нашего исполняемого файла Rust:

rust_binary {
    name: "cxx_test",
    srcs: ["lib.rs"],
    rustlibs: ["libcxx"],
    static_libs: ["libcxx_test_cpp"],
}

В файлах .cpp и .hpp определите функции C++ по своему усмотрению, используя по желанию типы оболочек CXX . For example, a cxx_test.hpp definition contains the following:

#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;
}