รูปแบบสนิมของ Android

หน้านี้ประกอบด้วยข้อมูลเกี่ยวกับ Android Logging ให้ตัวอย่าง Rust AIDL บอกวิธี เรียก Rust จาก C และให้คำแนะนำสำหรับ Rust/C++ Interop โดยใช้ CXX

การบันทึก Android

ตัวอย่างต่อไปนี้แสดงวิธีบันทึกข้อความไปยัง 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

ส่วนนี้แสดงตัวอย่างการใช้ AIDL กับ Rust ในรูปแบบ Hello World

ใช้ส่วน ภาพรวม AIDL คู่มือนักพัฒนาซอฟต์แวร์ Android เป็นจุดเริ่มต้น สร้าง 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 โมดูลไลบรารี 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.

ตัวอย่าง Rust Library

กำหนดไฟล์ 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: ["."],
}

ตัวอย่าง C ไบนารี

กำหนด 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

Rust–Java interop

jni crate จัดเตรียมความสามารถในการทำงานร่วมกันของ Rust กับ Java ผ่าน Java Native Interface (JNI) มันกำหนดคำจำกัดความประเภทที่จำเป็นสำหรับ Rust เพื่อสร้างไลบรารี Rust cdylib ที่เสียบเข้ากับ JNI ของ Java โดยตรง ( JNIEnv , JClass , JString เป็นต้น) ไม่เหมือนกับการเชื่อมโยง C++ ที่ทำ codegen ผ่าน cxx การทำงานร่วมกันของ Java ผ่าน JNI ไม่ต้องการขั้นตอนการสร้างรหัสระหว่างการสร้าง ดังนั้นจึงไม่ต้องการการสนับสนุนระบบบิลด์แบบพิเศษ โค้ด Java โหลด cdylib ที่ให้มากับ Rust เช่นเดียวกับไลบรารีดั้งเดิมอื่น ๆ

การใช้งาน

การใช้งานทั้งในโค้ด Rust และ Java นั้นครอบคลุมอยู่ใน เอกสาร jni crate โปรดปฏิบัติตามตัวอย่าง การเริ่มต้น ใช้งานที่ให้ไว้ที่นั่น หลังจากที่คุณเขียน src/lib.rs ให้กลับมาที่หน้านี้เพื่อเรียนรู้วิธีสร้างไลบรารีด้วยระบบบิลด์ของ Android

สร้างคำจำกัดความ

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++ โดยใช้ CXX

ลัง CXX ให้ FFI ที่ปลอดภัยระหว่าง Rust และชุดย่อยของ C ++ เอกสาร CXX ให้ตัวอย่างที่ดีเกี่ยวกับวิธีการทำงานโดยทั่วไป ตัวอย่างต่อไปนี้แสดงวิธีการใช้งานใน Android

หากต้องการให้ CXX สร้างโค้ด C++ ที่ Rust เรียกใช้ ให้กำหนด genrule เพื่อเรียกใช้ CXX และ cc_library_static เพื่อรวมกลุ่มนั้นไว้ในไลบรารี หากคุณวางแผนที่จะใช้รหัส Rust ของ C++ หรือใช้ประเภทที่แชร์ระหว่าง 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++ ตามที่คุณต้องการ โดยใช้ ประเภท wrapper 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;
}