Rust で Windows プログラミング - CreateWindow編

##環境
toolchain: stable-i686-pc-windows-msvc
rustc 1.32.0 (9fda7c223 2019-01-16)
[winapi 0.3.6](https://github.com/retep998/winapi-rs)

##初めてのウィンドウ
[MessageBox が表示](https://qiita.com/benki/items/778ba2bd1266d28bce61)されるだけでは物足りないのでウィンドウを作ってみましょう。

~~Cargo.toml の features にちょっと追加。~~
2019/2/12 追記
`"windef", "minwindef", "wingdi"`などは追加しなくても良いようです。

```toml:Cargo.toml
[dependencies.winapi]
version = "0.3"
features = ["winuser"] #"windef", "minwindef", "wingdi" は書かなくても OK
```

続いて main.rs に使用する module を書いていきます。MessageBox 編と比べるとかなり増えます。

```rust:main.rs
use winapi::{
    um::{
        winuser::{RegisterClassW, WNDCLASSW, CS_HREDRAW, CS_VREDRAW,
                  LoadIconW, IDI_APPLICATION, LoadCursorW, IDC_ARROW,
                  CreateWindowExW, ShowWindow, SW_NORMAL, UpdateWindow,
                  GetMessageW, TranslateMessage, DispatchMessageW, MSG,
                  WM_DESTROY, PostQuitMessage, DefWindowProcW, WS_OVERLAPPEDWINDOW},
        wingdi::{GetStockObject, WHITE_BRUSH},
    },
    shared::{
        windef::{HWND, HBRUSH},
        minwindef::{UINT, WPARAM, LPARAM, LRESULT},
    },
};
use std::ptr;
use std::mem;
```
main 関数です。`WNDCLASS`を登録して、`CreateWindow`して、メッセージループを回すというおなじみ（？）の流れですね。

```rust:main.rs
fn main() {
    unsafe {
        let class_name = encode("my_window_class_name");
        if !register_wndclass(&class_name) {
            return;
        }

        let hwnd = create_window(&class_name);
        if hwnd.is_null() {
            return;
        }
        ShowWindow(hwnd, SW_NORMAL);
        UpdateWindow(hwnd);
        let mut msg = mem::uninitialized::<MSG>();
        loop {
            if GetMessageW(&mut msg, ptr::null_mut(), 0, 0) == 0 {
                return;
            }
            TranslateMessage(&mut msg);
            DispatchMessageW(&mut msg);
        }
    }
}
```

`encode`関数は前回と同じなので省略します。では、`register_wndclass`関数を見てみましょう。`WNDCLASSW`構造体の初期化には`std::mem::zeroed()`を使っています。構造体のメンバをすべて書き連ねるのは面倒なので、`zeroed`で初期化して必要なメンバだけに値を設定すれば良いです。（ここでは WinAPI の詳細については述べないので、[MSDN](https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-registerclassw) で調べてみてください。）

```rust:main.rs
unsafe fn register_wndclass(class_name: &[u16]) -> bool {
    let mut winc = mem::zeroed::<WNDCLASSW>();
    winc.style = CS_HREDRAW | CS_VREDRAW;
    winc.lpfnWndProc = Some(win_proc);
    winc.hIcon = LoadIconW(ptr::null_mut(), IDI_APPLICATION);
    winc.hCursor = LoadCursorW(ptr::null_mut(), IDC_ARROW);
    winc.hbrBackground = GetStockObject(WHITE_BRUSH as i32) as HBRUSH;
    winc.lpszClassName = class_name.as_ptr();

    RegisterClassW(&winc) > 0
}
```

続いて、`create_window`関数です。

```rust:main.rs
unsafe fn create_window(class_name: &[u16]) -> HWND {
    CreateWindowExW(
        0, //dwExStyle: DWORD
        class_name.as_ptr(), //lpClassName: LPCWSTR
        encode("Hello, World!").as_ptr(), //lpWindowName: LPCWSTR
        WS_OVERLAPPEDWINDOW, //dwStyle: DWORD
        0, 0, 200, 200, //X: int, Y: int, nWidth: int, nHeight: int
        ptr::null_mut(), //hWndParent: HWND
        ptr::null_mut(), //hMenu: HMENU
        ptr::null_mut(), //hInstance: HINSTANCE
        ptr::null_mut(), //lpParam: LPVOID
    )
}
```

最後にウィンドウプロシージャの`win_proc`関数です。引数の`UINT`型、`WPARAM`型や、戻り値の`LRESULT`型などは、それぞれ Rust の`u32`、`usize`、`isize`とも書けますが、WinAPI の慣習（？）に従って、型を明示した方が分かりやすいと思います。今回のサンプルではそんなに有難味が分からないですが、`msg`の条件分岐が多くなってくると Rsut の`match`式パワーが発揮されます。

```rust:main.rs
unsafe extern "system" fn win_proc(hwnd: HWND, msg: UINT, w_param: WPARAM, l_param: LPARAM) -> LRESULT {
    match msg {
        WM_DESTROY => PostQuitMessage(0),
        _ => return DefWindowProcW(hwnd, msg, w_param, l_param),
    };
    0
}
```

main.rs をまとめてどうぞ。

```rust:main.rs
use winapi::{
    um::{
        winuser::{RegisterClassW, WNDCLASSW, CS_HREDRAW, CS_VREDRAW,
                  LoadIconW, IDI_APPLICATION, LoadCursorW, IDC_ARROW,
                  CreateWindowExW, ShowWindow, SW_NORMAL, UpdateWindow,
                  GetMessageW, TranslateMessage, DispatchMessageW, MSG,
                  WM_DESTROY, PostQuitMessage, DefWindowProcW, WS_OVERLAPPEDWINDOW},
        wingdi::{GetStockObject, WHITE_BRUSH},
    },
    shared::{
        windef::{HWND, HBRUSH},
        minwindef::{UINT, WPARAM, LPARAM, LRESULT},
    },
};
use std::ptr;
use std::mem;

fn main() {
    unsafe {
        let class_name = encode("my_window_class_name");
        if !register_wndclass(&class_name) {
            return;
        }

        let hwnd = create_window(&class_name);
        if hwnd.is_null() {
            return;
        }
        ShowWindow(hwnd, SW_NORMAL);
        UpdateWindow(hwnd);
        let mut msg = mem::uninitialized::<MSG>();
        loop {
            if GetMessageW(&mut msg, ptr::null_mut(), 0, 0) == 0 {
                return;
            }
            TranslateMessage(&mut msg);
            DispatchMessageW(&mut msg);
        }
    }
}

fn encode(source: &str) -> Vec<u16> {
    source.encode_utf16().chain(Some(0)).collect()
}

unsafe fn register_wndclass(class_name: &[u16]) -> bool {
    let mut winc = mem::zeroed::<WNDCLASSW>();
    winc.style = CS_HREDRAW | CS_VREDRAW;
    winc.lpfnWndProc = Some(win_proc);
    winc.hIcon = LoadIconW(ptr::null_mut(), IDI_APPLICATION);
    winc.hCursor = LoadCursorW(ptr::null_mut(), IDC_ARROW);
    winc.hbrBackground = GetStockObject(WHITE_BRUSH as i32) as HBRUSH;
    winc.lpszClassName = class_name.as_ptr();

    RegisterClassW(&winc) > 0
}

unsafe fn create_window(class_name: &[u16]) -> HWND {
    CreateWindowExW(
        0,
        class_name.as_ptr(),
        encode("Hello, World!").as_ptr(),
        WS_OVERLAPPEDWINDOW,
        0, 0, 200, 200,
        ptr::null_mut(),
        ptr::null_mut(),
        ptr::null_mut(),
        ptr::null_mut(),
    )
}

unsafe extern "system" fn win_proc(hwnd: HWND, msg: UINT, w_param: WPARAM, l_param: LPARAM) -> LRESULT {
    match msg {
        WM_DESTROY => PostQuitMessage(0),
        _ => return DefWindowProcW(hwnd, msg, w_param, l_param),
    };
    0
}
```

次回はウィンドウを最小化するとタスクトレイに格納されるようにします。
[Rust で Windows プログラミング - Shell_NotifyIcon編](https://qiita.com/benki/items/bbc575e471e5423db55a)