تتضمّن هذه الصفحة معلومات حول تسجيل الدخول إلى Android، وهي تقدّم مثال Rust AIDL وتخبرك بكيفية استدعاء Rust من C ، وتقدّم تعليمات حول التشغيل التفاعلي لـ Rust/C++ باستخدام 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, 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!");
}
وهذا يعني إضافة الاعتمادتين التاليتين الواردتين أعلاه (liblogger
وliblog_rust
)،
واستدعاء الطريقة init
مرة واحدة (يمكنك استدعاؤها أكثر من مرة إذا لزم الأمر)،
وتسجيل الرسائل باستخدام وحدات الماكرو المتوفرة. اطّلِع على صندوق المسجّل للحصول على قائمة بخيارات الإعداد الممكنة.
يوفر صندوق المسجِّل واجهة برمجة تطبيقات لتحديد ما تريد تسجيله. بناءً على ما إذا كان الرمز يعمل على الجهاز أو عند المضيف (مثل جزء من اختبار من جهة المضيف)، يتم تسجيل الرسائل باستخدام android_logger أو env_logger.
مثال على Rust AIDL
يقدّم هذا القسم مثالاً على طريقة Hello World لاستخدام AIDL مع Rust.
استخدِم قسم نظرة عامة على 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);
}
بعد ذلك، حدِّد الوحدة aidl_interface
في ملف external/rust/binder_example/aidl/Android.bp
. يجب تفعيل واجهة 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 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()
}
مثال على Async Rust AIDL
يوفّر هذا القسم مثالاً على غرار Hello World لاستخدام AIDL مع Rust غير المتزامن.
استنادًا إلى مثال 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
توفّر حزمة jni
إمكانية التشغيل التفاعلي لـ Rust مع Java من خلال واجهة Java Native
Interface (JNI). ويحدِّد التعريفات اللازمة للأنواع في Rust لإنشاء مكتبة cdylib
في Rust يتم توصيلها مباشرةً بواجهة JNI في Java (JNIEnv
وJClass
JString
وما إلى ذلك). على عكس عمليات ربط C++ التي تُجري عملية إنشاء الرموز البرمجية من خلال cxx
،
لا تتطلّب إمكانية التشغيل التفاعلي لـ Java من خلال واجهة JNI خطوة إنشاء رموز برمجية
أثناء عملية الإنشاء. وبالتالي، لا يحتاج إلى دعم خاص في نظام إنشاء المحتوى. تحمِّل رمز Java
cdylib
المقدَّمة من Rust مثل أي مكتبة أصلية أخرى.
الاستخدام
يمكن الاطّلاع على الاستخدام في كل من رمز Rust وJava من خلال
مستند قفص jni
. يُرجى
اتّباع مثال البدء
المقدَّم هناك. بعد كتابة 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 أمثلة جيدة على آلية عملها بشكل عام، وننصح بقراءتها أولاً للتعرّف على المكتبة وطريقة ربطها بين C++ وRust. يوضح المثال التالي كيفية استخدامه في 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"],
}
// 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
كتبعية لملف 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"
#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 كما هو موضّح أدناه في 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;
}