דפוסי חלודה של אנדרואיד

דף זה מכיל מידע על רישום אנדרואיד , מספק דוגמה של Rust AIDL , אומר לך כיצד להתקשר ל-Rust מ-C , ומספק הוראות עבור Rust/C++ Interop באמצעות CXX .

רישום אנדרואיד

הדוגמה הבאה מראה כיצד ניתן לרשום הודעות ל- logcat (במכשיר) או ל- stdout (במארח).

במודול Android.bp שלך, הוסף liblogger ו- liblog_rust כתלות:

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

לאחר מכן, במקור Rust שלך הוסף את הקוד הזה:

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

כלומר, הוסף את שתי התלות המוצגות לעיל ( liblogger ו- liblog_rust ), קרא למתודה init פעם אחת (אפשר לקרוא לה יותר מפעם אחת במידת הצורך), ויומן הודעות באמצעות פקודות המאקרו המסופקות. עיין בארגז לוגר עבור רשימה של אפשרויות תצורה אפשריות.

ארגז לוגר מספק API להגדרת מה אתה רוצה לרשום. תלוי אם הקוד פועל במכשיר או במארח (כגון חלק מבדיקה בצד המארח), הודעות נרשמות באמצעות android_logger או env_logger .

דוגמה ל- Rust AIDL

סעיף זה מספק דוגמה בסגנון Hello World לשימוש ב-AIDL עם Rust.

השתמש ב-Android Developer Guide AIDL סקירה כללית כנקודת התחלה, צור external/rust/binder_example/aidl/com/example/android/IRemoteService.aidl עם התכנים הבאים בקובץ 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);
}

לאחר מכן, בתוך הקובץ external/rust/binder_example/aidl/Android.bp , הגדר את מודול aidl_interface . עליך להפעיל במפורש את החלק האחורי של Rust מכיוון שהוא אינו מופעל כברירת מחדל.

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

הקצה האחורי של AIDL הוא מחולל מקור חלודה, כך שהוא פועל כמו מחוללים אחרים של מקור חלודה ומייצר ספריית חלודה. מודול ספריית Rust המיוצר יכול לשמש מודולי Rust אחרים כתלות. כדוגמה לשימוש בספרייה המיוצרת כתלות, ניתן להגדיר rust_library באופן הבא ב- 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",
    ],
}

שים לב שפורמט שם המודול עבור הספרייה שנוצרה על ידי AIDL המשמשת ב- rustlibs הוא שם המודול aidl_interface ואחריו -rust ; במקרה זה, com.example.android.remoteservice-rust .

לאחר מכן ניתן להפנות לממשק AIDL ב- src/lib.rs באופן הבא:

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

לבסוף, התחל את השירות בבינארי Rust כפי שמוצג להלן:

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

מתקשר ל-Rust מ-C

דוגמה זו מראה כיצד לקרוא ל- Rust מ- C.

ספריית חלודה לדוגמה

הגדר את הקובץ libsimple_printer ב- external/rust/simple_printer/libsimple_printer.rs באופן הבא:

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

ספריית Rust חייבת להגדיר כותרות שאותן מודולי C התלויים יכולים למשוך פנימה, אז הגדר את הכותרת external/rust/simple_printer/simple_printer.h באופן הבא:

#ifndef SIMPLE_PRINTER_H
#define SIMPLE_PRINTER_H

void print_c_hello_rust();


#endif

הגדר external/rust/simple_printer/Android.bp כפי שאתה רואה כאן:

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

דוגמה ג בינארית

הגדר external/rust/c_hello_rust/main.c באופן הבא:

#include "simple_printer.h"

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

הגדר external/rust/c_hello_rust/Android.bp באופן הבא:

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

לבסוף, בנה על ידי קריאה m c_hello_rust .

אינטררופ חלודה-ג'אווה

ארגז jni מספק יכולת פעולה הדדית של Rust עם Java באמצעות ממשק המקור של Java (JNI). הוא מגדיר את הגדרות הסוג הדרושות ל-Rust כדי לייצר ספריית Rust cdylib ישירות ל-JNI של Java ( JNIEnv , JClass , JString , וכן הלאה). בניגוד לקשרי C++ שמבצעים קודגן דרך cxx , יכולת פעולה הדדית של Java דרך JNI אינה דורשת שלב של יצירת קוד במהלך בנייה. לכן זה לא צריך תמיכה מיוחדת של מערכת בנייה. קוד ה-Java טוען את ה- cdylib המסופק ב-Rust כמו כל ספרייה מקומית אחרת.

נוֹהָג

השימוש בקוד Rust וגם ב-Java מכוסה בתיעוד של ארגז jni . אנא עקוב אחר הדוגמה לתחילת העבודה המופיעה שם. לאחר שתכתוב את src/lib.rs , חזור לדף זה כדי ללמוד כיצד לבנות את הספרייה עם מערכת הבנייה של אנדרואיד.

הגדרת בנייה

Java דורשת שספריית Rust תסופק כ- cdylib כדי שניתן יהיה לטעון אותה באופן דינמי. ההגדרה של ספריית Rust ב- Soong היא הבאה:

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

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

ספריית Java מפרטת את ספריית Rust כתלות required ; זה מבטיח שהוא מותקן במכשיר לצד ספריית Java למרות שזה לא תלוי בזמן הבנייה:

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

לחלופין, אם עליך לכלול את ספריית Rust בקובץ AndroidManifest.xml , הוסף את הספרייה ל- uses_libs באופן הבא:

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

Rust–C++ Interop באמצעות CXX

ארגז CXX מספק FFI בטוח בין Rust לבין תת-קבוצה של C++. תיעוד CXX נותן דוגמאות טובות לאיך זה עובד באופן כללי. הדוגמה הבאה מראה כיצד להשתמש בו באנדרואיד.

כדי ש-CXX יפיק את הקוד C++ שאליו Rust מתקשר, הגדר genrule להפעלת CXX ו- cc_library_static כדי לאגד אותו לספרייה. אם אתה מתכנן להתקשר ל-C++ בקוד Rust, או להשתמש בסוגים משותפים בין C++ ו-Rust, הגדר ז'נרל שני (כדי ליצור כותרת C++ המכילה את הכריכות 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"],
}

לאחר מכן קשר את זה לספריית Rust או לקובץ הפעלה:

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

בקבצי .cpp ו-. .hpp , הגדר את פונקציות C++ כרצונך, תוך שימוש בסוגי העטיפה של CXX כרצונך. לדוגמה, הגדרת cxx_test.hpp מכילה את הדברים הבאים:

#pragma once

#include "rust/cxx.h"

int greet(rust::Str greetee);

בעוד cxx_test.cpp מכיל

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

#include <iostream>

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

כדי להשתמש בזה מ-Rust, הגדר גשר CXX כמו להלן ב- 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;
}