home about

Rust winapi crate로 winapi 책 2장 띄우기

legacy

무조건 외우라고 강조하시던 추억의 WinAPI32 책 2장 코드를 Rust로 옮겼습니다. winapi-rs를 사용합니다.

삽질 포인트

  • 멀티바이트 지원 안 됨, 무조건 유니코드
    • https://github.com/retep998/winapi-rs/issues/845#issuecomment-562760350
    • 원인은 MAKEINTRESOURCE 이게 없음
      #define IS_INTRESOURCE(_r) ((((ULONG_PTR)(_r)) >> 16) == 0)
      #define MAKEINTRESOURCEA(i) ((LPSTR)((ULONG_PTR)((WORD)(i))))
      #define MAKEINTRESOURCEW(i) ((LPWSTR)((ULONG_PTR)((WORD)(i))))
      #ifdef UNICODE
      #define MAKEINTRESOURCE MAKEINTRESOURCEW
      #else
      #define MAKEINTRESOURCE MAKEINTRESOURCEA
      #endif // !UNICODE
      
  • 레퍼런스가 적어서 고생했는데 이거 참고했음
    • https://github.com/retep998/winapi-rs/issues/122
    • https://www.codeproject.com/Tips/1053658/Win-GUI-Programming-In-Rust-Language
    • 단 구버전이니까 kernel32-sys, user32-sys는 대체 할 수 있음, winapi crate README 참조

코드

[package]
name = "simple_box"
version = "0.1.0"
authors = ["hyunjun529 <hyunjun529@gmail.com>"]
edition = "2018"

[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3.8", features = ["winuser", "libloaderapi"] }
#[cfg(windows)] extern crate winapi;

use std::io::Error;
use std::mem::{size_of, zeroed};

#[cfg(windows)]
use winapi::shared::minwindef::{UINT, WPARAM, LPARAM, LRESULT, LPVOID, HINSTANCE};
use winapi::shared::windef::{HWND, HMENU, HBRUSH};
use winapi::um::winnt::{LPCSTR, LPCWSTR};
use winapi::um::winuser::{WNDCLASSEXW, LoadCursorW, LoadIconW, GetMessageW, DispatchMessageW, RegisterClassExW, CreateWindowExW, ShowWindow, MessageBoxA, TranslateMessage, DefWindowProcW, PostQuitMessage}; // functions
use winapi::um::winuser::{IDI_APPLICATION, IDC_ARROW, CS_HREDRAW, CS_VREDRAW, WS_EX_TOPMOST, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, WM_DESTROY, SW_SHOWDEFAULT}; // const variable
use winapi::um::libloaderapi::GetModuleHandleA;

#[cfg(windows)]
fn to_wstring(str : &str) -> Vec<u16> {
    use std::ffi::OsStr;
    use std::iter::once;
    use std::os::windows::ffi::OsStrExt;
    let v : Vec<u16> =
            OsStr::new(str).encode_wide().chain(once(0).into_iter()).collect();
    v
}

#[cfg(windows)]
unsafe extern "system"
fn wnd_proc(hwnd: HWND, msg: UINT, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
    match msg {
        WM_DESTROY => {
            PostQuitMessage(0); 0
        },
        _ => {
            DefWindowProcW(hwnd, msg, wparam, lparam)
        }
    }
}

#[cfg(windows)]
fn print_message(msg: &str) -> Result<i32, Error> {
    let wide: Vec<u16> = to_wstring(msg);

    let ret = unsafe {
        let h_instance = GetModuleHandleA(0 as LPCSTR);

        let wndclass = WNDCLASSEXW {
            cbSize: size_of::<WNDCLASSEXW>() as u32,
            cbClsExtra: 0,
            cbWndExtra: 0,
            hbrBackground: 16 as HBRUSH,
            hCursor: LoadCursorW(0 as HINSTANCE, IDC_ARROW), // IDC_ARROW A가 없다, 무조건 W임
            hIcon: LoadIconW(0 as HINSTANCE, IDI_APPLICATION),
            hIconSm: LoadIconW(0 as HINSTANCE, IDI_APPLICATION),
            hInstance: h_instance,
            lpfnWndProc: Some(wnd_proc), 
            lpszClassName: wide.as_ptr(),
            lpszMenuName: 0 as LPCWSTR,
            style: CS_HREDRAW | CS_VREDRAW,
        };

        let window = CreateWindowExW(
            WS_EX_TOPMOST,
            wide.as_ptr(),
            wide.as_ptr(),
            WS_OVERLAPPEDWINDOW,
            CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
            0 as HWND, 0 as HMENU,
            h_instance,
            0 as *mut winapi::ctypes::c_void
        );
        
        match RegisterClassExW(&wndclass) {
            0 => {
                MessageBoxA(
                    0 as HWND,
                    b"Call to RegisterClassEx failed!\0".as_ptr() as *const i8,
                    b"Win32 Guided Tour\0".as_ptr() as *const i8,
                    0 as UINT
                );
            },
            _atom => {
                let window = CreateWindowExW(
                    WS_EX_TOPMOST,
                    wide.as_ptr(),
                    wide.as_ptr(),
                    WS_OVERLAPPEDWINDOW,
                    CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
                    0 as HWND, 0 as HMENU,
                    h_instance,
                    0 as LPVOID
                );
                if window.is_null() {
                    MessageBoxA(
                        0 as HWND,
                        b"Call to CreateWindow failed!\0".as_ptr() as *const i8,
                        b"Win32 Guided Tour\0".as_ptr() as *const i8,
                        0 as UINT
                    );
                } else {
                    ShowWindow(window, SW_SHOWDEFAULT);
                    let mut msg = zeroed();
                    while GetMessageW(&mut msg, 0 as HWND, 0, 0) != 0 {
                        TranslateMessage(&msg);
                        DispatchMessageW(&msg);
                    }
                }
            }
        }
    };

    // if ret == 0 { Err(Error::last_os_error()) }
    // else { Ok(ret) }

    return Ok(0);
}

#[cfg(not(windows))]
fn print_message(msg: &str) -> Result<(), Error> {
    println!("{}", msg);
    Ok(())
}

fn main() {
    // https://docs.rs/winapi/0.3.0/winapi/um/winuser/index.html
    // https://github.com/retep998/winapi-rs/issues/122
    // https://www.codeproject.com/Tips/1053658/Win-GUI-Programming-In-Rust-Language
    print_message("Hello, world with rust winapi crate!").unwrap();
}

후기

  • wasm 붙일려면 cfg로 매크로 분기 태워야 할 것 같다.
  • 그걸 감안하고도 Rust는 갓언어가 맞음.
  • VS Code 씁시다.