안드로이드 러스트 패턴

이 페이지는 Android Logging 에 대한 정보를 포함하고, Rust AIDL 예제 를 제공하고, C에서 Rust를 호출 하는 방법을 알려주고, CXX를 사용한 Rust/C++ Interop 에 대한 지침을 제공합니다.

안드로이드 로깅

다음 예는 logcat (on-device) 또는 stdout (on-host)에 메시지를 기록하는 방법을 보여줍니다.

Android.bp 모듈에서 libloggerliblog_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!");
}

즉, 위에 표시된 두 가지 종속성( libloggerliblog_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 생성 라이브러리의 모듈 이름 형식은 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()
}

C에서 Rust 호출하기

이 예제는 C에서 Rust를 호출하는 방법을 보여줍니다.

Rust 라이브러리의 예

다음과 같이 external/rust/simple_printer/libsimple_printer.rslibsimple_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 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 를 호출하여 빌드합니다.

러스트-자바 상호 운용성

jni 크레이트는 JNI(Java Native Interface)를 통해 Java와 Rust 상호 운용성을 제공합니다. 이것은 Rust가 Java의 JNI( JNIEnv , JClass , JString 등)에 직접 연결되는 Rust cdylib 라이브러리를 생성하는 데 필요한 유형 정의를 정의합니다. cxx 를 통해 codegen을 수행하는 C++ 바인딩과 달리 JNI를 통한 Java 상호 운용성은 빌드 중 코드 생성 단계가 필요하지 않습니다. 따라서 특별한 빌드 시스템 지원이 필요하지 않습니다. Java 코드는 다른 네이티브 라이브러리와 마찬가지로 Rust에서 제공하는 cdylib 를 로드합니다.

용법

Rust 및 Java 코드에서의 사용법은 jni crate 문서 에서 다룹니다. 거기에 제공된 시작하기 예제를 따르십시오. 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++ Interop

CXX 크레이트는 Rust와 C++의 하위 집합 간에 안전한 FFI를 제공합니다. CXX 문서 는 일반적으로 어떻게 작동하는지에 대한 좋은 예를 제공합니다. 다음 예제는 Android에서 사용하는 방법을 보여줍니다.

CXX가 Rust가 호출하는 C++ 코드를 생성하도록 하려면 CXX를 호출하는 genrule 을 정의하고 이를 라이브러리로 묶는 cc_library_static 을 정의하십시오. C++에서 Rust 코드를 호출하거나 C++와 Rust 간에 공유되는 유형을 사용하려는 경우 두 번째 일반 규칙을 정의하십시오(Rust 바인딩을 포함하는 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"],
}

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 파일에서 원하는 대로 CXX 래퍼 유형 을 사용하여 원하는 대로 C++ 기능을 정의합니다. 예를 들어, 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에서 이것을 사용하려면 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;
}