Wzory rdzy na Androida

Ta strona zawiera informacje o logowaniu Androida , zawiera przykład Rust AIDL , mówi jak wywoływać Rust z C i zawiera instrukcje dotyczące współdziałania Rust/C++ przy użyciu CXX .

Rejestrowanie Androida

Poniższy przykład pokazuje, jak można rejestrować komunikaty w logcat (na urządzeniu) lub stdout (na hoście).

W module liblogger Android.bp liblog_rust jako zależności:

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

Następnie w swoim źródle Rust dodaj ten kod:

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

Oznacza to, że dodaj dwie zależności pokazane powyżej ( liblogger i liblog_rust ), wywołaj metodę init raz (możesz wywołać ją więcej niż raz, jeśli to konieczne) i rejestruj komunikaty przy użyciu dostarczonych makr. Zobacz skrzynkę rejestratora , aby uzyskać listę możliwych opcji konfiguracyjnych.

Skrzynka rejestratora zapewnia interfejs API do definiowania, co chcesz rejestrować. W zależności od tego, czy kod działa na urządzeniu, czy na hoście (na przykład jako część testu po stronie hosta), wiadomości są rejestrowane przy użyciu android_logger lub env_logger .

Przykład rdzy AIDL

Ta sekcja zawiera przykład użycia AIDL w stylu Hello World z Rustem.

Używając sekcji Przegląd AIDL Przewodnika dla programistów Androida jako punktu wyjścia, utwórz external/rust/binder_example/aidl/com/example/android/IRemoteService.aidl z następującą zawartością w pliku 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);
}

Następnie w pliku external/rust/binder_example/aidl/Android.bp zdefiniuj moduł aidl_interface . Musisz jawnie włączyć backend Rust, ponieważ nie jest on domyślnie włączony.

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

Backend AIDL jest generatorem źródeł Rust, więc działa jak inne generatory źródeł Rust i tworzy bibliotekę Rust. Wytworzony moduł biblioteki Rusta może być używany przez inne moduły Rusta jako zależność. Jako przykład wykorzystania wytworzonej biblioteki jako zależności, rust_library może być zdefiniowana w następujący sposób w 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",
    ],
}

Zauważ, że formatem nazwy modułu dla biblioteki generowanej przez AIDL używanej w rustlibs jest nazwa modułu aidl_interface , po której następuje -rust ; w tym przypadku com.example.android.remoteservice-rust .

Interfejs AIDL można następnie odwoływać się w src/lib.rs w następujący sposób:

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

Na koniec uruchom usługę w pliku binarnym Rust, jak pokazano poniżej:

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

Wywołanie Rust z C

Ten przykład pokazuje, jak wywołać Rust z C.

Przykładowa biblioteka Rust

Zdefiniuj plik libsimple_printer w external/rust/simple_printer/libsimple_printer.rs w następujący sposób:

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

Biblioteka Rust musi zdefiniować nagłówki, które mogą pobierać zależne moduły C, więc zdefiniuj nagłówek external/rust/simple_printer/simple_printer.h w następujący sposób:

#ifndef SIMPLE_PRINTER_H
#define SIMPLE_PRINTER_H

void print_c_hello_rust();


#endif

Zdefiniuj external/rust/simple_printer/Android.bp , jak widać tutaj:

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

Przykład C binarny

Zdefiniuj external/rust/c_hello_rust/main.c w następujący sposób:

#include "simple_printer.h"

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

Zdefiniuj external/rust/c_hello_rust/Android.bp w następujący sposób:

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

Na koniec skompiluj, wywołując m c_hello_rust .

Współpraca Rust–Java

Skrzynka jni zapewnia interoperacyjność Rust z Javą poprzez Java Native Interface (JNI). Definiuje niezbędne definicje typów dla Rust do tworzenia biblioteki cdylib Rust, która jest podłączana bezpośrednio do JNI Javy ( JNIEnv , JClass , JString , i tak dalej). W przeciwieństwie do powiązań C++, które wykonują codegen za pomocą cxx , współdziałanie Java za pośrednictwem JNI nie wymaga etapu generowania kodu podczas kompilacji. Dlatego nie wymaga specjalnego wsparcia systemu budowania. Kod Java ładuje bibliotekę cdylib dostarczoną przez Rust, jak każda inna biblioteka natywna.

Stosowanie

Wykorzystanie w kodzie Rust i Java jest opisane w dokumentacji jni crate . Postępuj zgodnie z podanym tam przykładem Pierwsze kroki . Po napisaniu src/lib.rs wróć do tej strony, aby dowiedzieć się, jak zbudować bibliotekę za pomocą systemu kompilacji Androida.

Definicja kompilacji

Java wymaga dostarczenia biblioteki Rust jako biblioteki cdylib , aby można ją było ładować dynamicznie. Definicja biblioteki Rust w Soong jest następująca:

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

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

Biblioteka Java wymienia bibliotekę Rust jako required zależność; zapewnia to, że jest on zainstalowany na urządzeniu wraz z biblioteką Java, mimo że nie jest to zależność czasu kompilacji:

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

Alternatywnie, jeśli musisz dołączyć bibliotekę Rust do pliku AndroidManifest.xml , dodaj bibliotekę do uses_libs w następujący sposób:

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

Współpraca Rust–C++ przy użyciu CXX

Skrzynia CXX zapewnia bezpieczne FFI między Rustem a podzbiorem C++. Dokumentacja CXX podaje ogólnie dobre przykłady tego, jak to działa. Poniższy przykład pokazuje, jak z niego korzystać w systemie Android.

Aby CXX generował kod C++, który wywołuje Rust, zdefiniuj genrule do wywoływania CXX i cc_library_static , aby umieścić go w bibliotece. Jeśli planujesz wywołanie C++ kodu Rust lub użycie typów współdzielonych między C++ i Rust, zdefiniuj drugą regułę (aby wygenerować nagłówek C++ zawierający powiązania 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"],
}

Następnie połącz to z biblioteką Rust lub plikiem wykonywalnym:

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

W plikach .cpp i .hpp zdefiniuj funkcje C++, jak chcesz, używając typów opakowań CXX zgodnie z potrzebami. Na przykład definicja cxx_test.hpp zawiera następujące elementy:

#pragma once

#include "rust/cxx.h"

int greet(rust::Str greetee);

Podczas gdy cxx_test.cpp zawiera

#include "cxx_test.hpp"
#include "lib.rs.h"

#include <iostream>

int greet(rust::Str greetee) {
  std::cout << "Hello, " << greetee << std::endl;
  return get_num();
}

Aby użyć tego z Rusta, zdefiniuj mostek CXX jak poniżej w 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;
}