Паттерны ржавчины 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, 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!");
}

То есть добавьте две показанные выше зависимости ( 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::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),
    }
}

Вызов 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 include_dirs so cc_binary knows where the headers are.
    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 , которая подключается непосредственно к JNI Java ( 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++. Статическая библиотека libcxx_test_cpp используется далее в качестве зависимости для нашего исполняемого файла Rust:

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