Windowsのウィンドウプロシージャ内におけるRustのパニック
RustのパニックがFFI境界を跨いで巻き戻そうとすると未定義動作となります[1]。Windowsでウィンドウを作る場合のウィンドウプロシージャもこの例に漏れずウィンドウプロシージャとメッセージループの間がFFI境界となるので、対策なしにウィンドウプロシージャでパニックが起きると未定義動作となります。
ここでstd::panic::catch_unwind
[2](以下catch_unwind
)とstd::panic::resume_unwind
[3](以下resume_unwind
)を用いることで未定義動作を起こさないようにします。
ウィンドウプロシージャとcatch_unwind
ウィンドウプロシージャのメッセージ処理をcatch_unwind
のクロージャ内で行うことで、その中で起きたパニックがcatch_unwind
の返り値に格納されてパニックがFFI境界を超えることを防ぎます。
use std::panic::catch_unwind;
use std::cell::RefCell;
use std::any::Any;
thread_local! {
static UNWIND: RefCell<Option<Box<dyn Any + Send>>> = RefCell::new(None);
}
unsafe extern "system" fn window_proc(
hwnd: HWND,
msg: UINT,
wparam: WPARAM,
lparam: LPARAM,
) -> LRESULT {
catch_unwind(|| {
// WM_DESTROY等のメッセージの処理はcatch_unwindの中で
})
.unwrap_or_else(|e| {
// catch_unwindの中で起きたパニックをUNWINDに入れる
UNWIND.with(|unwind| *unwind.borrow_mut() = Some(e));
// とりあえずメッセージの処理したことにしてウィンドウプロシージャから抜ける
0
})
}
メッセージループとresume_unwind
ウィンドウプロシージャで起きたパニックをメッセージループの中でresume_unwind
を用いて再開させます。
use std::panic::resume_unwind;
use std::ptr::null_mut;
use winapi::um::winuser::*;
unsafe {
let mut msg = MSG::default();
loop {
let ret = GetMessageW(&mut msg, null_mut(), 0, 0);
if ret == 0 || ret == -1 {
break;
}
DispatchMessageW(&msg);
// ウィンドウプロシージャで起きたパニックはここで再開される
UNWIND.with(|unwind| {
if let Some(e) = unwind.borrow_mut().take() {
resume_unwind(e);
}
});
}
}
WM_CREATE
WM_CREATE
でパニックが起きた場合を考え、CreateWindowEx
の次にresume_unwind
するかどうかをチェックします。WM_CREATE
の時に-1を返すとCreateWindowEx
はNULLを返すことが決まっているため、NULLチェックの前にパニックのチェックをする必要があります。
use winapi::um::winuser::*;
unsafe {
let hwnd = CreateWindowExW(
// 省略
);
UNWIND.with(|unwind| {
if let Some(e) = unwind.borrow_mut().take() {
resume_unwind(e);
}
});
if hwnd == null_mut() {
panic!("CreateWindowEx failed");
}
}
サンプル
マウスの右ボタンを押して離した瞬間にパニックを起こします。
また、cargo run --features=panic_create
とするとWM_CREATE
でパニックを起こすようになります。
-
FFI and panics - The Rustonomicon
https://doc.rust-lang.org/nomicon/ffi.html#ffi-and-panics ↩︎ -
std::panic::catch_unwind
https://doc.rust-lang.org/std/panic/fn.catch_unwind.html ↩︎ -
std::panic::resume_unwind
https://doc.rust-lang.org/std/panic/fn.resume_unwind.html ↩︎
Discussion