Как получить args от WinMain или wWinMain в Rust?

#windows #winapi #rust #ffi

Вопрос:

Я пытаюсь научиться работать с необработанными Win32 API и следую приведенному здесь руководству, но ни за что на свете не могу понять, как передать int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow) сигнатуру функции для работы. Я понимаю int WINAPI , что это не нужно…но как мне передать все эти параметры вызовам WinAPI? Особенно помехи и 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, но предыдущее поведение такое же, а не желаемое…

 #![windows_subsystem = "windows"]
 

Я могу получить дескриптор помехи, вручную вызвав GetModuleHandle() и передав нулевой указатель, но понятия не имею, как получить nShowCmd вручную.

Важное Примечание

Я использую ящик для окон, который я хотел бы использовать.

Любая помощь в этой ускользающей тайне была бы очень признательна!

P.S. Мое окно действительно открывается, и все работает нормально, как и ожидалось, включая работу с FFI и всех тех сумасшедших, которые там замешаны, лол. Но я просто хотел бы понять, как это делается. Можно обойтись и без nShowCmd, но мне бы очень хотелось понять, как это делается в rust. Я также не могу перезаписать fn main() подпись функции, поэтому не знаю, как это сделать.

Ответ №1:

WinMain является предоставляемой пользователем точкой входа в приложение Windows. Исходная точка входа в приложение, как видно из операционной системы, намного проще:

 DWORD CALLBACK RawEntryPoint(void);
 

Теперь библиотеки языковой поддержки должны восстановить информацию о запуске и позвонить в предоставленную пользователем точку входа (подробнее см. WinMain-это обычное имя точки входа процесса Win32).:

  • GetModuleHandle(NULL) для hInstance
  • hPrevInstance всегда находится NULL в 32-разрядной и 64-разрядной Windows
  • GetCommandLine для непространенной командной строки, переданной в программу
  • GetStartupInfo для получения большого количества государственной информации, wShowWindow в том числе соответствующей nCmdShow

Если у вас установлена Visual Studio, вы можете заглянуть в exe_common.inl, чтобы посмотреть, как библиотеки поддержки C и C справляются с этим.

С ржавчиной, к сожалению, все сложнее. Несмотря на то , что компилятор и компоновщик перепрофилировали реализацию CRT MSVC, ответственную за извлечение информации, которая будет передана 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(amp;mut si) };
    let cmd_show = si.wShowWindow as i32;

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

Теперь у вас есть доступ к значению, соответствующему nCmdShow параметру, переданному WinMain при компиляции для C или C (во всяком случае, примерно). В идеале вам нужно было бы посмотреть dwFlags , содержит ли STARTF_USESHOWWINDOW бит, и выдумать разумное значение по умолчанию, если это не так.

Тем не менее, я даже не уверен, какой цели nCmdShow WinMain служит переданный аргумент. Как объяснено ниже ShowWindow , использование этого значения не имеет никакого эффекта, когда оно заполняется из информации, предоставленной вызывающим абонентом.

Обновление 2021-10-28

Начиная с версии 0.22.1, ящик для Windows поставляется с предварительно встроенными привязками, что значительно упрощает использование API Windows. Следующее реализует ту же программу, используя предварительно построенные привязки вместо генерации кода во время компиляции.

Груз.томл

 [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(amp;mut si) };
    let cmd_show = si.wShowWindow as i32;

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

Комментарии:

1. Насколько я понимаю, один из способов nCmdShow полезен , когда вы создаете ярлык для an exe , вы можете выбрать, как должно начинаться окно, если оно свернуто, нормально и т. Д.

2. @jos Конечно, это возможно. Но для этого вам не нужен доступ к аргументу в вашем коде. При первом вызове ShowWindow() флаги , которые вы передаете, игнорируются, а STARTUPINFO вместо них используются флаги из списка. Вы можете просто пройти SW_SHOW и все равно заставить систему применить настройки, сохраненные в ярлыке.

Ответ №2:

В Kernel32 есть эта функция: GetStartupInfo() кажется, что в Windows-rs она сопоставлена bindings::Windows::Win32::System::Threading::GetStartupInfoW .

Эта функция заполняет STARTUPINFOW структуру, в которой между множеством полезных полей WORD wShowWindow есть то же значение, что и в последнем аргументе WinMain() .

Забавно то WinMain , что в этом нет ничего волшебного, это просто функция, которую реальная функция точки входа WinMainCRTStartup вызывает из кода инициализации CRT. Вы можете получить представление о том, как он делает свое дело, посмотрев на эквивалентный исходный код Wine. Там вы можете увидеть, что ваша идея позвонить GetModuleHandle(NULL) , чтобы получить hInstance это, правильная.