Android-Rostmuster

Diese Seite enthält Informationen zu Android Logging, ein Rust AIDL-Beispiel, das Ihnen zeigt, wie Sie Russ von C anrufen und eine Anleitung geben für Rust/C++ Interop Using CXX.

Android-Logging

Das folgende Beispiel zeigt, wie Sie Nachrichten in logcat (auf dem Gerät) oder stdout (auf Organisator).

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

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

Fügen Sie als Nächstes in Ihrer Rust-Quelle diesen Code hinzu:

use log::{debug, error, LevelFilter};

fn main() {
    let _init_success = logger::init(
        logger::Config::default()
            .with_tag_on_device("mytag")
            .with_max_level(LevelFilter::Trace),
    );
    debug!("This is a debug message.");
    error!("Something went wrong!");
}

Fügen Sie also die beiden oben gezeigten Abhängigkeiten (liblogger und liblog_rust) hinzu. Rufen Sie die Methode init einmal auf (Sie können sie bei Bedarf mehrmals aufrufen) und Protokollmeldungen mithilfe der bereitgestellten Makros. Weitere Informationen finden Sie in der Holzkiste finden Sie eine Liste der möglichen Konfigurationsoptionen.

Die Protokollierungsbox bietet ein API, mit dem Sie definieren können, was protokolliert werden soll. Je nach ob der Code auf dem Gerät oder auf dem Host ausgeführt wird (z. B. Teil eines Host-seitiger Test), werden die Meldungen entweder mit android_logger oder env_logger.

Rust AIDL-Beispiel

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

Android-Entwicklerleitfaden verwenden AIDL Overview (in englischer Sprache) Abschnitt als Ausgangspunkt verwenden, erstellen Sie external/rust/binder_example/aidl/com/example/android/IRemoteService.aidl mit dem folgenden Inhalt 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 Modul aidl_interface. Sie müssen das Rust-Back-End explizit aktivieren, da es nicht standardmäßig 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-Back-End ist ein Rust-Quellgenerator und funktioniert daher wie eine andere Rust-Quelle. Generatoren und produziert eine Rust-Bibliothek. Das erzeugte Rust-Bibliotheksmodul von anderen Rust-Modulen als Abhängigkeit verwendet. Als Beispiel für die Verwendung des Bibliothek als Abhängigkeit hat, kann rust_library folgendermaßen definiert werden: 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",
    ],
}

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

Auf die AIDL-Schnittstelle kann dann in src/lib.rs so verwiesen 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 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(())
    }
}

Abschließend starten Sie den Dienst in einem Rust-Binärprogramm, 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.as_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()
}

Beispiel für Async Rust AIDL

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

Fahren wir mit dem Beispiel RemoteService fort: der generierten AIDL-Back-End-Bibliothek enthält asynchrone Schnittstellen, mit denen ein asynchroner Server implementiert werden kann. Implementierung für die AIDL-Schnittstelle RemoteService.

Die generierte asynchrone Serverschnittstelle IRemoteServiceAsyncServer kann wie folgt implementiert:

use com_example_android_remoteservice::aidl::com::example::android::IRemoteService::{
    BnRemoteService, IRemoteServiceAsyncServer,
};
use binder::{BinderFeatures, Interface, Result as BinderResult};

/// This struct is defined to implement IRemoteServiceAsyncServer AIDL interface.
pub struct MyAsyncService;

impl Interface for MyAsyncService {}

#[async_trait]
impl IRemoteServiceAsyncServer for MyAsyncService {
    async fn getPid(&self) -> BinderResult<i32> {
        //Do something interesting...
        Ok(42)
    }

    async fn basicTypes(&self, _: i32, _: i64, _: bool, _: f32, _: f64,_: &str,) -> BinderResult<()> {
        //Do something interesting...
        Ok(())
    }
}

Die asynchrone Serverimplementierung kann so gestartet werden:

#[tokio::main(flavor = "multi_thread", worker_threads = 2)]
async fn main() {
    binder::ProcessState::start_thread_pool();

    let my_service = MyAsyncService;
    let my_service_binder = BnRemoteService::new_async_binder(
        my_service,
        TokioRuntime(Handle::current()),
        BinderFeatures::default(),
    );

    binder::add_service("myservice", my_service_binder.as_binder())
        .expect("Failed to register service?");

    task::block_in_place(move || {
        binder::ProcessState::join_thread_pool();
    });
}

Das Feld an_Ort_blockieren ist erforderlich, um den asynchronen Kontext zu verlassen, der es join_thread_pool ermöglicht, intern block_on. Das liegt daran, dass #[tokio::main] den Code in einem Anruf an block_on und join_thread_pool ruft möglicherweise block_on fest, wenn eine eingehende Transaktion verarbeitet wird. Durch Aufrufen einer block_on von einem block_on-Gerät aus führt zu einer Panik. Dies könnte auch durch Erstellen der Tokio-Laufzeit manuell statt #[tokio::main] zu verwenden und dann join_thread_pool aufzurufen außerhalb der Methode block_on.

Darüber hinaus enthält die Rust-Back-End-Bibliothek eine Schnittstelle, mit der Implementierung eines asynchronen Clients IRemoteServiceAsync für RemoteService implementieren, der wie folgt implementiert werden:

use com_example_android_remoteservice::aidl::com::example::android::IRemoteService::IRemoteServiceAsync;
use binder_tokio::Tokio;

#[tokio::main(flavor = "current_thread")]
async fn main() {
    let binder_service = binder_tokio::wait_for_interface::<dyn IRemoteServiceAsync<Tokio>>("myservice");

    let my_client = binder_service.await.expect("Cannot find Remote Service");

    let result = my_client.getPid().await;

    match result {
        Err(err) => panic!("Cannot get the process id from Remote Service {:?}", err),
        Ok(p_id) => println!("PID = {}", p_id),
    }
}

Rust von C anrufen

In diesem Beispiel wird gezeigt, wie Rust aus C aufgerufen wird.

Rust-Beispielbibliothek

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 von den abhängigen C-Modulen abgerufen werden können. Definieren Sie den external/rust/simple_printer/simple_printer.h-Header daher so:

#ifndef SIMPLE_PRINTER_H
#define SIMPLE_PRINTER_H

void print_c_hello_rust();


#endif

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

rust_ffi {
    name: "libsimple_c_printer",
    crate_name: "simple_c_printer",
    srcs: ["libsimple_c_printer.rs"],

    // Define export_include_dirs so cc_binary knows where the headers are.
    export_include_dirs: ["."],
}

Beispiel-C-Binärprogramm

Definieren Sie external/rust/c_hello_rust/main.c so:

#include "simple_printer.h"

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

Definieren Sie external/rust/c_hello_rust/Android.bp so:

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

Zuletzt erstellen Sie den Build durch Aufrufen von m c_hello_rust.

Rust-Java-Interoperabilität

Die Box jni bietet Rust-Interoperabilität mit Java über die native Java-Umgebung Schnittstelle (JNI). Es definiert die notwendigen Typdefinitionen, damit Rust eine Rust-cdylib-Bibliothek, die direkt in die JNI von Java eingebunden ist (JNIEnv, JClass, JString usw.). Im Gegensatz zu C++-Bindungen, die Codegen über cxx ausführen, Java-Interoperabilität über JNI erfordert keinen Schritt zur Codegenerierung während eines Build-Vorgangs. Daher ist keine spezielle Unterstützung für das Build-System erforderlich. Java wird die von Rust bereitgestellte cdylib wie jede andere native Bibliothek geladen.

Nutzung

Die Verwendung in Rust- und Java-Code wird im Dokumentation zu jni-Kisten. Bitte folgen Sie der Anleitung unter Erste Schritte. ein entsprechendes Beispiel. Nachdem Sie src/lib.rs geschrieben haben, kehren Sie zu dieser Seite zurück, um erfahren Sie, wie Sie die Bibliothek mit dem Build-System von Android erstellen.

Build-Definition

Für Java muss die Rust-Bibliothek als cdylib bereitgestellt werden, damit sie dynamisch geladen werden. 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 ist keine Abhängigkeit zur Build-Zeit:

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

Alternativ, wenn Sie die Rust-Bibliothek in eine AndroidManifest.xml aufnehmen müssen fügen Sie die Bibliothek so zu uses_libs hinzu:

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

Rust-C++-Interoperabilität mit CXX

Die CXX-Kiste ermöglicht sicheres FFI zwischen Rust und einer Teilmenge von C++. Die CXX-Dokumentation gibt gute Beispiele für die allgemeine Funktionsweise. Wir empfehlen, es zuerst zu lesen. um sich mit der Bibliothek und ihrer Verbindung zwischen C++ und Rust vertraut zu machen. Die Das folgende Beispiel zeigt, wie sie in Android verwendet werden.

Damit CXX den C++-Code generiert, den Rust aufruft, definieren Sie eine genrule, um Rufe CXX und ein cc_library_static auf, um das in einer Bibliothek zu bündeln. Wenn Sie Um Rust-Code in C++ aufzurufen oder Typen zu verwenden, die von C++ und Rust gemeinsam genutzt werden, definieren Sie einen second Genrule (zum Generieren eines C++ Headers, 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"],
}

// Generate the C++ code that Rust calls into.
genrule {
    name: "libcxx_test_bridge_code",
    tools: ["cxxbridge"],
    cmd: "$(location cxxbridge) $(in) > $(out)",
    srcs: ["lib.rs"],
    out: ["libcxx_test_cxx_generated.cc"],
}

// Generate a C++ header containing 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"],
}

Das cxxbridge-Tool wird oben verwendet, um die C++-Seite der Brücke zu generieren. Das libcxx_test_cpp Als Nächstes wird die statische Bibliothek als Abhängigkeit für unsere Rust-Programmdatei verwendet:

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

Definieren Sie in den Dateien .cpp und .hpp die C++-Funktionen nach Bedarf. Verwenden Sie nach Bedarf die CXX-Wrapper-Typen. Eine cxx_test.hpp-Definition enthält beispielsweise Folgendes:

#pragma once

#include "rust/cxx.h"
#include "lib.rs.h"

int greet(rust::Str greetee);

Während cxx_test.cppenthä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 diese aus Rust zu verwenden, definieren Sie eine CXX-Bridge wie unten 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;
}