Skip to content

Instantly share code, notes, and snippets.

@taikoubou
Created May 26, 2023 09:03
Show Gist options
  • Save taikoubou/14434738bf12a5aa37a330cacf387dc9 to your computer and use it in GitHub Desktop.
Save taikoubou/14434738bf12a5aa37a330cacf387dc9 to your computer and use it in GitHub Desktop.
windows-rsのウィンドウ生成プログラムの解説
use windows::{
core::*, Win32::Foundation::*, Win32::Graphics::Gdi::ValidateRect,
Win32::System::LibraryLoader::GetModuleHandleA, Win32::UI::WindowsAndMessaging::*,
};
fn main() -> Result<()> {
unsafe {
// GetModuleHandleAは指定したモジュールハンドルを取得する。
// 今回はNoneを指定しているため呼び出し元のプロセスの作成に使用されるファイルへのハンドル(ポインタ)を返す。
// 以下2行の--がついてる部分は別のクレートで定義されたものだったので違う!
// --戻り値の型は *mut c_void であり、これはC言語のvoid*と同等である。
// --とはいえRustなので、Rustの()型が返却される。
// ()型について:()はunitと呼ばれ、ただ一つのだけの値を持つ。
// 結果を返さない関数の戻り値の型として登場する。
// うまく用いることで構造体のコンストラクタのポインタの取得(https://algon-320.github.io/posts/rust-unit-like/)などができる。
// Win32::System::LibraryLoader::GetModuleHandleAは基本的には同じだが、戻り値がResult<HMODULE>が返ってくることに注意!
// windows::Win32::Foundation::HMODULEはタプル構造体でisizeのラッパーである。
// 関数が失敗した場合は0が返ってくるので要エラーチェック。
let instance = GetModuleHandleA(None)?;
debug_assert!(instance.0 != 0);
// s!マクロはRust UTF-8文字列リテラルからLPCSTR(= const char*)引数を生成するマクロである。
// w!マクロもあり、Rust UTF-8文字列リテラルからLPCWSTR引数を生成するマクロである。
let window_class = s!("window");
// WNDCLASSA構造体
// RegisterClass関数によって登録されるウィンドウクラス属性を格納する。
// 各メンバの詳細は(https://learn.microsoft.com/ja-jp/windows/win32/api/winuser/ns-winuser-wndclassa)参照
// 以下で使われているものは解説する。
let wc = WNDCLASSA {
// hCursor: クラスカーソルへのハンドルを表す。
// 今回はLoadCursorWを用いており、これは第一引数でインスタンスに関連付けされている実行ファイルを指定し?(あまりよくわからない、上記のinstanceではダメだったので)
// 第二引数でカーソルの形の指定をしている。例えばIDC_HANDを指定すると指をさした形のカーソルになる。
hCursor: LoadCursorW(None, IDC_ARROW)?,
// hInstance: クラスのウィンドウプロシージャを含むインスタンスへのハンドルを表す。
// つまりGetModuleHandleAで取得したモジュールハンドルを指定すればよい。
hInstance: instance,
// lpszClassName: ウィンドウクラス名を表す。
lpszClassName: window_class,
// style: クラスのスタイルを表す。
// u32であり、ビット演算でクラススタイルを導入できる。
// 以下の場合だと、ウィンドウのクライアント領域の幅が変更された場合にウィンドウを再描画し(CS_HREDRAW)、
// ウィンドウのクライアント領域の高さが変更された場合にウィンドウを再描画する(CS_VREDRAW)というスタイルになっている。
// 他にも閉じるボタンの無効化や、ウィンドウ内でダブルクリックされた場合にプロシージャにダブルクリックメッセージを送るスタイルなどがある。
// ここでクライアント領域とはタイトルバーやメニューバーなど外側の枠を除いたウィンドウの内側の領域のことを指す。
style: CS_HREDRAW | CS_VREDRAW,
// lpfnWndProc: ウィンドウのプロシージャへのポインタを表す。
lpfnWndProc: Some(wndproc),
//その他のメンバはデフォルト値をセットする。
..Default::default()
};
// RegisterClassA: ウィンドウクラスの登録
// 登録に成功するとウィンドウクラスを一意に識別するアトムという整数が返ってくる。
// 失敗した場合は0が返ってくるので要エラーチェック。
// アプリケーションが登録するウィンドウクラスは終了と同時に登録が解除されるが、DLLがアンロードされている場合は自動的には登録が解除されないため明示的な解除が必要。
let atom = RegisterClassA(&wc);
debug_assert!(atom != 0);
// createWindowExA: 拡張されたウィンドウスタイルを使いウィンドウを作成する。
// 戻り値はHWNDであり、新しいウィンドウへのハンドルが返される。
// 詳しい定義は(https://learn.microsoft.com/ja-jp/windows/win32/api/winuser/nf-winuser-createwindowexw)と
// (https://microsoft.github.io/windows-docs-rs/doc/windows/Win32/UI/WindowsAndMessaging/fn.CreateWindowExA.html)
// を参照せよ。
CreateWindowExA(
// dwexstyle: 作成する拡張ウィンドウスタイル。
WINDOW_EX_STYLE::default(),
// lpclassname: ウィンドウのクラス名
window_class,
//lpwindowname: ウィンドウの名前
s!("This is a sample window"),
// dwsytle: ウィンドウのスタイル
// 以下の設定はウィンドウは重なり合っており(WS_OVERLAPPEDWINDOW)、ウィンドウは最初に表示される(WS_VISIBLE)。
// 詳しいパラメータは(https://learn.microsoft.com/ja-jp/windows/win32/winmsg/window-styles)を参照
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
// x: ウィンドウの左上のx座標
// CW_USEDEFAULTは既定の位置にセットする。
// CW_USEDEFAULTは重複するウィンドウに対してのみ有効であり、またポップアップウィンドウや子ウィンドウに指定されている場合は0がセットされる。
CW_USEDEFAULT,
// y: ウィンドウの左上のy座標
CW_USEDEFAULT,
// nwidth: ウィンドウの幅のサイズ
CW_USEDEFAULT,
// nheight: ウィンドウの高さのサイズ
CW_USEDEFAULT,
// nwndparent: 親ウィンドウのへのハンドル
None,
// nmenu: メニューへのハンドル
None,
// hinstance: ウィンドウに関連付けるモジュールのインスタンスへのハンドル。
instance,
// lpparam: Option<*const c_void>
// WM_CREATEメッセージのlparamパラメータによって指されるCREATESTRUCT構造体を介してウィンドウに渡される値へのポインタ
None,
);
// メッセージキューからのメッセージを保持する変数
let mut message = MSG::default();
// GetMessageA: 呼び出し元のスレッドのメッセージキューからメッセージを取得する関数。
// 第一引数はlpmsg: *mut MSGであり、スレッドのメッセージキューからメッセージを受信するMSG構造体へのポインタ。
// 第二引数はhwnd: P0 ;(P0: IntoParam<HWND>)であり、メッセージを取得するウィンドウへのハンドル。Noneの場合はウィンドウメッセージとスレッドメッセージの両方が処理される。
// 第三引数はwmsgfiltermin: u32であり、取得するメッセージの最小値の整数。
// 第四引数はwmsgfiltermax: u32であり、取得するメッセージの最大値の整数。
// wmsgfilterminとwmsgfiltermaxがともに0の場合はGetMessageは使用可能なすべてのメッセージを返す。
// 戻り値の型はwindows::Win32::Foundation::BOOLでWM_QUITメッセージを取得する場合は0で返却され、エラーの場合は-1、それ以外は0,-1以外の値を返す。
// 詳細は(https://learn.microsoft.com/ja-jp/windows/win32/api/winuser/nf-winuser-getmessagea)を参照せよ。
while GetMessageA(&mut message, None, 0, 0).into() {
// 登録したウィンドウプロシージャにメッセージを発行する処理
DispatchMessageA(&message);
}
Ok(())
}
}
// extern "system" はextern "C"と同じだが、Win32の場合はextern "stdcall"となり、Windows API事態へのリンクになる。
// 参考サイト:https://runebook.dev/ja/docs/rust/reference/items/external-blocks
// extern "win64"などと書けばx86_64 WindowsのCのコードを指す(らしい)
// wndproc関数はアプリケーションで定義したウィンドウに送信されたメッセージを受信し処理するコールバック関数である。
// つまりウィンドウプロシージャ
// 以下引数の説明
// window: HWND ウィンドウへのハンドルを表す。
// message: u32 メッセージ、システム定義メッセージを受信する。詳しくは(https://learn.microsoft.com/ja-jp/windows/win32/winmsg/about-messages-and-message-queues#system-defined-messages)を参照。
// wparam: WPARAM 追加のメッセージ情報
// lparam: LPARAM 追加のメッセージ情報
// 戻り値の型はLRESULTで、これはメッセージ処理をした結果を表す。
extern "system" fn wndproc(window: HWND, message: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
unsafe {
match message {
// WM_PAINT: システムまたは他のアプリケーションがアプリケーションのウィンドウの一部を描画するメッセージ
WM_PAINT => {
println!("WM_PAINT");
// ValidateReact(hWnd: HWND, lpRect: *const RECT) -> windows::Win32::Foundation::BOOL
// 指定したウィンドウの更新領域から四角形を削除することで四角形内のクライアント領域を返す。
// 第一引数は更新リージョンが変更されるウィンドウへのハンドル。
// 第二引数は更新領域から削除する四角形のクライアント座標を含むRECT構造体へのポインタ
// Noneの場合はクライアント領域全体をが削除される。
ValidateRect(window, None).expect("ウィンドウの削除に失敗しました。");
LRESULT(0)
}
// WM_DESTROY: ウィンドウを破棄する際に送信されるメッセージ
// これはウィンドウが画面から削除された後に破棄されるウィンドウプロシージャに送信される・
WM_DESTROY => {
println!("WM_DESTROY");
// PostQuitMessage: スレッドが終了を要求したことをシステムに示す。
// 引数はアプリケーションの終了コードを表す。
// (システムの?)メッセージキューにWM_QUITメッセージをポストする。
PostQuitMessage(0);
LRESULT(0)
}
// 特にメッセージがないときは、DefWindowProcA関数を呼び出す。
// これはアプリケーションが処理しないウィンドウメッセージの既定の処理を提供する。
// 引数はwndprocと同じ。
_ => DefWindowProcA(window, message, wparam, lparam),
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment