"Ain't It Funny" is a 2009 Jennifer Lopez hit. But here I'm thinking about rustc.
EDIT: The originally shown program wasn't correct and I've replaced it. I apologize for the mistake.
A Windows program handles GUI in the main thread, MT
, and collects data from the web in a worker thread, WT
. When the data is ready, WT
posts a message to the window procedure in MT
to have it displayed. To post the message, WT
uses the window handle, hwnd: HWND
, from MT
. hwnd
is generated once at the program's start and stays unchanged for the program's lifetime, so passing it between threads is perfectly safe. The program below simulates this situation.
//[dependencies]
//winapi = { version = "0.3.9", features = ["wingdi", "winuser", "libloaderapi", "combaseapi", "objbase", "shobjidl", "winerror"] }
//lazy_static = "1.4.0"
use std::error::Error;
use std::ptr::{null, null_mut};
use std::sync::Mutex;
use std::thread;
use std::time;
use winapi::shared::minwindef::*;
use winapi::shared::windef::*;
use winapi::um::libloaderapi::GetModuleHandleW;
use winapi::um::winuser::*;
pub const WM_WEBUPDT: UINT = 0xFEDC;
/// Turns a Rust string slice into a null-terminated utf-16 vector.
pub fn wide_null(s: &str) -> Vec<u16> {
s.encode_utf16().chain(Some(0)).collect()
}
static TEXT: Mutex<String> = Mutex::new(String::new());
// Window procedure to handle events
pub unsafe extern "system" fn window_proc(
hwnd: HWND, msg: UINT, wparam: WPARAM, lparam: LPARAM,
) -> LRESULT {
match msg {
WM_CLOSE => {
DestroyWindow(hwnd);
}
WM_DESTROY => {
PostQuitMessage(0);
}
WM_WEBUPDT => {
RedrawWindow(hwnd, null(), null_mut(), RDW_INVALIDATE | RDW_ERASE);
}
WM_PAINT => {
let t = TEXT.lock().unwrap().clone();
let mut ps: PAINTSTRUCT = std::mem::zeroed();
let hdc: HDC;
hdc = BeginPaint(hwnd, &mut ps);
let mut rec: RECT = std::mem::zeroed();
GetClientRect(hwnd, &mut rec);
rec.top += 4;
rec.left += 6;
rec.bottom -= 4;
rec.right -= 6;
let txt = wide_null(&t);
DrawTextW(
hdc,
txt.as_ptr(),
txt.len().try_into().unwrap(),
&mut rec,
DT_TOP | DT_LEFT,
);
EndPaint(hwnd, &ps);
}
_ => return DefWindowProcW(hwnd, msg, wparam, lparam),
}
return 0;
}
// Declare class and instantiate window
fn create_main_window(name: &str, title: &str) -> Result<HWND, Box<dyn Error>> {
let name = wide_null(name);
let title = wide_null(title);
unsafe {
let hinstance = GetModuleHandleW(null_mut());
let wnd_class = WNDCLASSEXW {
cbSize: std::mem::size_of::<WNDCLASSEXW>() as u32,
style: CS_OWNDC | CS_HREDRAW | CS_VREDRAW,
lpfnWndProc: Some(window_proc),
cbClsExtra: 0,
cbWndExtra: 0,
hInstance: hinstance,
hIcon: LoadIconW(null_mut(), IDI_APPLICATION),
hCursor: LoadCursorW(null_mut(), IDC_ARROW),
hbrBackground: COLOR_WINDOWFRAME as HBRUSH,
lpszMenuName: null_mut(),
lpszClassName: name.as_ptr(),
hIconSm: LoadIconW(null_mut(), IDI_APPLICATION),
};
// Register window class
if RegisterClassExW(&wnd_class) == 0 {
MessageBoxW(
null_mut(),
wide_null("Window Registration Failed!").as_ptr(),
wide_null("Error").as_ptr(),
MB_ICONEXCLAMATION | MB_OK,
);
return Err("Window Registration Failed".into());
};
// Create a window based on registered class
let handle = CreateWindowExW(
0, // dwExStyle
name.as_ptr(), // lpClassName
title.as_ptr(), // lpWindowName
WS_OVERLAPPEDWINDOW | WS_VISIBLE, // dwStyle
710, // Int x
580, // Int y
700, // Int nWidth
400, // Int nHeight
null_mut(), // hWndParent
null_mut(), // hMenu
hinstance, // hInstance
null_mut(), // lpParam
);
if handle.is_null() {
MessageBoxW( null_mut(),
wide_null("Window Creation Failed!").as_ptr(),
wide_null("Error!").as_ptr(), MB_ICONEXCLAMATION | MB_OK,
);
return Err("Window Creation Failed!".into());
}
Ok(handle)
}
}
// Message handling loop
fn run_message_loop(hwnd: HWND) -> WPARAM {
unsafe {
let mut msg: MSG = std::mem::zeroed();
loop {
// Get message from message queue
if GetMessageW(&mut msg, hwnd, 0, 0) > 0 {
TranslateMessage(&msg);
DispatchMessageW(&msg);
} else {
// Return on error (<0) or exit (=0) cases
return msg.wParam;
}
}
}
}
fn main() {
let mut int_text = 1234567;
*TEXT.lock().unwrap() = int_text.to_string() + "\n";
let hwnd = create_main_window("Main Window", "Main Window").expect("Window creation failed!");
unsafe {
ShowWindow(hwnd, SW_SHOW);
UpdateWindow(hwnd);
}
let hwnd2 = hwnd;
thread::spawn(move || loop {
thread::sleep(time::Duration::from_millis(2000));
int_text += 1234567;
*TEXT.lock().unwrap() += &(int_text.to_string() + "\n");
unsafe {
PostMessageW (hwnd2, WM_WEBUPDT, 0 as WPARAM, 0 as LPARAM);
}
});
run_message_loop(hwnd);
}
This program would not compile. Rustc would not allow passing hwnd2: HWND
between threads.
But you can easily fool rustc by casting hwnd2
to usize
in MT
and casting back usize
to HWND
in WT
.
fn main() {
let mut int_text = 1234567;
*TEXT.lock().unwrap() = int_text.to_string() + "\n";
let hwnd = create_main_window("Main Window", "Main Window").expect("Window creation failed!");
unsafe {
ShowWindow(hwnd, SW_SHOW);
UpdateWindow(hwnd);
}
let hwnd2 = hwnd as usize;
thread::spawn(move || loop {
thread::sleep(time::Duration::from_millis(2000));
int_text += 1234567;
*TEXT.lock().unwrap() += &(int_text.to_string() + "\n");
unsafe {
PostMessageW (hwnd2 as HWND, WM_WEBUPDT, 0 as WPARAM, 0 as LPARAM);
}
});
run_message_loop(hwnd);
}
Should it work this way? What would be the proper way to handle this situation?