Jose Quesada
Jose Quesada

Reputation: 477

How to get args from WinMain or wWinMain in Rust?

I am trying to learn how to work with raw Win32 API's and am following the tutorial here, but cannot, for the life of me, figure out how to pass int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow) function signature to work. I do understand the int WINAPI isn't needed...but how do I get all of those parameters to pass to WinAPI calls? Especially hInstance and nCmdShow?

My Goal

Get hInstance and nShowCmd from

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow) {}

into a Rust program, perhaps something like:

fn main(/* args, such as hInstance, nShowCmd here /*) {
}

or, the more likely way:

fn main() {
    std::env::/* hopefully something like args() /*;
}

What I've tried

I've tried getting the args, but this just passes the command-line arguments that I used to spawn the program, just as args[0] being the name of the program, which is the expected behavior. Also, calling args_os() gives the same result.

I've also tried to set the windows subsystem, but the previous behavior is the same, not the desired behavior...

#![windows_subsystem = "windows"]

I am able to get the hInstance handle by manually calling GetModuleHandle() and passing in a null pointer, but have no idea how to get the nShowCmd manually.

Important Note

I am using the windows crate, which is what I would like to use.

Any help to this eluding mystery would be much appreciated!

P.S. My window does open, and everything works fine, as expected, including working with FFI, and all the crazies involved there, lol. But I just would like to understand how this is done. One can get by without the nShowCmd, but I would really like to be able to understand how this is done in rust. I also cannot overwrite the fn main() function signature, so not sure how to go about it.

Upvotes: 6

Views: 2704

Answers (2)

IInspectable
IInspectable

Reputation: 51414

WinMain is the user-provided entry point of a Windows application. The raw application entry point as seen by the OS is far simpler:

DWORD CALLBACK RawEntryPoint(void);

It is now up to language support libraries to recover the startup information and call into the user-provided entry point (see WinMain is just the conventional name for the Win32 process entry point for details):

  • GetModuleHandle(NULL) for hInstance
  • hPrevInstance is always NULL in 32-bit and 64-bit Windows
  • GetCommandLine for the unparsed command line passed to the program
  • GetStartupInfo for lots of state information, including the wShowWindow that corresponds to nCmdShow

If you have Visual Studio installed you can have a look inside exe_common.inl to see how the C and C++ support libraries go about this.

With Rust things are unfortunately more complex. Even though the compiler and linker repurpose MSVC's CRT implementation responsible for extracting the information that would be passed into WinMain, I'm not aware of a way to get a hold of this from Rust.

You're going to have to recover that information manually. Getting to the nCmdShow parameter is a bit more involved, so let's illustrate that here:

// build.rs
// Using windows-rs 0.17.2; version 0.10.0 and later should be just fine
fn main() {
    windows::build!(Windows::Win32::System::Threading::GetStartupInfoW,)
}
// src/main.rs
mod bindings {
    windows::include_bindings!();
}

use bindings::Windows::Win32::System::Threading::{GetStartupInfoW, STARTUPINFOW};

fn main() {
    let mut si = STARTUPINFOW {
        cb: std::mem::size_of::<STARTUPINFOW>() as u32,
        ..Default::default()
    };
    unsafe { GetStartupInfoW(&mut si) };
    let cmd_show = si.wShowWindow as i32;

    println!("nCmdShow: {:?}", cmd_show);
}

With that you now have access to a value that corresponds to the nCmdShow parameter passed into WinMain when compiling for C or C++ (roughly, anyway). Ideally you would need to see whether dwFlags contains the STARTF_USESHOWWINDOW bit, and fabricate a reasonable default when it doesn't.

That said, I'm not even sure what purpose the nCmdShow argument passed into WinMain serves. As explained under ShowWindow, using that value doesn't have any effect when it is populated from caller-provided information.

Update 2021-10-28

Starting with version 0.22.1 the windows crate comes with pre-built bindings, making it a lot easier to consume the Windows API. The following implements the same program using the pre-built bindings in place of compile-time code generation.

Cargo.toml

[package]
name = "startup_info"
version = "0.0.0"
edition = "2021"

[dependencies.windows]
version = "0.22.1"
features = ["Win32_Foundation", "Win32_System_Threading"]

main.rs

use windows::Win32::System::Threading::{GetStartupInfoW, STARTUPINFOW};

fn main() {
    let mut si = STARTUPINFOW {
        cb: std::mem::size_of::<STARTUPINFOW>() as u32,
        ..Default::default()
    };
    unsafe { GetStartupInfoW(&mut si) };
    let cmd_show = si.wShowWindow as i32;

    println!("nCmdShow: {:?}", cmd_show);
}

Upvotes: 9

rodrigo
rodrigo

Reputation: 98368

There is this function in Kernel32: GetStartupInfo() that in windows-rs seems to be mapped to bindings::Windows::Win32::System::Threading::GetStartupInfoW.

This function fills a STARTUPINFOW structure that has, between a lot of useful fields, WORD wShowWindow that has the same value as the last argument in WinMain().

The funny thing about WinMain is that there is nothing magic in it, it is just the function that the real entry point function, WinMainCRTStartup calls from the CRT initialization code. You can get an idea of how it does its thing by looking at the equivalent Wine source code. There you can see that your idea of calling GetModuleHandle(NULL) to get the hInstance is the right one.

Upvotes: 5

Related Questions