Contents
프로세스 간 통신(IPC; Inter-Process Communication)
타우리에서는 프로세스 간 통신을 위해 서로 격리된 프로세스가 안전하게 보호된 통신을 할 수 있도록 Asynchronous Message Passing 이라는 독특한 스타일의 프로세스 간 통신을 사용합니다. 여기서 프로세스는 간단한 데이터 표현을 사용하여 직렬화된 요청 및 응답을 교환합니다. 메시지 전달은 수신자가 필요에 따라 요청을 거부하거나 폐기할 수 있기 때문에 공유 메모리 또는 직접 기능 액세스보다 안전한 기술입니다. 예를 들어 타우리 Core 프로세스가 요청을 악의적 메시지라고 판단하면 요청을 폐기 해버리고 해당 기능을 실행하지 않습니다. 이번 글에서 다룰 내용은 타우리가 사용하는 2가지의 프로세스간 통신 방법인 이벤트(event)와 명령(command)입니다. 이와는 별개로 IPC 호출을 가로채는 모킹(mocking)이라는 방법도 제공하고 있습니다. 모킹에 대해서는 향후에 따로 정리해 올리겠습니다.
이벤트(Event)
이벤트는 단방향으로만 메시지를 전송할 수 있으며, 하나의 프로세스에서 하나 이상의 프로세스로 이벤트를 보낼 수 있습니다. 전역 메시지의 경우 모든 프로세스에게 이벤트를 보낼 수 있으며, 지정한 프로세스에게만 이벤트를 보낼 수도 있습니다. 또한 이벤트는 아래에 소개할 명령(Commands)과는 달리 프론트엔드(Webview)와 코어 프로세스에서 모두 보낼 수가 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
use tauri::{Window, AppHandle, Manager};
use std::thread;
#[tauri::command]
fn call_rust(_window: Window, _app: AppHandle) {
thread::spawn(move || {
thread::sleep(std::time::Duration::from_secs(1));
_window.emit("emit_to_js", "Emit an event to the frontend.").unwrap();
});
}
fn main() {
tauri::Builder::default()
.setup(|_app| {
let main_window = _app.get_window("main").unwrap();
let cc_main_window = main_window.clone();
let _ = cc_main_window.listen("emit_to_rust", move |e| {
println!(">> {}", e.payload().unwrap());
});
...
Ok(())
})
.invoke_handler(tauri::generate_handler![call_rust])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const { invoke } = window.__TAURI__.tauri;
const { listen } = window.__TAURI__.event;
const { appWindow } = window.__TAURI__.window;
...
async function to_rust() {
appWindow.emit('emit_to_rust', 'Emit an event to Rust.');
};
async function from_rust() {
await listen('emit_to_js', e => {
console.log("<< " + e.payload);
});
} from_rust();
window.addEventListener("DOMContentLoaded", () => {
...
document
.querySelector("#call-rust-button")
.addEventListener("click", () => to_rust());
});
위의 예제는 javascript와 러스트에서 서로 이벤트를 발생시키는 예제입니다. 이벤트를 발생시키기 위해 각각 emit()을 이용하였습니다.
요약
명령(Commands)
명령은 프론트엔드(Webview)에서 Rust 함수 또는 메소드에 인수와 함께 직접 호출하고 결과 값을 반환 받을 수 있는 외부 함수 인터페이스입니다. async 키워드를 사용하여 비동기식으로 실행 할 수도 있습니다. 비동기식으로 실행할 경우에는 async_runtime::spawn을 이용하여 스레드에서 명령을 실행합니다. Rust 함수(또는 메소드)를 명령으로 바꾸려면, 함수(또는 메소드)위에 #[tauri::command]
한 줄을 추가하면 됩니다. 아래의 예를 참고하여 주세요.
1
2
3
4
#[tauri::command]
fn my_command(msg: String) {
println!("I got message '{}'", msg);
}
명령은 위의 예제에서 처럼 매개변수를 가질수도 있으며(없을 수도 있음), 매개변수로는 일반 매개변수와 특수 매개변수를 가질 수가 있습니다. 일반 매개변수는 사용자가 입력한 매개변수를 이야기 하며, 특수 매개변수는 tauri::command 매크로에 의해 자동으로 주입되는
- tauri::window : 명령을 호출한 윈도우에 대한 핸들
- tauri::AppHandle : 전연 인스턴스에 대한 핸들
- tauri::State
: 전역적으로 관리되는 상태 값 T를 입력(사용을 위해서 이전에 manage(T)를 호출해야 함)
위의 3 종류가 있습니다.
아래는 특수 매개변수에 대한 간단한 예시입니다.
1
2
3
4
#[tauri::command]
fn my_command(window: tauri::Window, _app: tauri::AppHandle, msg: String) {
println!("I was invoked from window({}) and got message '{}'.", window.label(), msg);
}
위의 두 예제에 대한 호출은 아래와 같이 호출 해주시면 됩니다.
1
2
3
const { invoke } = window.__TAURI__.tauri;
...
await invoke("my_command", {msg: "Hello, Tauri!"});
두 함수의 정의는 각각 다른데, 위의 예제와 같이 하나로 호출이 가능한 것에 대해 의아해 하실 수 있습니다. 그 이유는 바로 tauri::command 매크로에 있습니다. 매크로는 프론트엔드(javascript)에서는 특수 매개변수는 보이지 않도록 재정의하기 때문입니다.
이외에도 비동기식으로도 IPC 통신이 가능한데 이는 create-tauri-app을 이용하여 프로젝트를 생성하면 기본으로 생성되는 내용이기 때문에 설명을 생략하도록 하겠습니다.
요약
참고 : 위의 내용은 타우리 공식 가이드문서의 『프론트엔드로 부터 러스트 호출하기』와 『이벤트』를 참고하여 만들었습니다.