dart ffi(foreign function interface)를 통해 rust library를 사용하는 방법을 공유합니다. 예제 app으로는 rust의 audio library의 하나인 rodio 사용해서 음악을 재생합니다.
본 글의 내용은 https://medium.com/flutter-community/how-to-call-a-rust-function-from-dart-using-ffi-f48f3ea3af2c 에서 영감을 얻어 작성하였고, 2022년 9월 기준으로 빌드 가능한 환경으로 구성했으며, rust library에 대한 내용을 추가했습니다.
준비물
- dart SDK v2.18.0
- rust v1.63.0
- rodio에서 재생가능한 음악 파일 (wav, mp3, ...)
- windows or macos (여기에서는 macOS를 사용했습니다.)
진행단계
- rust library 작성
- rust app 작성
- rust library의 ffi interface 적용
- rust library를 사용하는 dart app 작성
1. rust library 작성
dart app을 먼서 생성합니다. app 구현은 4단계에서 진행합니다.
# ~/examples/dart_audio_app_with_rust를 예제코드의 root(EX_ROOT)로 지정합니다.
cd ~/examples
dart create -t console dart_audio_app_with_rust
cd dart_audio_app_with_rust
export EX_ROOT=~/examples/dart_audio_app_with_rust
cargo rust library 생성합니다.
cargo new --lib audio_lib
cd audio_lib
rodio를 추가합니다.
cargo add rodio
library code(src/lib.rs)를 test code와 함께 작성합니다.
use rodio::{Decoder, OutputStream, Source};
use std::ffi::CStr;
use std::os::raw::c_char;
use std::{fs::File, io::BufReader};
pub fn play(path: &str) {
// Get a output stream handle to the default physical sound device
let (_stream, stream_handle) = OutputStream::try_default().unwrap();
// Load a sound from a file, using a path relative to Cargo.toml
let file = BufReader::new(File::open(path).unwrap());
// Decode that sound file into a source
let source = Decoder::new(file).unwrap();
// Play the sound directly on the device
stream_handle
.play_raw(source.convert_samples())
.expect("can't play audio file");
// The sound plays in a separate audio thread,
// so we need to keep the main thread alive while it's playing.
std::thread::sleep(std::time::Duration::from_secs(1));
println!("play sound in rust");
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_play_audio() {
play("../data/beep-01a.wav");
}
}
test를 실행하면 음악이 재생됩니다. (음악 파일 경로는 준비된 파일로 수정하세요.)
cargo test
2. rust app 작성
이번단계에서는 rust app에서 작성된 library 코드를 실행해 봅니다.
아래와 같이 app을 생성합니다.
cd $EX_ROOT
cargo new audio_app
cd audio_app
app의 Cargo.toml에 audio_lib 의존성을 추가합니다.
[dependencies]
audio_lib = { path = "../audio_lib" }
application code(src/main.rs)를 작성합니다.
use audio_lib::play;
fn main() {
play("data/beep-01a.wav");
}
아래와 같이 실행합니다. 1단계에서 재생한 음악이 그대로 나오면 app도 잘 작동하는 것입니다.
cargo run
3. rust library의 ffi interface 적용
이 단계에서는 rust library를 외부에서 쓸 수 있도록 dynamic library로 빌드하고 api도 c interface로 wrapping 합니다.
c type의 dynamic library로 빌드되도록 audio_lib/Cargo.toml 수정합니다.
[lib]
crate-type = ["cdylib", "lib"]
library 코드의 c용 interface를 추가합니다.
#[no_mangle]
pub extern "C" fn play_for_ffi(ptr: *const c_char) {
let cstr_path = unsafe { CStr::from_ptr(ptr) };
play(cstr_path.to_str().unwrap());
}
이제 rust library를 C기반 application에서 사용할 수 있는단계가 되었습니다.
4. rust library를 사용하는 dart app 작성
dart application code를 아래와 같이 작성합니다.
import 'dart:ffi' as ffi;
import 'dart:ffi';
import 'package:ffi/ffi.dart';
// rust/native function
typedef NativePlayFunction = ffi.Void Function(ffi.Pointer<Utf8>);
// dart function
typedef PlayFunction = void Function(ffi.Pointer<Utf8>);
void main(List<String> arguments) {
ffi.DynamicLibrary dl =
ffi.DynamicLibrary.open('target/debug/libaudio_lib.dylib');
final PlayFunction play = dl
.lookup<ffi.NativeFunction<NativePlayFunction>>("play_for_ffi")
.asFunction();
final ffi.Pointer<Utf8> path = "data/beep-01a.wav".toNativeUtf8().cast();
play(path);
}
dart app에서 필요한 의존성 패키지를 설치합니다.
dart pub add ffi
이제 아래를 실행하면 dart 에서 rust library를 호출하여 audio가 재생되는 것을 들을 수 있습니다.
dart run
위에서 소개한 내용은 https://github.com/yeoupooh/dart_audio_app_with_rust 에서 전체 소스가 공개되어 있습니다.
다음엔 flutter 에서 rust library를 사용하는 예제를 만들어 보겠습니다.