Patrones de óxido de Android

Esta página contiene información sobre el registro de Android , proporciona un ejemplo de Rust AIDL , le indica cómo llamar a Rust desde C y brinda instrucciones para la interoperabilidad de Rust/C++ usando CXX .

Registro de Android

El siguiente ejemplo muestra cómo puede registrar mensajes en logcat (en el dispositivo) o stdout (en el host).

En su módulo Android.bp , agregue liblogger y liblog_rust como dependencias:

rust_binary {
    name: "logging_test",
    srcs: ["src/main.rs"],
    rustlibs: [
        "liblogger",
        "liblog_rust",
    ],
}

A continuación, en su fuente de Rust, agregue 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!");
}

Es decir, agregue las dos dependencias que se muestran arriba ( liblogger y liblog_rust ), llame al método init una vez (puede llamarlo más de una vez si es necesario) y registre los mensajes usando las macros provistas. Consulte la caja del registrador para obtener una lista de posibles opciones de configuración.

La caja del registrador proporciona una API para definir lo que desea registrar. Según si el código se ejecuta en el dispositivo o en el host (como parte de una prueba del lado del host), los mensajes se registran mediante android_logger o env_logger .

Ejemplo de óxido AIDL

Esta sección proporciona un ejemplo al estilo Hello World del uso de AIDL con Rust.

Utilizando la sección Descripción general de AIDL de la Guía para desarrolladores de Android como punto de partida, cree external/rust/binder_example/aidl/com/example/android/IRemoteService.aidl con el siguiente contenido en el archivo 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);
}

Luego, dentro del archivo external/rust/binder_example/aidl/Android.bp , defina el módulo aidl_interface . Debe habilitar explícitamente el backend de Rust porque no está habilitado de forma predeterminada.

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

El backend de AIDL es un generador de fuentes de Rust, por lo que funciona como otros generadores de fuentes de Rust y produce una biblioteca de Rust. El módulo de biblioteca de Rust producido puede ser utilizado por otros módulos de Rust como una dependencia. Como ejemplo del uso de la biblioteca producida como dependencia, se puede definir una rust_library de la siguiente manera en 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",
    ],
}

Tenga en cuenta que el formato del nombre del módulo para la biblioteca generada por AIDL utilizada en rustlibs es el nombre del módulo aidl_interface seguido de -rust ; en este caso, com.example.android.remoteservice-rust .

Luego se puede hacer referencia a la interfaz AIDL en src/lib.rs de la siguiente manera:

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

Finalmente, inicie el servicio en un binario de Rust como se muestra a continuación:

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()
}

Llamando a Rust desde C

Este ejemplo muestra cómo llamar a Rust desde C.

Biblioteca Rust de ejemplo

Defina el archivo libsimple_printer en external/rust/simple_printer/libsimple_printer.rs de la siguiente manera:

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

La biblioteca Rust debe definir encabezados que los módulos C dependientes puedan extraer, así que defina el encabezado external/rust/simple_printer/simple_printer.h de la siguiente manera:

#ifndef SIMPLE_PRINTER_H
#define SIMPLE_PRINTER_H

void print_c_hello_rust();


#endif

Defina external/rust/simple_printer/Android.bp como se ve aquí:

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

Ejemplo C binario

Defina external/rust/c_hello_rust/main.c de la siguiente manera:

#include "simple_printer.h"

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

Defina external/rust/c_hello_rust/Android.bp de la siguiente manera:

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

Finalmente, compila llamando a m c_hello_rust .

Interoperabilidad de Rust-Java

La caja jni proporciona interoperabilidad de Rust con Java a través de la interfaz nativa de Java (JNI). Define las definiciones de tipo necesarias para que Rust produzca una biblioteca cdylib de Rust que se conecta directamente a JNI de Java ( JNIEnv , JClass , JString , etc.). A diferencia de los enlaces de C++ que realizan la generación de código a través de cxx , la interoperabilidad de Java a través de JNI no requiere un paso de generación de código durante una compilación. Por lo tanto, no necesita soporte especial del sistema de compilación. El código Java carga el cdylib proporcionado por Rust como cualquier otra biblioteca nativa.

Uso

El uso en código Rust y Java está cubierto en la documentación de jni . Siga el ejemplo de introducción que se proporciona allí. Después de escribir src/lib.rs , regrese a esta página para obtener información sobre cómo compilar la biblioteca con el sistema de compilación de Android.

Definición de compilación

Java requiere que la biblioteca Rust se proporcione como cdylib para que pueda cargarse dinámicamente. La definición de la biblioteca Rust en Soong es la siguiente:

rust_ffi_shared {
    name: "libhello_jni",
    crate_name: "hello_jni",
    srcs: ["src/lib.rs"],

    // The jni crate is required
    rustlibs: ["libjni"],
}

La biblioteca Java enumera la biblioteca Rust como una dependencia required ; esto garantiza que se instale en el dispositivo junto con la biblioteca de Java aunque no sea una dependencia en tiempo de compilación:

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

Alternativamente, si debe incluir la biblioteca de Rust en un archivo AndroidManifest.xml , agregue la biblioteca a uses_libs de la siguiente manera:

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

Interoperabilidad de Rust-C++ usando CXX

La caja CXX proporciona FFI segura entre Rust y un subconjunto de C++. La documentación de CXX da buenos ejemplos de cómo funciona en general. El siguiente ejemplo muestra cómo usarlo en Android.

Para que CXX genere el código C++ al que llama Rust, defina una genrule para invocar CXX y un cc_library_static para agruparlo en una biblioteca. Si planea que C++ llame al código de Rust, o use tipos compartidos entre C++ y Rust, defina una segunda regla general (para generar un encabezado de C++ que contenga los enlaces de 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"],
}

Luego vincule esto a una biblioteca Rust o ejecutable:

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

En los archivos .cpp y .hpp , defina las funciones de C++ como desee, utilizando los tipos de contenedor CXX como desee. Por ejemplo, una definición de cxx_test.hpp contiene lo siguiente:

#pragma once

#include "rust/cxx.h"

int greet(rust::Str greetee);

Mientras que cxx_test.cpp contiene

#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 esto de Rust, defina un puente CXX como se muestra a continuación en 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;
}