Modelli di ruggine Android

Questa pagina contiene informazioni su Android Logging , fornisce un esempio di Rust AIDL , spiega come chiamare Rust da C e fornisce istruzioni per l' interoperabilità Rust/C++ usando CXX .

Registrazione Android

L'esempio seguente mostra come registrare i messaggi su logcat (sul dispositivo) o stdout (sull'host).

Nel tuo modulo Android.bp , aggiungi liblogger e liblog_rust come dipendenze:

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

Successivamente, nel tuo sorgente Rust aggiungi questo codice:

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

Cioè, aggiungi le due dipendenze mostrate sopra ( liblogger e liblog_rust ), chiama il metodo init una volta (puoi chiamarlo più di una volta se necessario) e registra i messaggi usando le macro fornite. Vedere la cassa del logger per un elenco di possibili opzioni di configurazione.

La cassa del logger fornisce un'API per definire cosa si desidera registrare. A seconda che il codice sia in esecuzione sul dispositivo o sull'host (come parte di un test lato host), i messaggi vengono registrati utilizzando android_logger o env_logger .

Esempio di AIDL ruggine

Questa sezione fornisce un esempio in stile Hello World dell'utilizzo di AIDL con Rust.

Utilizzando la sezione Panoramica AIDL della Guida per gli sviluppatori Android come punto di partenza, creare external/rust/binder_example/aidl/com/example/android/IRemoteService.aidl con i seguenti contenuti nel file 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);
}

Quindi, all'interno del file external/rust/binder_example/aidl/Android.bp , definisci il modulo aidl_interface . Devi abilitare esplicitamente il back-end Rust perché non è abilitato per impostazione predefinita.

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

Il backend AIDL è un generatore di sorgenti Rust, quindi funziona come altri generatori di sorgenti Rust e produce una libreria Rust. Il modulo della libreria Rust prodotto può essere utilizzato da altri moduli Rust come dipendenza. Come esempio dell'utilizzo della libreria prodotta come dipendenza, una rust_library può essere definita come segue in 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",
    ],
}

Si noti che il formato del nome del modulo per la libreria generata da AIDL utilizzata in rustlibs è il nome del modulo aidl_interface seguito da -rust ; in questo caso, com.example.android.remoteservice-rust .

L'interfaccia AIDL può quindi essere referenziata in src/lib.rs come segue:

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

Infine, avvia il servizio in un binario Rust come mostrato di seguito:

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

Chiamando Rust da C

Questo esempio mostra come chiamare Rust da C.

Esempio di libreria di ruggine

Definire il file libsimple_printer in external/rust/simple_printer/libsimple_printer.rs come segue:

//! 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 libreria Rust deve definire le intestazioni che i moduli C dipendenti possono inserire, quindi definire l'intestazione external/rust/simple_printer/simple_printer.h come segue:

#ifndef SIMPLE_PRINTER_H
#define SIMPLE_PRINTER_H

void print_c_hello_rust();


#endif

Definisci external/rust/simple_printer/Android.bp come vedi qui:

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

Esempio C binario

Definisci external/rust/c_hello_rust/main.c come segue:

#include "simple_printer.h"

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

Definisci external/rust/c_hello_rust/Android.bp come segue:

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

Infine, compila chiamando m c_hello_rust .

Interoperabilità ruggine-Java

La cassa jni fornisce l'interoperabilità Rust con Java tramite Java Native Interface (JNI). Definisce le definizioni di tipo necessarie affinché Rust produca una libreria cdylib Rust che si collega direttamente a JNI di Java ( JNIEnv , JClass , JString e così via). A differenza dei binding C++ che eseguono codegen tramite cxx , l'interoperabilità Java tramite JNI non richiede un passaggio di generazione del codice durante una compilazione. Pertanto non ha bisogno di un supporto speciale per il sistema di compilazione. Il codice Java carica cdylib fornito da Rust come qualsiasi altra libreria nativa.

Utilizzo

L'utilizzo sia nel codice Rust che in quello Java è trattato nella documentazione di jni crate . Si prega di seguire l'esempio per iniziare fornito lì. Dopo aver scritto src/lib.rs , torna a questa pagina per scoprire come creare la libreria con il sistema di build di Android.

Costruisci definizione

Java richiede che la libreria Rust sia fornita come cdylib in modo che possa essere caricata dinamicamente. La definizione della libreria Rust in Soong è la seguente:

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

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

La libreria Java elenca la libreria Rust come dipendenza required ; questo assicura che sia installato sul dispositivo insieme alla libreria Java anche se non è una dipendenza in fase di compilazione:

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

In alternativa, se devi includere la libreria Rust in un file AndroidManifest.xml , aggiungi la libreria a uses_libs come segue:

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

Interoperabilità Rust-C++ tramite CXX

La cassa CXX fornisce un FFI sicuro tra Rust e un sottoinsieme di C++. La documentazione CXX fornisce buoni esempi di come funziona in generale. L'esempio seguente mostra come usarlo in Android.

Per fare in modo che CXX generi il codice C++ in cui Rust chiama, definisci un genrule per invocare CXX e un cc_library_static per raggrupparlo in una libreria. Se prevedi di fare in modo che C++ chiami il codice Rust o utilizzi tipi condivisi tra C++ e Rust, definisci un secondo genrule (per generare un'intestazione C++ contenente i collegamenti 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"],
}

Quindi collegalo a una libreria o eseguibile Rust:

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

Nei file .cpp e .hpp , definisci le funzioni C++ come desideri, utilizzando i tipi di wrapper CXX come desideri. Ad esempio, una definizione cxx_test.hpp contiene quanto segue:

#pragma once

#include "rust/cxx.h"

int greet(rust::Str greetee);

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

Per usarlo da Rust, definisci un bridge CXX come di seguito in 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;
}