Windows GUI + Console Output, Linux-style

前端 未结 4 857
再見小時候
再見小時候 2020-12-18 21:10

I have a GUI application, which I am developing cross-platform for Linux and Windows. On Linux, everything works smoothly. However, I\'ve run into a hitch on Windows. I woul

相关标签:
4条回答
  • 2020-12-18 21:24

    I think you should create a console application and then check who initialized the process (probably cmd.exe) and depending on that you can hide the console window. Then you create a window in it... the donwside on this is that the console window may be open for a moment until you hide it and it's gonna look very ugly, I suppose. Opening later the console doesn't have that issue but I don't know if the stdout redirects to it as it does in console apps or if you have to set it somehow, or maybe you have to redirect in every call... no, there must be a better way!

    0 讨论(0)
  • 2020-12-18 21:29

    I remember reading something about this and if I remember correctly the solution was to add a gui to a console project instead of adding a console to a gui project because the latter could only be done by opening a new console.

    0 讨论(0)
  • 2020-12-18 21:35

    The best solution I have found so far is to have two executables.

    • program.exe is the GUI application.
    • program.com is a helper command-line application that spawns program.exe and passes standard I/O to it. (It's not a COM executable from DOS, it's just a renamed standard PE executable; since .com is before .exe in the default preference order of cmd.exe, you can type program and it will automatically call program.com instead of program.exe if both are in the path.)

    With this setup, you can type program at the Windows command prompt, write to the standard output in program.exe, and it will correctly appear on the console; no console windows will be spawned when you open program.exe from the GUI.

    Here is an example implementation of the helper program, taken from Inkscape: http://bazaar.launchpad.net/~inkscape.dev/inkscape/trunk/view/head:/src/winconsole.cpp

    The helper creates three pipes, and spawns the GUI program with CreateProcess, giving it the appropriate ends of the pipes. Then it creates three threads that copy data between the pipes and the standard I/O of the helper program in an infinite loop. The helper is compiled as a console application (important) - the -mconsole switch in MinGW.

    /**
     * \file
     * Command-line wrapper for Windows.
     *
     * Windows has two types of executables: GUI and console.
     * The GUI executables detach immediately when run from the command
     * prompt (cmd.exe), and whatever you write to standard output
     * disappears into a black hole. Console executables
     * do display standard output and take standard input from the console,
     * but when you run them from the GUI, an extra console window appears.
     * It's possible to hide it, but it still flashes for a fraction
     * of a second.
     *
     * To provide an Unix-like experience, where the application will behave
     * correctly in command line mode and at the same time won't create
     * the ugly console window when run from the GUI, we have to have two
     * executables. The first one, inkscape.exe, is the GUI application.
     * Its entry points are in main.cpp and winmain.cpp. The second one,
     * called inkscape.com, is a small helper application contained in
     * this file. It spawns the GUI application and redirects its output
     * to the console.
     *
     * Note that inkscape.com has nothing to do with "compact executables"
     * from DOS. It's a normal PE executable renamed to .com. The trick
     * is that cmd.exe picks .com over .exe when both are present in PATH,
     * so when you type "inkscape" into the command prompt, inkscape.com
     * gets run. The Windows program loader does not inspect the extension,
     * just like an Unix program loader; it determines the binary format
     * based on the contents of the file.
     *
     *//*
     * Authors:
     *   Jos Hirth <jh@kaioa.com>
     *   Krzysztof Kosinski <tweenk.pl@gmail.com>
     *
     * Copyright (C) 2008-2010 Authors
     *
     * Released under GNU GPL, read the file 'COPYING' for more information
     */
    
    #ifdef WIN32
    #undef DATADIR
    #include <windows.h>
    
    struct echo_thread_info {
        HANDLE echo_read;
        HANDLE echo_write;
        unsigned buffer_size;
    };
    
    // thread function for echoing from one file handle to another
    DWORD WINAPI echo_thread(void *info_void)
    {
        echo_thread_info *info = static_cast<echo_thread_info*>(info_void);
        char *buffer = reinterpret_cast<char *>(LocalAlloc(LMEM_FIXED, info->buffer_size));
        DWORD bytes_read, bytes_written;
    
        while(true){
            if (!ReadFile(info->echo_read, buffer, info->buffer_size, &bytes_read, NULL) || bytes_read == 0)
                if (GetLastError() == ERROR_BROKEN_PIPE)
                    break;
    
            if (!WriteFile(info->echo_write, buffer, bytes_read, &bytes_written, NULL)) {
                if (GetLastError() == ERROR_NO_DATA)
                    break;
            }
        }
    
        LocalFree(reinterpret_cast<HLOCAL>(buffer));
        CloseHandle(info->echo_read);
        CloseHandle(info->echo_write);
    
        return 1;
    }
    
    int main()
    {
        // structs that will store information for our I/O threads
        echo_thread_info stdin = {NULL, NULL, 4096};
        echo_thread_info stdout = {NULL, NULL, 4096};
        echo_thread_info stderr = {NULL, NULL, 4096};
        // handles we'll pass to inkscape.exe
        HANDLE inkscape_stdin, inkscape_stdout, inkscape_stderr;
        HANDLE stdin_thread, stdout_thread, stderr_thread;
    
        SECURITY_ATTRIBUTES sa;
        sa.nLength=sizeof(SECURITY_ATTRIBUTES);
        sa.lpSecurityDescriptor=NULL;
        sa.bInheritHandle=TRUE;
    
        // Determine the path to the Inkscape executable.
        // Do this by looking up the name of this one and redacting the extension to ".exe"
        const int pathbuf = 2048;
        WCHAR *inkscape = reinterpret_cast<WCHAR*>(LocalAlloc(LMEM_FIXED, pathbuf * sizeof(WCHAR)));
        GetModuleFileNameW(NULL, inkscape, pathbuf);
        WCHAR *dot_index = wcsrchr(inkscape, L'.');
        wcsncpy(dot_index, L".exe", 4);
    
        // we simply reuse our own command line for inkscape.exe
        // it guarantees perfect behavior w.r.t. quoting
        WCHAR *cmd = GetCommandLineW();
    
        // set up the pipes and handles
        stdin.echo_read = GetStdHandle(STD_INPUT_HANDLE);
        stdout.echo_write = GetStdHandle(STD_OUTPUT_HANDLE);
        stderr.echo_write = GetStdHandle(STD_ERROR_HANDLE);
        CreatePipe(&inkscape_stdin, &stdin.echo_write, &sa, 0);
        CreatePipe(&stdout.echo_read, &inkscape_stdout, &sa, 0);
        CreatePipe(&stderr.echo_read, &inkscape_stderr, &sa, 0);
    
        // fill in standard IO handles to be used by the process
        PROCESS_INFORMATION pi;
        STARTUPINFOW si;
    
        ZeroMemory(&si,sizeof(STARTUPINFO));
        si.cb = sizeof(STARTUPINFO);
        si.dwFlags = STARTF_USESTDHANDLES;
        si.hStdInput = inkscape_stdin;
        si.hStdOutput = inkscape_stdout;
        si.hStdError = inkscape_stderr;
    
        // spawn inkscape.exe
        CreateProcessW(inkscape, // path to inkscape.exe
                       cmd, // command line as a single string
                       NULL, // process security attributes - unused
                       NULL, // thread security attributes - unused
                       TRUE, // inherit handles
                       0, // flags
                       NULL, // environment - NULL = inherit from us
                       NULL, // working directory - NULL = inherit ours
                       &si, // startup info - see above
                       &pi); // information about the created process - unused
    
        // clean up a bit
        LocalFree(reinterpret_cast<HLOCAL>(inkscape));
        CloseHandle(pi.hThread);
        CloseHandle(pi.hProcess);
        CloseHandle(inkscape_stdin);
        CloseHandle(inkscape_stdout);
        CloseHandle(inkscape_stderr);
    
        // create IO echo threads
        DWORD unused;
        stdin_thread = CreateThread(NULL, 0, echo_thread, (void*) &stdin, 0, &unused);
        stdout_thread = CreateThread(NULL, 0, echo_thread, (void*) &stdout, 0, &unused);
        stderr_thread = CreateThread(NULL, 0, echo_thread, (void*) &stderr, 0, &unused);
    
        // wait until the standard output thread terminates
        WaitForSingleObject(stdout_thread, INFINITE);
    
        return 0;
    }
    
    #endif
    
    0 讨论(0)
  • 2020-12-18 21:38

    We use ::AllocConsole() instead of ::AttachConsole and it remains open throughout the app. Try that?

    0 讨论(0)
提交回复
热议问题