W.I.S. Laboratory
menu-bar

Rust winapi


RustからWindowsAPIを叩いてデバイスコンテキストを使ってみる

RustからWindowsAPIを叩いてGUI(というかデバイスコンテキストを使った画面描画)をやってみた。
使ったクレートはwinapiの0.3.9と、乱数生成のためにrandの0.8.4。
CでAPIを叩くときと同じく、CreateWindow関数でウィンドウを生成し、GetMessageまたはPeekMessage関数でメッセージを取得、DispatchMessage関数でウィンドウプロシージャへメッセージを送出、ウィンドウプロシージャでメッセージを受け取って処理を振り分けるという記述になる。

1. プロジェクトを作成したいフォルダへ移動して cargo new する

例)D:\Rust\ にプロジェクトを作成する場合
1. コマンドプロンプトを起動
2. 「D:」(エンター)
3. 「cd \Rust」(エンター)
4. 「cargo new winapi」(エンター)
5. 「cd winapi」(エンター)
6. 「cargo run」(エンター)
7. 「Hello World」が出てきたらOK
8. コマンドプロンプトは閉じずにそのままにしておく

2. プロジェクトフォルダ内の Cargo.toml の [dependencies] 以下を編集する。


※バージョン情報はコマンドプロンプトで「cargo search winapi」「cargo search rand」とすると各々確認できる。

3. srcフォルダ内の「main.rs」を編集する。


4. cargo run する

1. コマンドプロンプトで「cargo run」(エンター)

「これは本当にRustか」と疑うほどに、CでWindowsAPIを叩いているかのような見た目だ。
関数名も型名も windows.h ほぼそのままの、Cでよく見てきたマイクロソフト記述なので、ぱっと見は本当にCでAPIを叩いている感じがする。
ところどころ、型名がむき出しになっている箇所(*mut winapi::shared::windef::POINT など)があるのはコンパイラがそう書きなさいと言ってきたのでそのまま書いたものだ。

Cと比べてRustならではの点は以下のような感じ。

  • ウィンドウクラスやメッセージ、RECT構造体などの宣言をするときに必ず全フィールドの初期値を書かなくてはならない
  • リテラルの 0 を「as HWND」などとして関数が求める型にキャストしなくてはならない
  • API関数の戻り値も「as HANDLE」などとしてキャストしなくてはならない。
  • VC++やMinGWなら「TEXT("...")」とすればコンパイラが必要に応じて適切にUTF-16変換などをしてくれるが、Rustは自前で変換関数を用意する必要がある
  • UTF-16に変換した文字列(Vec<u16>)を生ポインタに変換して変数に保持させると、Rustのライフタイム云々で消滅してしまうので、生ポインタが必要になる都度as_ptr()する必要がある

結構ハマったのが、GetClientRect()関数でRECT構造体に現在のウィンドウサイズを取得する箇所だ。
Cだとここは &rect として構造体のポインタを渡すところなので、「let mut rect = RECT {...} 」とした上で「&rect」をGetClientRectに渡したのだが、「types differ in mutability」エラーが出てしまう。
変数をミュータブルにしているのに「ミュータブルにしろ」とコンパイラが言ってくるのだ。
結局参照ミュータブルで「&mut rect」としてGetClientRectに渡すとうまくいった。
ミュータブル借用で渡す場合は変数をmutした上で渡す時にも&mutしてあげないといけないというRustの基本で躓いていたわけだが、Cに慣れきった頭ではここにたどり着くのに随分時間を要した。
Rustはこのミュータブル回りが結構難しいというかややこしい。(見慣れたAPI関数なのも要因かも)

また上にも書いたのだが、APIに渡す文字列への生ポインタやウィンドウクラス構造体に格納する生ポインタ(*const u16)を予め変数に代入しておくことができない。
Cだとリテラルの文字列は静的領域に置かれるのでプログラム終了まで生き続けるのだが、Rustは「ライフタイム」があるため消滅してしまうのだ。
Rustの文字列はCで言うところの unsigned char[] だと思ったほうが良いかもしれない。
関数に渡したり構造体のフィールド値として記述するその都度、as_ptr()メソッドで毎回生ポインタ化するとうまくいく。

あとRustはとにかく型に厳しい。
Cに慣れた頭でなんとなく「0」と書こうものなら、即座に「これは何型の0だ?求めている0と違うぞ」となる。
ただ、これだけ厳しいと「うっかりバグ」というものは本当に駆逐できるだろうなと思う。
Rustを書いているとCが緩々な言語に思えてくる。(実際緩々だけども)

マイクロソフトがRustからWindowsAPIを叩くためのクレートを公開するらしいので、これならもっと簡単に(Rustっぽく)APIを叩くことができそう。


[ 戻る ]
saluteweb