Windows GUI + консольный вывод в стиле Linux - PullRequest
16 голосов
/ 13 июля 2011

У меня есть приложение с графическим интерфейсом, которое я разрабатываю кросс-платформенный для Linux и Windows.В Linux все работает без сбоев.Тем не менее, я столкнулся с проблемой на Windows.Я хотел бы иметь возможность записывать определенные сообщения на консоль с помощью приложения с графическим интерфейсом в Windows, в стиле 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» из командной строки.Однако, как только окно фактически инициализируется и открывается моей программой, консоль возвращается.Мне нужно решение, которое позволит мне продолжать доступ к консоли в течение всей жизни моей программы, не открывая новую консоль, если она не использовалась изначально.

Ответы [ 4 ]

2 голосов
/ 16 июля 2011

Мы используем :: AllocConsole () вместо :: AttachConsole, и он остается открытым во всем приложении. Попробуйте это?

1 голос
/ 21 августа 2015

Лучшее решение, которое я нашел на данный момент, - это два исполняемых файла.

  • program.exe - приложение с графическим интерфейсом.
  • program.com - это вспомогательное приложение командной строки, которое порождает program.exe и передает ему стандартный ввод / вывод. (Это не исполняемый файл COM из DOS, это просто переименованный стандартный исполняемый файл PE; поскольку .com предшествует .exe в порядке предпочтения по умолчанию cmd.exe, вы можете ввести program, и он автоматически вызовет program.com вместо program.exe, если оба находятся в пути.)

С помощью этой настройки вы можете ввести program в командной строке Windows, записать в стандартный вывод в program.exe, и он будет правильно отображаться на консоли; никакие окна консоли не будут появляться при открытии program.exe из графического интерфейса.

Вот пример реализации вспомогательной программы, взятой из Inkscape: http://bazaar.launchpad.net/~inkscape.dev/inkscape/trunk/view/head:/src/winconsole.cpp

Помощник создает три канала и порождает программу GUI с CreateProcess, давая ему соответствующие концы каналов. Затем он создает три потока, которые копируют данные между каналами и стандартным вводом / выводом вспомогательной программы в бесконечном цикле. Помощник скомпилирован как консольное приложение (важно) - переключатель -mconsole в 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 голосов
/ 13 июля 2011

Я думаю, вам следует создать консольное приложение, а затем проверить, кто инициализировал процесс (возможно, cmd.exe), и в зависимости от этого вы можете скрыть окно консоли.Затем вы создаете в нем окно ... Недостатком этого является то, что окно консоли может быть открыто на мгновение, пока вы его не скроете, и оно будет выглядеть очень уродливо, я полагаю.При открытии позже у консоли нет этой проблемы, но я не знаю, перенаправляет ли stdout на него, как это происходит в консольных приложениях, или вам нужно как-то его настроить, или, возможно, вам нужно перенаправлять при каждом вызове ... нет, должен быть лучший способ!

0 голосов
/ 13 июля 2011

Я помню, что читал что-то об этом, и, если я правильно помню, было решение добавить графический интерфейс пользователя в консольный проект вместо добавления консоли в проект графического интерфейса, поскольку последнее можно было сделать только путем открытия новой консоли.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...