"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?