Android Rostmuster

Diese Seite enthält Informationen zur Android-Protokollierung , ein Rust-AIDL-Beispiel , erklärt Ihnen, wie Sie Rust von C aus aufrufen , und enthält Anweisungen für Rust/C++ Interop Using CXX .

Android-Protokollierung

Das folgende Beispiel zeigt, wie Sie Nachrichten in logcat (auf dem Gerät) oder stdout (auf dem Host) protokollieren können.

Fügen Sie in Ihrem Android.bp -Modul liblogger und liblog_rust als Abhängigkeiten hinzu:

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

Als nächstes fügen Sie in Ihrer Rust-Quelle diesen Code hinzu:

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

Das heißt, fügen Sie die beiden oben gezeigten Abhängigkeiten ( liblogger und liblog_rust ) hinzu, rufen Sie die Methode init einmal auf (Sie können sie bei Bedarf mehr als einmal aufrufen) und protokollieren Sie Nachrichten mit den bereitgestellten Makros. Eine Liste möglicher Konfigurationsoptionen finden Sie in der Logger-Kiste .

Die Logger-Crate bietet eine API zum Definieren, was Sie protokollieren möchten. Je nachdem, ob der Code auf dem Gerät oder auf dem Host ausgeführt wird (z. B. als Teil eines hostseitigen Tests), werden Nachrichten entweder mit android_logger oder env_logger protokolliert .

Rust AIDL-Beispiel

Dieser Abschnitt enthält ein Beispiel im Stil von Hello World für die Verwendung von AIDL mit Rust.

Erstellen Sie mithilfe des Abschnitts Übersicht über AIDL im Android-Entwicklerhandbuch als Ausgangspunkt external/rust/binder_example/aidl/com/example/android/IRemoteService.aidl mit den folgenden Inhalten in der Datei 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);
}

Definieren Sie dann in der Datei external/rust/binder_example/aidl/Android.bp das Modul aidl_interface . Sie müssen das Rust-Backend explizit aktivieren , da es standardmäßig nicht aktiviert ist.

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

Das AIDL-Backend ist ein Rust-Quellgenerator, funktioniert also wie andere Rust-Quellgeneratoren und erzeugt eine Rust-Bibliothek. Das erzeugte Rust-Bibliotheksmodul kann von anderen Rust-Modulen als Abhängigkeit verwendet werden. Als Beispiel für die Verwendung der produzierten Bibliothek als Abhängigkeit kann eine rust_library wie folgt 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",
    ],
}

Beachten Sie, dass das Format des Modulnamens für die AIDL-generierte Bibliothek, die in rustlibs verwendet wird, der aidl_interface gefolgt von -rust ist; in diesem Fall com.example.android.remoteservice-rust .

Die AIDL-Schnittstelle kann dann in src/lib.rs wie folgt referenziert werden:

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

Starten Sie schließlich den Dienst in einer Rust-Binärdatei wie unten gezeigt:

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

Aufruf von Rust von C

Dieses Beispiel zeigt, wie man Rust von C aus aufruft.

Beispiel Rust-Bibliothek

Definieren Sie die Datei libsimple_printer in external/rust/simple_printer/libsimple_printer.rs wie folgt:

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

Die Rust-Bibliothek muss Header definieren, die die abhängigen C-Module abrufen können, also definieren Sie den external/rust/simple_printer/simple_printer.h Header wie folgt:

#ifndef SIMPLE_PRINTER_H
#define SIMPLE_PRINTER_H

void print_c_hello_rust();


#endif

Definieren Sie external/rust/simple_printer/Android.bp wie hier zu sehen:

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

Beispiel C binär

Definieren Sie external/rust/c_hello_rust/main.c wie folgt:

#include "simple_printer.h"

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

Definieren Sie external/rust/c_hello_rust/Android.bp wie folgt:

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

Erstellen Sie schließlich durch Aufrufen von m c_hello_rust .

Rust-Java-Interop

Die jni Kiste bietet Rust-Interoperabilität mit Java über das Java Native Interface (JNI). Es definiert die notwendigen Typdefinitionen für Rust, um eine Rust cdylib Bibliothek zu erstellen, die sich direkt in Javas JNI ( JNIEnv , JClass , JString , usw.) einklinken lässt. Im Gegensatz zu C++-Bindungen, die Codegen über cxx ausführen, erfordert die Java-Interoperabilität über JNI keinen Codegenerierungsschritt während eines Builds. Daher benötigt es keine spezielle Build-System-Unterstützung. Der Java-Code lädt die von Rust bereitgestellte cdylib wie jede andere native Bibliothek.

Verwendung

Die Verwendung in Rust- und Java-Code wird in der jni -Crate-Dokumentation behandelt . Bitte folgen Sie dem dort bereitgestellten Getting-Started- Beispiel. Nachdem Sie src/lib.rs haben, kehren Sie zu dieser Seite zurück, um zu erfahren, wie Sie die Bibliothek mit dem Build-System von Android erstellen.

Build-Definition

Java erfordert, dass die Rust-Bibliothek als cdylib bereitgestellt wird, damit sie dynamisch geladen werden kann. Die Definition der Rust-Bibliothek in Soong lautet wie folgt:

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

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

Die Java-Bibliothek listet die Rust-Bibliothek als required Abhängigkeit auf; Dadurch wird sichergestellt, dass es zusammen mit der Java-Bibliothek auf dem Gerät installiert wird, obwohl es sich nicht um eine Build-Time-Abhängigkeit handelt:

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

Wenn Sie die Rust-Bibliothek alternativ in eine AndroidManifest.xml -Datei einbinden müssen, fügen Sie die Bibliothek wie folgt zu uses_libs :

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

Rust–C++-Interop mit CXX

Die CXX- Kiste bietet sicheres FFI zwischen Rust und einer Teilmenge von C++. Die CXX-Dokumentation gibt gute Beispiele dafür, wie es im Allgemeinen funktioniert. Das folgende Beispiel zeigt, wie es in Android verwendet wird.

Damit CXX den C++-Code generiert, den Rust aufruft, definieren Sie eine genrule zum Aufrufen von CXX und eine cc_library_static , um dies in einer Bibliothek zu bündeln. Wenn Sie planen, dass C++ Rust-Code aufruft oder Typen verwendet, die zwischen C++ und Rust geteilt werden, definieren Sie eine zweite Genrule (um einen C++-Header zu generieren, der die Rust-Bindungen enthält).

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

Verknüpfen Sie dies dann mit einer Rust-Bibliothek oder einer ausführbaren Datei:

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

Definieren Sie in den .cpp und .hpp Dateien die C++-Funktionen wie gewünscht und verwenden Sie die CXX-Wrapper-Typen wie gewünscht. Beispielsweise enthält eine cxx_test.hpp Definition Folgendes:

#pragma once

#include "rust/cxx.h"

int greet(rust::Str greetee);

Während cxx_test.cpp enthält

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

#include <iostream>

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

Um dies von Rust aus zu verwenden, definieren Sie eine CXX-Bridge wie folgt 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;
}