이 페이지에서는 Android 로깅에 관한 정보와 Rust AIDL 예를 제공하며, C에서 Rust를 호출하는 방법과 CXX를 사용한 Rust/C++ 상호 운용성에 관해 설명합니다.
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
메서드를 한 번 호출하고(필요한 경우 두 번 이상 호출 가능), 제공된 매크로를 사용하여 메시지를 로깅합니다. 가능한 구성 옵션 목록은 로거 크레이트를 참고하세요.
로거 크레이트는 로깅할 항목을 정의하기 위한 API를 제공합니다. 코드가 온디바이스에서 실행되는지 아니면 온호스트에서 실행되는지(예: 호스트 측 테스트의 일환으로)에 따라 메시지가 android_logger 또는 env_logger를 사용하여 로깅됩니다.
Rust AIDL 예
이 섹션에서는 Rust에서 AIDL을 사용하는 Hello World 스타일의 예를 제공합니다.
Android 개발자 가이드 AIDL 개요 섹션을 시작점으로 참고하여, IRemoteService.aidl
파일에 아래의 콘텐츠로 external/rust/binder_example/aidl/com/example/android/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 모듈이 종속 항목으로 사용할 수 있습니다. 생성된 라이브러리를 종속 항목으로 사용하는 방법을 보여주는 예로, 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(())
}
}
마지막으로, 아래와 같이 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 예
이 섹션에서는 Async Rust에서 AIDL을 사용하는 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::main]
을 사용하는 대신 수동으로 tokio 런타임을 빌드하여 피할 수 있으며 그런 다음 block_on
메서드 외부에서 join_thread_pool
을 호출하면 됩니다.
또한 Rust 백엔드 생성 라이브러리에는 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 호출
이 예에서는 C에서 Rust를 호출하는 방법을 보여줍니다.
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!");
}
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
크레이트는 Java 네이티브 인터페이스(JNI)를 통해 Rust와 Java 간의 상호 운용성을 제공합니다. Rust에서 Java의 JNI(JNIEnv
, JClass
, JString
등)에 직접 연결하는 Rust cdylib
라이브러리를 생성하는 데 필요한 유형을 정의합니다. cxx
를 통해 codegen을 실행하는 C++ 결합과 달리 JNI를 통한 Java 상호 운용성은 빌드 중 코드 생성 단계가 필요하지 않습니다. 따라서 특별한 빌드 시스템을 지원할 필요가 없습니다. Java 코드는 다른 네이티브 라이브러리와 마찬가지로 Rust에서 제공하는 cdylib
를 로드합니다.
사용법
Rust 및 자바 코드 사용 방법은 jni
크레이트 문서에서 다룹니다. 이 문서에 제공된 시작하기 예를 따르세요. src/lib.rs
를 작성한 후 이 페이지로 돌아와 Android 빌드 시스템으로 라이브러리를 빌드하는 방법을 알아보세요.
빌드 정의
Java는 동적으로 로드할 수 있도록 Rust 라이브러리를 cdylib
로 제공해야 합니다. Soong의 Rust 라이브러리 정의는 다음과 같습니다.
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"]
[...]
}
또는 AndroidManifest.xml
파일에 Rust 라이브러리를 포함해야 하는 경우 다음과 같이 uses_libs
에 라이브러리를 추가합니다.
java_library {
name: "libhelloworld",
[...]
uses_libs: ["libhellorust"]
[...]
}
CXX를 사용한 Rust–C++ 상호 운용성
CXX 크레이트는 Rust와 C++ 하위 집합 간에 안전한 FFI를 제공합니다. CXX 문서는 일반적인 작동 방식을 보여주는 예를 제공하므로 먼저 문서를 읽고 라이브러리 및 라이브러리가 C++와 Rust를 브리징하는 방식을 익히는 것이 좋습니다. 다음 예에서는 Android에서 CXX 크레이트를 사용하는 방법을 보여줍니다.
CXX가 Rust가 호출하는 C++ 코드를 생성하도록 하려면 genrule
을 정의하여 CXX를 호출하고 cc_library_static
을 정의하여 이를 라이브러리에 번들화합니다. C++로 Rust 코드를 호출하거나 C++와 Rust 간에 공유되는 유형을 사용하려는 경우 Rust 결합을 포함하는 C++ 헤더를 생성하기 위해 두 번째 genrule을 정의합니다.
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
파일에서 CXX 래퍼 유형을 원하는 방식으로 사용하여 C++ 함수를 원하는 대로 정의합니다.
예를 들어, 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에서 사용하려면 아래와 같이 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;
}