【问题标题】:Windows GUI + Console Output, Linux-styleWindows GUI + 控制台输出,Linux 风格
【发布时间】:2011-10-04 02:53:40
【问题描述】:

我有一个 GUI 应用程序,我正在为 Linux 和 Windows 开发跨平台。在 Linux 上,一切都很顺利。但是,我在 Windows 上遇到了麻烦。我希望能够使用 Windows 上的 GUI 应用程序将某些消息记录到控制台,Linux 风格。

我所说的Linux风格的意思是,如果程序从控制台打开,输出将转到控制台,但是如果程序是打开的,例如通过开始菜单,用户将永远看不到控制台输出。显然,这比在 Windows 上听起来要难。

目前,我在 main() 中使用了以下技巧:

#if _WINDOWS /* Fix console output on Windows */
if (AttachConsole(ATTACH_PARENT_PROCESS)) {
    freopen("CONOUT$","wb",stdout);
    freopen("CONOUT$","wb",stderr);
}
#endif

这允许我在程序实际打开窗口之前创建输出,例如从命令行响应“--help”。但是,一旦我的程序实际初始化并打开了一个窗口,就会返回控制台。我需要一个解决方案,让我可以在程序的整个生命周期内继续访问控制台,而无需打开新的控制台(如果最初没有使用)。

【问题讨论】:

标签: c++ windows user-interface console


【解决方案1】:

我记得读过一些关于此的内容,如果我没记错的话,解决方案是将 gui 添加到控制台项目而不是添加控制台到 gui 项目,因为后者只能通过打开新控制台来完成。

【讨论】:

    【解决方案2】:

    我认为您应该创建一个控制台应用程序,然后检查谁初始化了该进程(可能是 cmd.exe),并根据该情况隐藏控制台窗口。然后你在其中创建一个窗口......这方面的缺点是控制台窗口可能会打开一会儿,直到你隐藏它并且它看起来会非常难看,我想。稍后打开控制台没有这个问题,但我不知道标准输出是否像在控制台应用程序中那样重定向到它,或者你是否必须以某种方式设置它,或者你可能必须在每次调用中重定向......不,一定有更好的办法!

    【讨论】:

      【解决方案3】:

      我们使用 ::AllocConsole() 而不是 ::AttachConsole,它在整个应用程序中保持打开状态。试试看?

      【讨论】:

        【解决方案4】:

        目前我发现的最佳解决方案是拥有两个可执行文件。

        • program.exe 是 GUI 应用程序。
        • program.com 是一个辅助命令行应用程序,它生成 program.exe 并将标准 I/O 传递给它。 (它不是 DOS 下的 COM 可执行文件,它只是重命名的标准 PE 可执行文件;因为在默认优先顺序 cmd.exe 中,.com.exe 之前,您可以键入 program,它会自动调用 program.com如果两者都在路径中,而不是 program.exe。)

        通过此设置,您可以在 Windows 命令提示符下键入program,将program.exe 写入标准输出,它会正确显示在控制台上;当您从 GUI 打开 program.exe 时,不会生成任何控制台窗口。

        以下是帮助程序的示例实现,取自 Inkscape: http://bazaar.launchpad.net/~inkscape.dev/inkscape/trunk/view/head:/src/winconsole.cpp

        帮助程序创建三个管道,并使用CreateProcess 生成 GUI 程序,给它适当的管道末端。然后它创建三个线程,在无限循环中在管道和辅助程序的标准 I/O 之间复制数据。帮助程序被编译为控制台应用程序(重要) - MinGW 中的 -mconsole 开关。

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-11-18
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多