Windows にて unsafe な呼び出しをくるんで Rust の世界で扱う

  • 4
    いいね
  • 0
    コメント

はじめに

Rust Advent Calendar 12/8 の記事です。

要求された空間や時間内での動作、 デバイスドライバやオペレーティングシステムのような低レベルなコードなど他の言語が苦手とする多数のユースケースを得意とします。

プログラミング言語Rust より

このうたい文句にやられ、9月くらいから Windows API を Rust で呼び出して、パフォーマンス情報を取得するようなコードを書いていました。
ROki1988/perf_client

Windows API の呼び出しについては、すでにあった retep998/winapi-rs を使っています。
で、winapi-rs は unsafe な関数たちです。

このコードたちをどう Rust の世界へ引き込んだらいいんだろう、と考えたりしたので、
そちらについて触れていきます。

Wrapper を書く

結論として、Wrapper 関数を書くことで対応しました。

fn pdh_open_query() -> Result<winapi::PDH_HQUERY, winapi::PDH_STATUS> {
    use std::ptr;
    let mut hquery = winapi::INVALID_HANDLE_VALUE;
    let ret = unsafe { pdh::PdhOpenQueryW(ptr::null(), 0, &mut hquery) };

    if winapi::winerror::SUCCEEDED(ret) {
        Ok(hquery)
    } else {
        Err(ret)
    }
}

上記関数はパフォーマンスカウンターの値を取得する PDH* 関数の1つになります。
pdh::PdhOpenQueryWwinapi-rs にて定義されている関数です。

書いたり消したりしながら、以下の方針が形になってきました。

  • Wrapper 関数内で unsafe を完結させる
    • かつ unsafe の範囲をなるべく小さくする
  • 結果は Result の文脈で包む
    • かつ Wrapper 関数のエラーの型は ERROR_CODE でそろえる

たぶん当たり前のこと書いてますw けどそろえることが大事、としました。
これで Wrapper 関数の呼び出し元では Rust の世界でコードを書いていけます。

impl<'a> Iterator for PdhControllerIterator<'a> {
    type Item = PdhCollectValue;

    fn next(&mut self) -> Option<Self::Item> {
        if self.index == 0 {
            pdh_collect_query_data(self.pdhc.hquery);
        }

        let item = self.pdhc
            .items
            .get(self.index)
            .iter()
            .flat_map(|c| {
                pdh_get_formatted_counter_value(c.hcounter, PDH_FMT_DOUBLE)
                    .map(|v| PdhCollectValue::new(&c.element, v))
            })
            .last();
        self.index += 1;
        item
    }
}

上記コードでは pdh_collect_query_datapdh_get_formatted_counter_value が Wrapper 関数にあたります。

unsafe は一切見当たらず、Rust の世界で関数をつなげながら、ガシガシ書いていけました。

具体的には Drop トレイトを実装してハンドルの開放をしたり、Iterator トレイトを実装して map で値を取り出せるようにしたりしました。
いやぁ楽しい!

おわりに

Wrapper 関数を書くのは少し手間ですが、Rust の世界の呼び出し元では心地よくコードがかけました。
うたい文句にやられてよかったです。

この投稿は Rust Advent Calendar 20168日目の記事です。