หน้านี้มีข้อมูลเกี่ยวกับการบันทึกใน Android มีตัวอย่าง AIDL ของ Rust บอกวิธี เรียกใช้ Rust จาก C และมีวิธีการ สำหรับการทำงานร่วมกันของ Rust/C++ โดยใช้ CXX
การบันทึก Android
ตัวอย่างต่อไปนี้แสดงวิธีบันทึกข้อความไปยัง logcat
(ในอุปกรณ์) หรือ
stdout
(ในโฮสต์)
ในโมดูล Android.bp
ให้เพิ่ม liblogger
และ liblog_rust
เป็นทรัพยากร Dependency ดังนี้
rust_binary {
name: "logging_test",
srcs: ["src/main.rs"],
rustlibs: [
"liblogger",
"liblog_rust",
],
}
จากนั้นเพิ่มโค้ดนี้ในแหล่งที่มาของ Rust
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!");
}
กล่าวคือ ให้เพิ่มการขึ้นต่อกัน 2 รายการที่แสดงด้านบน (liblogger
และ liblog_rust
)
เรียกใช้เมธอด init
หนึ่งครั้ง (คุณเรียกใช้ได้มากกว่า 1 ครั้งหากจำเป็น) และ
บันทึกข้อความโดยใช้มาโครที่ระบุ ดูรายการตัวเลือกการกำหนดค่าที่เป็นไปได้ใน
logger crate
Crate ของโปรแกรมบันทึกมี API สำหรับกำหนดสิ่งที่คุณต้องการบันทึก ข้อความจะได้รับการบันทึกโดยใช้ android_logger หรือ env_logger ทั้งนี้ขึ้นอยู่กับว่าโค้ดทำงานในอุปกรณ์หรือในโฮสต์ (เช่น เป็นส่วนหนึ่งของ การทดสอบฝั่งโฮสต์)
ตัวอย่าง AIDL ของ Rust
ส่วนนี้จะแสดงตัวอย่างการใช้ 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 ที่สร้างขึ้นสามารถใช้เป็นทรัพยากร Dependency โดยโมดูล Rust อื่นๆ ได้
ตัวอย่างการใช้ไลบรารีที่สร้างขึ้นเป็นทรัพยากร Dependency สามารถกำหนด 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 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.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()
}
ตัวอย่าง AIDL ของ Rust แบบอะซิงโครนัส
ส่วนนี้แสดงตัวอย่างการใช้ AIDL กับ Rust แบบไม่พร้อมกันในสไตล์ "Hello World"
จากRemoteService
ตัวอย่างเดิม ไลบรารีแบ็กเอนด์ AIDL ที่สร้างขึ้น
มีอินเทอร์เฟซแบบไม่พร้อมกันซึ่งใช้เพื่อติดตั้งใช้งานเซิร์ฟเวอร์แบบไม่พร้อมกัน
สำหรับอินเทอร์เฟซ AIDL RemoteService
อินเทอร์เฟซเซิร์ฟเวอร์แบบอะซิงโครนัสที่สร้างขึ้น IRemoteServiceAsyncServer
สามารถ
นำไปใช้ได้ดังนี้
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(())
}
}
คุณเริ่มการติดตั้งใช้งานเซิร์ฟเวอร์แบบไม่พร้อมกันได้ดังนี้
#[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();
});
}
โปรดทราบว่าต้องมี
block_in_place
เพื่อออกจากบริบทแบบไม่พร้อมกัน ซึ่งจะช่วยให้ join_thread_pool
ใช้
block_on ภายในได้ เนื่องจาก #[tokio::main]
จะห่อหุ้มโค้ด
ในการเรียกใช้ block_on
และ join_thread_pool
อาจเรียกใช้
block_on
เมื่อจัดการธุรกรรมขาเข้า การเรียกใช้
block_on
จากภายใน block_on
จะทำให้เกิดข้อผิดพลาดร้ายแรง นอกจากนี้ คุณยังหลีกเลี่ยงปัญหานี้ได้โดยการสร้างรันไทม์ของ Tokio ด้วยตนเองแทนการใช้ #[tokio::main]
แล้วเรียกใช้ join_thread_pool
นอกเมธอด block_on
นอกจากนี้ ไลบรารีที่สร้างขึ้นจากแบ็กเอนด์ Rust ยังมีอินเทอร์เฟซที่ช่วยให้
ใช้ไคลเอ็นต์แบบไม่พร้อมกัน IRemoteServiceAsync
สำหรับ RemoteService
ซึ่งสามารถ
ใช้ได้ดังนี้
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 จาก C
ตัวอย่างนี้แสดงวิธีเรียกใช้ Rust จาก C
ตัวอย่างไลบรารี Rust
กำหนดไฟล์ 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 export_include_dirs so cc_binary knows where the headers are.
export_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
Crate jni
ช่วยให้ Rust ทำงานร่วมกับ Java ได้ผ่าน Java Native
Interface (JNI) ซึ่งจะกำหนดคำจำกัดความประเภทที่จำเป็นสำหรับ Rust เพื่อสร้างไลบรารี cdylib
Rust ที่เชื่อมต่อกับ JNI ของ Java โดยตรง (JNIEnv
, JClass
, JString
และอื่นๆ) การทำงานร่วมกันของ Java ผ่าน JNI ไม่จำเป็นต้องมีขั้นตอนการสร้างโค้ดในระหว่างการบิลด์ ซึ่งแตกต่างจากการเชื่อมโยง C++ ที่ดำเนินการสร้างโค้ดผ่าน cxx
จึงไม่จำเป็นต้องรองรับระบบบิลด์พิเศษ โค้ด 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
Dependency
ซึ่งช่วยให้มั่นใจได้ว่าจะมีการติดตั้งไลบรารี Rust ลงในอุปกรณ์พร้อมกับไลบรารี Java แม้ว่า
จะไม่ใช่ Dependency ในเวลาบิลด์ก็ตาม
java_library {
name: "libhelloworld",
[...]
required: ["libhellorust"]
[...]
}
หรือหากต้องรวมไลบรารี Rust ไว้ในAndroidManifest.xml
ไฟล์ ให้เพิ่มไลบรารีลงใน uses_libs
ดังนี้
java_library {
name: "libhelloworld",
[...]
uses_libs: ["libhellorust"]
[...]
}
การทำงานร่วมกันระหว่าง Rust กับ C++ โดยใช้ CXX
Crate CXX มี FFI ที่ปลอดภัย ระหว่าง Rust กับชุดย่อยของ C++ เอกสารประกอบของ CXX มีตัวอย่างที่ดีเกี่ยวกับวิธีการทำงานโดยทั่วไป และเราขอแนะนำให้อ่านเอกสารนี้ก่อน เพื่อให้คุ้นเคยกับไลบรารีและวิธีที่ไลบรารีนี้เชื่อมต่อ C++ กับ Rust ตัวอย่างต่อไปนี้แสดงวิธีใช้ใน Android
หากต้องการให้ CXX สร้างโค้ด C++ ที่ Rust เรียกใช้ ให้กำหนด genrule
เพื่อ
เรียกใช้ CXX และ cc_library_static
เพื่อรวมโค้ดนั้นไว้ในไลบรารี หากคุณวางแผน
ที่จะให้ C++ เรียกใช้โค้ด Rust หรือใช้ประเภทที่แชร์ระหว่าง C++ กับ Rust ให้กำหนด
genrule ที่ 2 (เพื่อสร้างส่วนหัว C++ ที่มี Binding ของ 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"],
}
// 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"],
}
cxxbridge
เครื่องมือ
ใช้ด้านบนเพื่อสร้างฝั่ง C++ ของบริดจ์ จากนั้นจะใช้libcxx_test_cpp
ไลบรารีแบบคงที่เป็นทรัพยากร Dependency สำหรับไฟล์ปฏิบัติการ 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"
#include "lib.rs.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 Bridge ดังที่แสดงด้านล่างใน 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;
}