RustでWindowsXP用のWindowsアプリケーションを開発する

前回の記事ではWindowsXP用のコンソールアプリケーションの作成を行いました。

https://qiita.com/bluexe203/items/2b210ba5e641fdf95ed0

今回はWindowsXP用のWindowsアプリケーションの作成を行います。(crateの作成とそれを利用したサンプル)

# crateの作成
今回作成したcrateのサンプル

https://github.com/bluexe203/rust_win_xp_gui

windows-sysの0.42.0を使用します。
winapi-rsやwindows-rsを使用したサンプルは他の方が記事にされていますが、
WindowsXPでwindows-rsのビルドが通らない(rust1.49では通らない)ため、
windows-sysのみを使用しています。(edition = "2018"のためXP用に利用できます)

```toml:Cargo.toml
[package]
name = "rust_win_xp_gui"
version = "0.1.0"
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
[dependencies.windows-sys]
version = "0.42.0"
features = [
    "Win32_UI_WindowsAndMessaging",
    "Win32_Foundation",
    "Win32_Security",
    "Win32_System_Threading",
    "Win32_Graphics_Gdi",
]
```

## struct base_windowの実装

自作のウィンドウを表示するには大雑把に以下の手順が必要となります

- RegisterClass(WindowProcの登録を含む)
- CreateWindow
- ウィンドウの表示(ShowWindow,UpdateWindow)
- メッセージループ

WindowProcはstaticの関数のため、base_windowのリソースにアクセスするのが少々面倒になります。
WindowsSDKのサンプルでよく使われているのが、
1. CreateWindowでstructのthisポインタをパラメータに追加
1. WindowProcのWM_NCCREATEでSetWindowLongPtrでHWNDとthisポインタを紐づけ(SetWindowLongPtrWは32bitと64bitで処理が変わるので注意)
1. WindowProcでGetWindowLongPtrで指定したHWNDからthisポインタが取得できた場合は、struct側のメンバのWindowProcを呼び出す。

といった方法になります。
下記サンプルではメンバのWindowProcでメッセージとコールバック関数の対応にHashMapを使用しています。

<details><summary>src/base_window.rs(全文)</summary><div>


```rust:src/base_window.rs
use std::{collections::HashMap, ffi::c_void, mem, ptr};
use windows_sys::{Win32::{
    Foundation::*,
    Graphics::Gdi::{GetStockObject, WHITE_BRUSH},
    UI::WindowsAndMessaging::*,
}, core::PCWSTR};

pub type MessageCallback<T> = fn(&mut BaseWindow<T>, HWND, u32, WPARAM, LPARAM) -> LRESULT;

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

pub struct BaseWindow<T> {
    hwnd: HWND,
    x: i32,
    y: i32,
    width: i32,
    height: i32,
    title: String,
    class_name: String,
    message_map: HashMap<u32, MessageCallback<T>>,
    win_proc : MessageCallback<T>,
    content: T,
}

impl<T> BaseWindow<T> {
    pub fn new(
        x: i32,
        y: i32,
        width: i32,
        height: i32,
        class_name: &str,
        title: &str,
        content: T,
    ) -> Self {
        Self {
            hwnd: 0,
            x,
            y,
            width,
            height,
            title: title.to_string(),
            class_name: class_name.to_string(),
            message_map: HashMap::new(),
            win_proc : Self::def_win_proc,
            content,
        }
    }

    pub unsafe fn default_wnd_class(&self, proc : WNDPROC, class_name : PCWSTR) -> WNDCLASSW {
        WNDCLASSW {
            style: CS_HREDRAW | CS_VREDRAW,
            lpfnWndProc: proc,
            cbClsExtra: 0,
            cbWndExtra: 0,
            hInstance: 0,
            hIcon: LoadIconW(0, IDI_APPLICATION),
            hCursor: LoadCursorW(0, IDC_ARROW),
            hbrBackground: GetStockObject(WHITE_BRUSH) as isize,
            lpszMenuName: ptr::null_mut(),
            lpszClassName: class_name,
        }
    }

    pub fn create_window(&mut self, wnd_class : Option<WNDCLASSW>) -> bool {
        unsafe {
            let name = to_wstring(&self.class_name);
            let mut winc = self.default_wnd_class(Some(Self::win_proc_static), name.as_ptr());

            if let Some(winc_in) = wnd_class {
                winc = winc_in;
            }

            if RegisterClassW(&winc) > 0 {
                self.hwnd = CreateWindowExW(
                    0,
                    name.as_ptr(),
                    to_wstring(&self.title).as_ptr(),
                    WS_OVERLAPPEDWINDOW,
                    self.x,
                    self.y,
                    self.width,
                    self.height,
                    0,
                    0,
                    0,
                    self as *mut Self as *const c_void,
                );

                if self.hwnd != 0 {
                    return true;
                }
            }
            false
        }
    }

    pub fn get_hwnd(&self) -> HWND {
        self.hwnd
    }
    fn set_hwnd(&mut self, hwnd: HWND) {
        self.hwnd = hwnd;
    }

    pub fn set_win_proc(&mut self, proc: MessageCallback<T>) {
        self.win_proc = proc;
    }

    pub fn get_content_mut(&mut self) -> &mut T {
        &mut self.content
    }
    pub fn add_message_map(&mut self, msg: u32, callback: MessageCallback<T>) {
        self.message_map.insert(msg, callback);
    }
    pub fn clear_message_map(&mut self) {
        self.message_map.clear();
    }

    pub fn def_win_proc(&mut self, hwnd: HWND, msg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
        unsafe {
            match msg {
                WM_DESTROY => {
                    PostQuitMessage(0);
                }
                _ => {
                    if let Some(callback) = self.message_map.get(&msg) {
                        // callbackが0以外の場合DefWindowProcWをスキップする
                        if callback(self, hwnd, msg, wparam, lparam) != 0 {
                            return 0;
                        }
                    }
                }
            }
            DefWindowProcW(hwnd, msg, wparam, lparam)
        }
    }

    unsafe extern "system" fn win_proc_static(
        hwnd: HWND,
        msg: u32,
        wparam: WPARAM,
        lparam: LPARAM,
    ) -> LRESULT {
        if msg == WM_NCCREATE {
            let cs = lparam as *const CREATESTRUCTW;
            let this = (*cs).lpCreateParams as *mut Self;

            (*this).set_hwnd(hwnd);
            #[cfg(target_pointer_width = "64")]
            SetWindowLongPtrW(hwnd, GWLP_USERDATA, this as isize);
            #[cfg(target_pointer_width = "32")]
            SetWindowLongPtrW(hwnd, GWLP_USERDATA, this as i32);
        } else {
            let this = GetWindowLongPtrW(hwnd, GWLP_USERDATA) as *mut Self;

            if !this.is_null() {
                return ((*this).win_proc)(&mut (*this), hwnd, msg, wparam, lparam);
            }
        }

        DefWindowProcW(hwnd, msg, wparam, lparam)
    }
}
```

</div></details>

## メイン関数の実装

上記で作成したcrateを使う側の処理になります。
ウィンドウ作成後にメッセージとコールバックの紐づけを行います。

```rust
fn on_paint(
...
) -> LRESULT {
    ...
    false // falseの場合DefWindowProcWを呼び出す。
}
fn on_lbutton_up(
...
) -> LRESULT {
    ...
    false // falseの場合DefWindowProcWを呼び出す。
}

fn main() {
...
    // メッセージマップの登録(WM_PAINT, WM_LBUTTONUP)
    window.add_message_map(WM_PAINT, on_paint);
    window.add_message_map(WM_LBUTTONUP, on_lbutton_up);
...
}
```


rustでコンソールのウィンドウを出さないようにするには
#![windows_subsystem = "windows"]
をソースの最初に追加します。
またそれにともなって、build用のスクリプトのサブシステムもSUBSYSTEM:WINDOWSに変更します。

```powershell:run.ps1
rustup default 1.49
$env:CARGO_BUILD_RUSTFLAGS = '-C target-feature=+crt-static -C link-arg=/SUBSYSTEM:WINDOWS,5.01'
cargo run --example simple --release --target=i686-pc-windows-msvc
rustup default stable
```

<details><summary>examples/simple.rs(全文)</summary><div>

```rust:examples/simple.rs
#![windows_subsystem = "windows"]
use std::{mem, ptr};

use rust_win_xp_gui::base_window::*;
use windows_sys::{Win32::{UI::WindowsAndMessaging::*, Graphics::Gdi::*, Foundation::*}};

struct TestStruct {
    pub param1: u32,
    pub param2: u32,
    pub param3: u32,
}

fn main() {
    unsafe {
        let mut test = TestStruct {
            param1: 0,
            param2: 0,
            param3: 0,
        };
        let mut window = BaseWindow::new(CW_USEDEFAULT, CW_USEDEFAULT, 640, 480, "base_window_class", "test", test);
        if window.create_window(None) {
            // メッセージマップの登録(WM_PAINT, WM_LBUTTONUP)
            window.add_message_map(WM_PAINT, on_paint);
            window.add_message_map(WM_LBUTTONUP, on_lbutton_up);

            let hwnd = window.get_hwnd();
            ShowWindow(hwnd, SW_NORMAL);
            UpdateWindow(hwnd);

            let mut msg = mem::zeroed::<MSG>();
            loop {
                if GetMessageW(&mut msg, 0, 0, 0) == 0 {
                    return;
                }
                TranslateMessage(&mut msg);
                DispatchMessageW(&mut msg);
            }
        }
    }
}

fn on_paint(
    window: &mut BaseWindow<TestStruct>,
    hwnd: HWND,
    msg: u32,
    wparam: WPARAM,
    lparam: LPARAM,
) -> LRESULT {
    let mut content = window.get_content_mut();
    unsafe {
        let mut ps = mem::zeroed::<PAINTSTRUCT>();
        let hdc = BeginPaint(hwnd , &mut ps);
        let str = format!("ウィンドウをクリックしてください。クリック回数={}", &content.param1);
        let wstr = to_wstring(&str);
        TextOutW(hdc, 100, 100, wstr.as_ptr(), wstr.len() as i32 - 1);
        EndPaint(hwnd , &mut ps);
    }
    0 // 0の場合DefWindowProcWを呼び出す。
}

fn on_lbutton_up(
    window: &mut BaseWindow<TestStruct>,
    hwnd: HWND,
    msg: u32,
    wparam: WPARAM,
    lparam: LPARAM,
) -> LRESULT {
    let mut content = window.get_content_mut();
    content.param1 = content.param1 + 1;
    unsafe {
        let str = format!("Call on_lbutton_up {}", &content.param1);
        MessageBoxW(
            hwnd,
            to_wstring(&str).as_ptr(),
            to_wstring("title").as_ptr(),
            MB_OK,
        );
        InvalidateRect(hwnd, ptr::null_mut(), 0);
    }
    0 // 0の場合DefWindowProcWを呼び出す。
}
```

</div></details>

WindowsXP上でサンプルを実行すると以下のように表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1783262/acb408b9-2e67-6bc6-d550-e852fce4593d.png)