Skip to content

start: Make nCmdShow better match how Windows sets it #17799

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Nov 2, 2023

Conversation

squeek502
Copy link
Collaborator

@squeek502 squeek502 commented Oct 31, 2023

Partial follow up to #17763 (comment) (cc @e4m2)

nCmdShow is a parameter of WinMain that "Controls how the window is to be shown."

From the added comment:

    // This makes Zig mostly match the nCmdShow behavior of a C program with a WinMain symbol:
    // - With STARTF_USESHOWWINDOW set in STARTUPINFO.dwFlags of the CreateProcess call:
    //   - Compiled with subsystem:console -> nCmdShow is always SW_SHOWDEFAULT
    //   - Compiled with subsystem:windows -> nCmdShow is STARTUPINFO.wShowWindow from
    //     the parent CreateProcess call
    // - With STARTF_USESHOWWINDOW unset:
    //   - nCmdShow is always SW_SHOWDEFAULT
    //
    // TODO: Figure out how to check the subsystem and use it to match the above
    //       behavior, or intentionally avoid matching the above behavior with
    //       regards to the subsystem.

With regards to the TODO, it might make sense to just intentionally keep the current behavior: always forward wShowWindow if STARTF_USESHOWWINDOW is set regardless of the subsystem of the process, since it doesn't seem like a program compiled with the console subsystem would care about nCmdShow, or if they do, then I'm not sure why it makes sense to not set nCmdShow.

Tested the Windows behavior with the following setup:


// launch-show.c
#include <windows.h>
#include <stdio.h>
#include <tchar.h>
#include <shlwapi.h>

void _tmain( int argc, TCHAR *argv[] )
{
    STARTUPINFO si;
    PROCESS_INFORMATION pi;

    ZeroMemory( &si, sizeof(si) );
    si.cb = sizeof(si);
    ZeroMemory( &pi, sizeof(pi) );

    if( argc != 3 )
    {
        printf("Usage: %s [wShowWindow] [cmdline]\n", argv[0]);
        return;
    }

    si.wShowWindow = StrToInt(argv[1]);
    si.dwFlags |= STARTF_USESHOWWINDOW;

    // Start the child process. 
    if( !CreateProcess( NULL,   // No module name (use command line)
        argv[2],        // Command line
        NULL,           // Process handle not inheritable
        NULL,           // Thread handle not inheritable
        FALSE,          // Set handle inheritance to FALSE
        0,              // No creation flags
        NULL,           // Use parent's environment block
        NULL,           // Use parent's starting directory 
        &si,            // Pointer to STARTUPINFO structure
        &pi )           // Pointer to PROCESS_INFORMATION structure
    ) 
    {
        printf( "CreateProcess failed (%d).\n", GetLastError() );
        return;
    }

    // Wait until child process exits.
    WaitForSingleObject( pi.hProcess, INFINITE );

    // Close process and thread handles. 
    CloseHandle( pi.hProcess );
    CloseHandle( pi.hThread );
}

Compiled with:

zig build-exe launch-show.c -lc -lShlwapi

// winmain.c
#include <windows.h>
#include <stdio.h>
#include <winbase.h>

int APIENTRY WinMain(_In_ HINSTANCE hInstance,
                     _In_opt_ HINSTANCE hPrevInstance,
                     _In_ LPSTR    lpCmdLine,
                     _In_ int       nCmdShow)
{
    STARTUPINFO info;
    GetStartupInfo(&info);

    CHAR buf[256];
    wsprintf(buf, "WinMain: %d GetStartupInfo: %d", nCmdShow, info.wShowWindow);
    printf("%s\n", buf);
    MessageBoxA(NULL, buf, NULL, 0);
}

Compiled with:

zig build-exe winmain.c -lc -femit-bin=winmain-console.exe --subsystem console

and

zig build-exe winmain.c -lc -femit-bin=winmain-windows.exe --subsystem windows

> launch-show.exe 5 winmain-console.exe
WinMain: 10 GetStartupInfo: 5

(the STARTUPINFO returned from GetStartupInfo has the wShowWindow of the CreateProcess call, but nCmdShow passed to WinMain is SW_SHOWDEFAULT)

> launch-show.exe 5 winmain-windows.exe
<message box with the string "WinMain: 5 GetStartupInfo: 5">

If the si.dwFlags |= STARTF_USESHOWWINDOW; line in launch-show.exe is commented out, then nCmdShow in WinMain is always SW_SHOWDEFAULT.

@expikr
Copy link
Contributor

expikr commented Oct 31, 2023

Note: can also test via shell shortcuts like so:

image

which is basically a proxy to what you're doing programmatically, but still useful to test from the shell to verify the behavior indeed matches.

@squeek502
Copy link
Collaborator Author

Addressed the comments, this should be mergeable unless there are more.

I will have a follow-up PR for the subsystem stuff. Right now, std.builtin.subsystem.? == .Windows will always be true given how std.builtin.subsystem works which is fine (it just means we'll pass on the 'show window' value to processes compiled with the console subsystem).

Copy link
Member

@andrewrk andrewrk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really appreciate those nice comments, thank you!

@andrewrk andrewrk enabled auto-merge (rebase) November 2, 2023 00:34
@andrewrk andrewrk merged commit 42d4d07 into ziglang:master Nov 2, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants