Rust调用WinAPI实现窗口

突然想玩WinAPI。但是说实在的,真不想写C++。

正好Rust那边有人包好了一个crate:winapi方便我们调用,资料也有一些,抄点儿玩玩看。

环境

操作系统Win10 1909,rustc --versionrustc 1.39.0 (4560ea788 2019-11-04)cargo --versioncargo 1.39.0 (1c6ec66d5 2019-09-30),依然使用Visual Studio 2019的link.exe。

参考

实践

Rust包作成

Set-Location D:\prj
cargo new hello_window

修改.\Cargo.toml

[package]
name = "hello_window"
version = "0.1.0"
authors = ["user"]
edition = "2018"

[dependencies.winapi]
version = "0.3"
features = ["winuser", "winbase"]

修改.\src\lib.rs

use std::mem;
use std::ptr;
use winapi::{
    shared::{
        minwindef::{LPARAM, LRESULT, UINT, WPARAM},
        windef::{HBRUSH, HWND},
    },
    um::{
        winbase::lstrlenW,
        wingdi::{GetStockObject, TextOutW, WHITE_BRUSH},
        winuser::{
            BeginPaint, CreateWindowExW, DefWindowProcW, DispatchMessageW, EndPaint, GetMessageW,
            LoadCursorW, LoadIconW, PostQuitMessage, RegisterClassW, ShowWindow, TranslateMessage,
            UpdateWindow, CS_HREDRAW, CS_VREDRAW, IDC_ARROW, IDI_APPLICATION, MSG, PAINTSTRUCT,
            SW_NORMAL, WM_DESTROY, WM_PAINT, WNDCLASSW, WS_OVERLAPPEDWINDOW,
        },
    },
};

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>();
        let mut msg = mem::MaybeUninit::<MSG>::uninit().assume_init();
        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!").as_ptr(),
        WS_OVERLAPPEDWINDOW,
        0,
        0,
        240, // window width
        160, // window height
        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_PAINT => {
            let greetings = encode("Welcome!").as_ptr();
            let mut ps = mem::MaybeUninit::<PAINTSTRUCT>::uninit().assume_init();
            let hdc = BeginPaint(hwnd, &mut ps);
            TextOutW(hdc, 5, 5, greetings, lstrlenW(greetings));
            EndPaint(hwnd, &mut ps);
        }
        WM_DESTROY => PostQuitMessage(0),
        _ => return DefWindowProcW(hwnd, msg, w_param, l_param),
    };
    0
}

解说

并没有WinAPI的经验,看看上面的参考就好吧。

稍微注意一下(实际已被注释掉):

let mut msg = mem::uninitialized::<MSG>();

编译器会抱怨它deprecated,那么就替换一下:

let mut msg = mem::MaybeUninit::<MSG>::uninit().assume_init();

再就是,想用lstrlenW,就必须在.\Cargo.tomlwinbase要到手,不然哪怕use语句写得好好的,代码提示或许也能看得见,但它就是干瞪眼,说找不到。

在这个项目,并不是说Rust编译得通就OK。哪怕类型看上去对应得上,编译通过,还是达不到预期效果。

虽然无关紧要:之前也玩过MessageBox,参见:Rust で Windows プログラミング - MessageBox編。并不复杂,照做就是。

后记

至于说当时为什么想玩WinAPI,其实是希望看看制作TSF框架输入法的可行性。现在看来,要是我的话,可能还不够吧。毕竟连个窗口都费尽心思。

行吧,就当自己赚到了点经验。

InSb

InSb

只是跟工作和生活相关的记录