এই পৃষ্ঠায় অ্যান্ড্রয়েড লগিং সম্পর্কিত তথ্য, একটি রাস্ট AIDL উদাহরণ , C থেকে রাস্ট কল করার পদ্ধতি এবং CXX ব্যবহার করে রাস্ট/C++ ইন্টারঅপারেশনের নির্দেশাবলী রয়েছে।
অ্যান্ড্রয়েড লগিং
নিম্নলিখিত উদাহরণটি দেখায় কিভাবে আপনি logcat (ডিভাইসে) বা stdout (হোস্টে) বার্তা লগ করতে পারেন।
আপনার Android.bp মডিউলে liblogger এবং liblog_rust ডিপেন্ডেন্সি হিসেবে যোগ করুন:
rust_binary {
name: "logging_test",
srcs: ["src/main.rs"],
rustlibs: [
"liblogger",
"liblog_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 ব্যবহার করে মেসেজগুলো লগ করা হয়।
রাস্ট AIDL উদাহরণ
এই অংশে রাস্টের সাথে AIDL ব্যবহারের একটি হ্যালো ওয়ার্ল্ড-ধাঁচের উদাহরণ দেওয়া হয়েছে।
অ্যান্ড্রয়েড ডেভেলপার গাইডের 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 মডিউলটি সংজ্ঞায়িত করুন। আপনাকে অবশ্যই রাস্ট ব্যাকএন্ডটি স্পষ্টভাবে সক্রিয় করতে হবে, কারণ এটি ডিফল্টরূপে সক্রিয় থাকে না।
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 ব্যাকএন্ড একটি রাস্ট সোর্স জেনারেটর, তাই এটি অন্যান্য রাস্ট সোর্স জেনারেটরের মতোই কাজ করে এবং একটি রাস্ট লাইব্রেরি তৈরি করে। উৎপাদিত রাস্ট লাইব্রেরি মডিউলটি অন্যান্য রাস্ট মডিউল দ্বারা একটি ডিপেন্ডেন্সি হিসেবে ব্যবহার করা যেতে পারে। উৎপাদিত লাইব্রেরিটিকে ডিপেন্ডেন্সি হিসেবে ব্যবহারের একটি উদাহরণ হিসেবে, external/rust/binder_example/Android.bp ফাইলে একটি rust_library নিম্নরূপে সংজ্ঞায়িত করা যেতে পারে:
rust_library {
name: "libmyservice",
srcs: ["src/lib.rs"],
crate_name: "myservice",
rustlibs: [
"com.example.android.remoteservice-rust",
"libbinder_rs",
],
}
উল্লেখ্য যে, rustlibs এ ব্যবহৃত AIDL-দ্বারা তৈরি লাইব্রেরির মডিউল নামের ফরম্যাটটি হলো aidl_interface মডিউল নামের পরে -rust যুক্ত করা; এই ক্ষেত্রে, com.example.android.remoteservice-rust ।
এরপর src/lib.rs এ AIDL ইন্টারফেসটি নিম্নোক্তভাবে উল্লেখ করা যেতে পারে:
// 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(())
}
}
অবশেষে, নিচে দেখানো অনুযায়ী একটি রাস্ট বাইনারিতে সার্ভিসটি চালু করুন:
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 উদাহরণ
এই বিভাগে অ্যাসিঙ্ক রাস্টের সাথে AIDL ব্যবহারের একটি হ্যালো ওয়ার্ল্ড-ধাঁচের উদাহরণ দেওয়া হয়েছে।
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::main] ব্যবহার না করে টোকিও রানটাইম ম্যানুয়ালি বিল্ড করে এবং তারপর block_on মেথডের বাইরে join_thread_pool কল করার মাধ্যমেও এটি এড়ানো যেতে পারে।
এছাড়াও, রাস্ট ব্যাকএন্ড দ্বারা জেনারেট করা লাইব্রেরিতে একটি ইন্টারফেস অন্তর্ভুক্ত রয়েছে যা RemoteService জন্য IRemoteServiceAsync নামক একটি অ্যাসিঙ্ক ক্লায়েন্ট ইমপ্লিমেন্ট করার সুযোগ দেয়, যা নিম্নোক্তভাবে প্রয়োগ করা যেতে পারে:
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),
}
}
সি থেকে রাস্টকে ডাকুন
এই উদাহরণটি দেখায় কিভাবে C থেকে Rust-কে কল করতে হয়।
উদাহরণ রাস্ট লাইব্রেরি
external/rust/simple_printer/libsimple_printer.rs এ libsimple_printer ফাইলটি নিম্নরূপে সংজ্ঞায়িত করুন:
//! 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!");
}
রাস্ট লাইব্রেরিকে অবশ্যই এমন হেডার ফাইল সংজ্ঞায়িত করতে হবে যা নির্ভরশীল সি মডিউলগুলো ব্যবহার করতে পারে, তাই 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: ["."],
}
উদাহরণ সি বাইনারি
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 ক্রেটটি জাভা নেটিভ ইন্টারফেস (JNI)-এর মাধ্যমে জাভার সাথে রাস্টের আন্তঃকার্যক্ষমতা প্রদান করে। এটি রাস্টের জন্য প্রয়োজনীয় টাইপ ডেফিনিশনগুলো নির্ধারণ করে, যার ফলে একটি রাস্ট cdylib লাইব্রেরি তৈরি হয় যা সরাসরি জাভার JNI-তে প্লাগ-ইন করা যায় ( JNIEnv , JClass , JString ইত্যাদি)। C++ বাইন্ডিংয়ের মতো নয়, যা cxx মাধ্যমে কোডজেন সম্পন্ন করে, JNI-এর মাধ্যমে জাভা আন্তঃকার্যক্ষমতার জন্য বিল্ডের সময় কোনো কোড-জেনারেটর ধাপের প্রয়োজন হয় না। তাই এর জন্য বিশেষ বিল্ড-সিস্টেম সাপোর্টের প্রয়োজন হয় না। জাভা কোড অন্য যেকোনো নেটিভ লাইব্রেরির মতোই রাস্ট-প্রদত্ত cdylib লোড করে।
ব্যবহার
রাস্ট এবং জাভা উভয় কোডেই এর ব্যবহার jni ক্রেট ডকুমেন্টেশনে বিস্তারিতভাবে বর্ণনা করা আছে। অনুগ্রহ করে সেখানে দেওয়া 'গেটিং স্টার্টেড' উদাহরণটি অনুসরণ করুন। src/lib.rs লেখার পর, অ্যান্ড্রয়েডের বিল্ড সিস্টেম দিয়ে লাইব্রেরিটি কীভাবে বিল্ড করতে হয় তা জানতে এই পৃষ্ঠায় ফিরে আসুন।
সংজ্ঞা তৈরি করুন
জাভাতে রাস্ট লাইব্রেরিকে cdylib হিসেবে সরবরাহ করতে হয়, যাতে এটিকে ডাইনামিকভাবে লোড করা যায়। Soong-এ রাস্ট লাইব্রেরির সংজ্ঞাটি নিম্নরূপ:
rust_ffi_shared {
name: "libhello_jni",
crate_name: "hello_jni",
srcs: ["src/lib.rs"],
// The jni crate is required
rustlibs: ["libjni"],
}
জাভা লাইব্রেরিটি রাস্ট লাইব্রেরিকে একটি required নির্ভরতা হিসেবে তালিকাভুক্ত করে; এটি নিশ্চিত করে যে, যদিও এটি কোনো বিল্ড-টাইম নির্ভরতা নয়, তবুও এটি জাভা লাইব্রেরির পাশাপাশি ডিভাইসে ইনস্টল হবে।
java_library {
name: "libhelloworld",
[...]
required: ["libhellorust"]
[...]
}
বিকল্পভাবে, যদি আপনাকে AndroidManifest.xml ফাইলে রাস্ট লাইব্রেরি অন্তর্ভুক্ত করতেই হয়, তাহলে লাইব্রেরিটি uses_libs এ নিম্নরূপভাবে যোগ করুন:
java_library {
name: "libhelloworld",
[...]
uses_libs: ["libhellorust"]
[...]
}
CXX ব্যবহার করে রাস্ট–সি++ ইন্টারঅপারেশন
CXX ক্রেটটি রাস্ট এবং সি++ এর একটি উপসেটের মধ্যে নিরাপদ FFI প্রদান করে। CXX ডকুমেন্টেশনে এটি সাধারণভাবে কীভাবে কাজ করে তার ভালো উদাহরণ দেওয়া আছে এবং লাইব্রেরিটি ও এটি যেভাবে সি++ এবং রাস্টের মধ্যে সংযোগ স্থাপন করে, তার সাথে পরিচিত হওয়ার জন্য আমরা প্রথমে সেটি পড়ার পরামর্শ দিই। নিচের উদাহরণটি দেখায় কীভাবে এটি অ্যান্ড্রয়েডে ব্যবহার করতে হয়।
রাস্ট যে C++ কোডকে কল করবে, তা CXX দিয়ে তৈরি করানোর জন্য, CXX-কে কল করতে একটি genrule এবং সেটিকে একটি লাইব্রেরিতে বান্ডল করতে একটি cc_library_static সংজ্ঞায়িত করুন। যদি আপনি C++ দিয়ে রাস্ট কোড কল করার পরিকল্পনা করেন, অথবা C++ এবং রাস্টের মধ্যে শেয়ার করা টাইপ ব্যবহার করতে চান, তাহলে দ্বিতীয় একটি জেনরুল সংজ্ঞায়িত করুন (রাস্ট বাইন্ডিং সম্বলিত একটি C++ হেডার তৈরি করার জন্য)।
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"],
}
ব্রিজটির C++ অংশ তৈরি করার জন্য উপরে cxxbridge টুলটি ব্যবহার করা হয়েছে। এরপরে আমাদের রাস্ট এক্সিকিউটেবলের একটি ডিপেন্ডেন্সি হিসেবে libcxx_test_cpp স্ট্যাটিক লাইব্রেরিটি ব্যবহার করা হয়:
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();
}
রাস্ট থেকে এটি ব্যবহার করতে, lib.rs ফাইলে নিচে দেখানো অনুযায়ী একটি CXX ব্রিজ সংজ্ঞায়িত করুন:
#[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;
}