`no_std` / `no_main` Hello World example for Windows

I've had a few people ask for Windows examples of no_std and no_main Hello World over at min-sized-rust. I'd like to update or make Windows specific copies of

And run these in CI on Windows so that users will have nice minimal examples of how to start with minimized binaries using these techniques on Windows.

Would there happen to be a Rust-Windows expert that could help provide some small stand alone Hello World snippets that build on Windows in these scenarios?

what's the criteria for no_std and no_main? if it's ok to get rid of the crt, and only use native Windows APIs (e.g. using the winapi or windows-sys crate), I can easily get a no_std 2.5KiB binary file with the following snippet on x86_64-msvc toolchain, (but I doubt its practical usefulness due to lack of crt):

# Cargo.toml
[dependencies]
windows-sys = { version = "0.52.0", features = [
	"Win32_Foundation",
	"Win32_UI_WindowsAndMessaging", # for `MessageBoxA`
	"Win32_System_Threading", # for `ExitProcess`
] }
[profile.release]
panic = "abort"     # Abort on panic
// main.rs
#![no_std]
#![no_main]
#![windows_subsystem = "windows"]

use core::panic::PanicInfo;

use windows_sys::core::PCSTR;
use windows_sys::Win32::System::Threading::ExitProcess;
use windows_sys::Win32::UI::WindowsAndMessaging::MessageBoxA;
use windows_sys::Win32::UI::WindowsAndMessaging::MB_ICONERROR;
use windows_sys::Win32::UI::WindowsAndMessaging::MB_OK;


// just put a infinite loop to satisfy the compiler for this
// below is completely meaningless because we have `panic = "abort"` in the profile
// and we cannot even do panic due to no crt
// here is for illustration purpose only
#[panic_handler]
fn panic_handler(_: &PanicInfo<'_>) -> ! {
	// without the helper macro: const PANIC: PCSTR= b"fatal error\0".as_ptr();
	const PANIC: PCSTR = windows_sys::s!("fatal error");
	unsafe {
		MessageBoxA(0, PANIC, PANIC, MB_ICONERROR);
		ExitProcess(1);
	}
}

/// this is the default entry point name
/// can be changed through a linker flag, but you need a build script for that
#[no_mangle]
fn mainCRTStartup() {
	// without the helper macro: const MESSAGE: PCSTR= b"panic\0".as_ptr();
	const MESSAGE: PCSTR = windows_sys::s!("hello, win32!");
	const TITLE: PCSTR = windows_sys::s!("hello");
	unsafe {
		MessageBoxA(0, MESSAGE, TITLE, MB_OK);
		ExitProcess(0);
	}
}
3 Likes

For me, it's the minimal amount of code needed to simply print Hello World, while also using

#![no_std]
#![no_main]

What you posted above looks very promising, thanks!

For no_main, you can use the same thing the standard library does to get a stdout handle. It looks like this:

#![no_main]

use std::fs::File;
use std::io::Write as _;
use std::os::windows::{io::FromRawHandle as _, raw::HANDLE};

#[link(name = "kernel32")]
extern "system" {
    pub fn GetStdHandle(nstdhandle: u32) -> HANDLE;
}

pub const STD_OUTPUT_HANDLE: u32 = 4294967285;

fn stdout() -> File {
    unsafe { File::from_raw_handle(GetStdHandle(STD_OUTPUT_HANDLE)) }
}

#[no_mangle]
pub fn main(_argc: i32, _argv: *const *const u8) -> u32 {
    let mut stdout = stdout();
    stdout.write_all(b"Hello, world!\n").unwrap();

    0
}

See GetStdHandle function - Windows Console | Microsoft Learn

1 Like

if you mean the message should be "printed" to the console window , then you can use the "Console" API. (well, my snippet above pops a message box, which I assume is not CI friendly anyway)

also, don't forget to change the windows_subsystem attribute to "console". (although technically for a "windows" subsystem program, you can use AttachConsole to output to the console window of its parent process, it behaves slight differently)

# Cargo.toml
[dependencies]
windows-sys = { version = "0.52.0", features = [
	"Win32_Foundation",
	"Win32_System_Threading", // for `ExitProcess`
	"Win32_System_Console", // for `WriteConsoleA` etc
] }
# ...
// main.rs
#![no_main]
#![no_std]
#![windows_subsystem = "console"]

use core::ffi::c_void;
use core::panic::PanicInfo;

use windows_sys::Win32::System::Console::GetStdHandle;
use windows_sys::Win32::System::Console::WriteConsoleA;
use windows_sys::Win32::System::Console::STD_OUTPUT_HANDLE;
use windows_sys::Win32::System::Threading::ExitProcess;

// used when `windows_subsystem = "windows"`
//use windows_sys::Win32::System::Console::AttachConsole;
//use windows_sys::Win32::System::Console::ATTACH_PARENT_PROCESS;

#[panic_handler]
fn panic(_: &PanicInfo<'_>) -> ! {
	unsafe {
		ExitProcess(1);
	}
}

#[no_mangle]
fn mainCRTStartup() -> ! {
	let message = "hello world\n";
	unsafe {
		// need this when `windows_subsystem = "windows"`
		// AttachConsole(ATTACH_PARENT_PROCESS);

		// get a handle to the console output buffer
		let console = GetStdHandle(STD_OUTPUT_HANDLE);

		// write the message to the console buffer
		// alternatively, `WriteFile` can be used in this case too, need additional feature flags for `windows-sys` crate
		WriteConsoleA(
			console,
			message.as_ptr().cast::<c_void>(),
			message.len() as u32,
			core::ptr::null_mut(),
			core::ptr::null(),
		);

		ExitProcess(0)
	}
}

PS:

to be honest, it might be fun, but in practice I see very little benefit of perusing such extreme no_std on Windows (no_main is fine, but I doubt you would get significant size reduction compared to "normal" rust programs), because you end up using only the native APIs (i.e. unsafe all over the place) besides core, it's roughly "C program, but disguised in rust syntax).

the native Win32 (and NT) APIs are very complicated, for anyone who needs to use them a lot, I would recommend the winsafe crate (whether you are doing min_size or not):

3 Likes

Thanks @nerditation ! These examples were exactly what I was looking for.

These have been upstreamed and I gave you credit in the MR

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.