如何在 Rust 中从 WinMain 或 wWinMain 获取 args?

6

我正在尝试学习如何使用原始的Win32 API,并按照这里的教程进行学习,但是我弄不清楚如何传递 int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow) 函数签名。我确实理解 int WINAPI 不需要...但是如何将所有这些参数传递给WinAPI调用呢?特别是hInstance和nCmdShow?

我的目标

从中获取hInstance和nShowCmd。

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

将其嵌入到Rust程序中,可能类似于以下内容:
fn main(/* args, such as hInstance, nShowCmd here /*) {
}

或者,更有可能的方法:

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

我尝试过的方法

我尝试获取参数,但这只是传递了我用于启动程序的命令行参数,args[0] 是程序的名称,这是预期行为。同时调用 args_os() 也会得到同样的结果。

我还尝试设置窗口子系统,但之前的行为仍然相同,不是期望的行为...

#![windows_subsystem = "windows"]

我可以通过手动调用 GetModuleHandle() 并传入 null 指针来获取 hInstance 句柄,但不知道如何手动获取 nShowCmd。

重要提示

我正在使用 windows crate,这是我想要使用的内容。

任何对这个问题的帮助都将不胜感激!

P.S. 我的窗口确实打开了,并且一切正常,包括与 FFI 一起工作和所有涉及其中的疯狂操作,哈哈。但我只是想了解这是如何完成的。没有 nShowCmd 也可以过得去,但我真的很想能够理解如何在 Rust 中完成这些操作。我还不能重写 fn main() 函数签名,因此不确定该怎么做。

2个回答

9

WinMain 是Windows应用程序的用户提供的入口点。操作系统看到的原始应用程序入口点要简单得多:

DWORD CALLBACK RawEntryPoint(void);

现在需要语言支持库来恢复启动信息并调用用户提供的入口点 (详情请参见WinMain只是Win32进程入口点的约定名称):

如果您安装了Visual Studio,可以查看 exe_common.inl 中C和C++支持库的实现方法。

使用Rust可能会更加复杂。尽管编译器和链接器重新利用MSVC的CRT实现来提取将传递给WinMain的信息,但我不知道如何从Rust中获得这些信息。

您需要手动恢复该信息。获取nCmdShow参数需要更多步骤,因此让我们在这里进行说明:

// 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);
}

有了这个,您现在可以访问与编译C或C++时传递给WinMainnCmdShow参数相对应的值(大致如此)。理想情况下,您需要查看dwFlags是否包含STARTF_USESHOWWINDOW位,并在其未包含时构建一个合理的默认值。

话虽如此,我甚至不确定传递给WinMainnCmdShow参数有什么用处。正如在ShowWindow中所解释的那样,当它从调用方提供的信息填充时,使用该值没有任何效果。

更新2021-10-28

从版本0.22.1开始,windows crate带有预构建绑定,使得更轻松地使用Windows API。以下使用预构建绑定实现了与编译时代码生成相同的程序。

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);
}

据我理解,nCmdShow 的一种有用方式是在创建 exe 的快捷方式时,您可以选择窗口的启动方式,如最小化、正常等。 - Jose Quesada
1
@jos 当然可以。但是为了让它起作用,您不需要在代码中访问参数。第一次调用 ShowWindow() 时,忽略传递的标志,并使用 STARTUPINFO 中的标志。您只需传递 SW_SHOW 即可,仍然可以让系统应用存储在快捷方式中的设置。 - IInspectable

5

在Kernel32中有这个函数:GetStartupInfo(),在windows-rs中似乎被映射为bindings::Windows::Win32::System::Threading::GetStartupInfoW

这个函数填充了一个STARTUPINFOW结构体,其中有很多有用的字段,包括WORD wShowWindow,它的值与WinMain()的最后一个参数相同。

关于WinMain的有趣之处在于它没有什么神奇的地方,它只是真正的入口点函数WinMainCRTStartup从CRT初始化代码中调用的函数。你可以通过查看等效的Wine源代码来了解它的运行方式。在那里,你可以看到你调用GetModuleHandle(NULL)获取hInstance的想法是正确的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接