【问题标题】:AttachConsole and QProcess::readAll()AttachConsole 和 QProcess::readAll()
【发布时间】:2015-08-21 09:31:14
【问题描述】:

用户抱怨我的 cmd 应用程序在特定 GUI 设置中调用时会闪现一个命令行窗口。

看在他的份上,我将应用程序做成了一个 gui 应用程序并附加到控制台。除了从 powershell 调用光标时出现问题外,效果很好。

最大的问题是输出现在不再被调用 Qt 应用程序(QProcess::MergedChannelsreadAll)捕获,因为 cmd 应用程序直接将其输出放到包含控制台窗口而不是调用 Qt 应用程序。

有没有比调用AttachConsole 更好的方法,或者我应该在应用程序中添加一个特殊的命令行选项来防止附加?

编辑:附加代码https://github.com/Snorenotify/Snoretoast/blob/master/src/main.cpp#L209

【问题讨论】:

    标签: c++ windows qt


    【解决方案1】:

    我在使用 Inkscape 时遇到了一个非常相似的问题。当 GUI 应用程序在控制台(类似于 Unix)中运行时,从 GUI 应用程序获得命令行输出的最佳方式是拥有两个可执行文件。

    • program.exe 是一个窗口应用程序。
    • program.com 是一个辅助控制台应用程序,它生成 program.exe 并将控制台输入和输出传递给它。请注意,它与 DOS 中的 COM 可执行文件无关 - 它只是一个重命名为 .com 的标准 PE 可执行文件。

    由于cmd shell 中可执行扩展的默认优先顺序在.exe 之前具有.com,因此在shell 中键入program 将执行program.com,而不是program.exe

    有关工作示例,请参阅此文件: http://bazaar.launchpad.net/~inkscape.dev/inkscape/trunk/view/head:/src/winconsole.cpp - 为方便起见粘贴在下方。

    /**
     * \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
    

    总结一下:辅助应用程序创建了三个管道。它使用CreateProcess 生成窗口化应用程序,为其提供适当的管道末端作为标准输入、输出和错误句柄。最后,它创建了三个线程,将数据从管道复制到辅助应用程序的标准输入、输出和错误。

    【讨论】:

    • 这是一个绝妙的技巧。我喜欢它。
    • 嗯,这就是我对 github.com/TheOneRing/vsd 所做的事情(但也有调试流),但我想我宁愿编译两个版本(一个 cmd 版本和一个 GUI 版本),因为这仍然是干净得多。 AttachConsole 代码的目的是避免需要两个版本。
    • @TheOneRing 如何为用户提供具有 99% 相同内容的两个大文件(“更干净”),而不是只提供一个大文件和一个非常小的附加文件?
    • 由于您可以使用 dll,您的应用程序可以非常小。根据我对CreateProcessW 的经验,我知道使用线程重定向输出也不是那么简单。
    • 关于我看到的唯一优化是只使用一个工作线程 - 数据复制将受内存限制,浪费三个线程的堆栈页面只是为了洗牌这些小缓冲区没有什么意义在管道之间。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-07-08
    • 1970-01-01
    • 2017-11-15
    • 1970-01-01
    • 1970-01-01
    • 2017-05-18
    • 2010-09-27
    相关资源
    最近更新 更多