Padrões de ferrugem do Android

Esta página contém informações sobre o Android Logging , fornece um exemplo de Rust AIDL , informa como chamar Rust de C e fornece instruções para Rust/C++ Interop Using CXX .

Registro do Android

O exemplo a seguir mostra como você pode registrar mensagens no logcat (no dispositivo) ou no stdout (no host).

Em seu 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, em sua fonte Rust, adicione este código:

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

Ou seja, adicione as duas dependências mostradas acima ( liblogger e liblog_rust ), chame o método init uma vez (você pode chamá-lo mais de uma vez se necessário) e registre mensagens usando as macros fornecidas. Consulte a caixa do registrador para obter uma lista de opções de configuração possíveis.

A caixa do registrador fornece uma API para definir o que você deseja 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 android_logger ou env_logger .

Exemplo de ferrugem AIDL

Esta seção fornece um exemplo no estilo Hello World do uso de AIDL com Rust.

Usando a seção Visão geral do AIDL do Guia do desenvolvedor Android como ponto de partida, crie external/rust/binder_example/aidl/com/example/android/IRemoteService.aidl com o seguinte conteúdo 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, dentro do arquivo external/rust/binder_example/aidl/Android.bp , defina o módulo aidl_interface . Você deve habilitar explicitamente o back-end Rust porque ele não está habilitado 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 backend AIDL é um gerador de origem Rust, portanto, funciona como outros geradores de origem Rust e produz uma biblioteca Rust. O módulo da biblioteca Rust produzido 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 da seguinte forma 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",
    ],
}

Observe que o formato do nome do módulo para a biblioteca gerada por AIDL usada em rustlibs é o nome do módulo aidl_interface seguido por -rust ; neste caso, com.example.android.remoteservice-rust .

A interface AIDL pode então ser referenciada em src/lib.rs da seguinte forma:

// 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 com_example_android_remoteservice::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).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()
}

Chamando Rust de C

Este exemplo mostra como chamar Rust de C.

Exemplo de biblioteca de ferrugem

Defina o arquivo libsimple_printer em external/rust/simple_printer/libsimple_printer.rs da seguinte forma:

//! 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 deve definir cabeçalhos que os módulos C dependentes podem receber, portanto, defina o cabeçalho external/rust/simple_printer/simple_printer.h da seguinte maneira:

#ifndef SIMPLE_PRINTER_H
#define SIMPLE_PRINTER_H

void print_c_hello_rust();


#endif

Defina external/rust/simple_printer/Android.bp como você vê aqui:

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

Exemplo C binário

Defina external/rust/c_hello_rust/main.c da seguinte forma:

#include "simple_printer.h"

int main() {
  print_c_hello_rust();
  return 0;
}

Defina external/rust/c_hello_rust/Android.bp da seguinte forma:

cc_binary {
    name: "c_hello_rust",
    srcs: ["main.c"],
    shared_libs: ["libsimple_c_printer"],
}

Finalmente, construa chamando m c_hello_rust .

Interoperabilidade Rust-Java

A caixa jni fornece interoperabilidade Rust com Java por meio da Java Native Interface (JNI). Ele define as definições de tipo necessárias para Rust produzir uma biblioteca Rust cdylib que se conecta diretamente ao JNI do Java ( JNIEnv , JClass , JString e assim por diante). Ao contrário das associações C++ que executam codegen por meio de cxx , a interoperabilidade Java por meio da JNI não requer uma etapa de geração de código durante uma compilação. Portanto, ele não precisa de suporte especial ao sistema de compilação. O código Java carrega o cdylib fornecido pelo Rust como qualquer outra biblioteca nativa.

Uso

O uso em código Rust e Java é abordado na documentação do jni crate . Siga o exemplo de introdução fornecido lá. Depois de escrever src/lib.rs , volte a esta página para aprender como construir a biblioteca com o sistema de compilação do Android.

Definição de compilação

Java requer que a biblioteca Rust seja fornecida como um cdylib para que possa ser carregada dinamicamente. A definição da biblioteca Rust em Soong é a seguinte:

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 biblioteca Rust como uma dependência required ; isso garante que ele seja instalado no dispositivo junto com a biblioteca Java, mesmo que não seja uma dependência de tempo de compilação:

java_library {
        name: "libhelloworld",
        [...]
        required: ["libhellorust"]
        [...]
}

Como alternativa, se você precisar incluir a biblioteca Rust em um arquivo AndroidManifest.xml , adicione a biblioteca a uses_libs da seguinte maneira:

java_library {
        name: "libhelloworld",
        [...]
        uses_libs: ["libhellorust"]
        [...]
}

Interoperabilidade Rust–C++ usando CXX

A caixa CXX fornece FFI seguro entre Rust e um subconjunto de C++. A documentação do CXX fornece bons exemplos de como ele funciona em geral. O exemplo a seguir mostra como usá-lo no Android.

Para que o CXX gere o código C++ que Rust chama, defina um genrule para invocar o CXX e um cc_library_static para agrupar isso em uma biblioteca. Se você planeja fazer com que C++ chame o código Rust ou use tipos compartilhados entre C++ e Rust, defina uma segunda genrule (para gerar um cabeçalho C++ contendo as ligações 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"],
}

genrule {
    name: "libcxx_test_bridge_code",
    tools: ["cxxbridge"],
    cmd: "$(location cxxbridge) $(in) >> $(out)",
    srcs: ["lib.rs"],
    out: ["libcxx_test_cxx_generated.cc"],
}

// This defines a second genrule to generate a C++ header.
// * The generated header contains 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"],
}

Em seguida, vincule isso a uma biblioteca ou 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++ conforme desejar, usando os tipos de wrapper CXX conforme desejado. Por exemplo, uma definição de cxx_test.hpp contém o seguinte:

#pragma once

#include "rust/cxx.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 isso do Rust, defina uma ponte CXX como abaixo em 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;
}