Вывод Console.WriteLine из приложений Windows WPF на реальную консоль

Справочная информация. Я пытаюсь добавить возможности командной строки и пакетной обработки в существующее приложение WPF Windows. Когда я обнаруживаю некоторые параметры при запуске, я подавляю окно от появления, выполняю некоторую обработку и завершаю сразу. Теперь, поскольку нет UI, я бы хотел вывести некоторые сообщения в stdout/stderr. Рассмотрим следующий код:

namespace WpfConsoleTest
{
    public partial class App : Application
    {
        protected override void OnStartup(StartupEventArgs e)
        {
            Console.WriteLine("Start");
            System.Threading.Thread.Sleep(1000);
            Console.WriteLine("Stop");
            Shutdown(0);
        }
    }
}

Когда я запускаю из командной строки, я ожидаю следующий вывод:

Start
Stop

Вместо этого:

C:\test>WpfConsoleTest.exe

C:\test>

Вы можете перенаправить вывод:

C:\test>WpfConsoleTest.exe > out.txt

C:\test>type out.txt
Start
Stop

Перенаправление в CON не работает, к сожалению:

C:\test>WpfConsoleTest.exe > CON

C:\test>

Другая проблема заключается в том, что после запуска WpfConsoleTest.exe завершает работу instantietaly. Итак:

C:\test>WpfConsoleTest.exe > out.txt & type out.txt

C:\test>

Но:

C:\test>WpfConsoleTest.exe > out.txt & ping localhost > nul & type out.txt
Start
Stop

Лучшим решением, с которым я смог дойти, является использование start /B /wait:

C:\test>start /B /wait WpfConsoleTest.exe > out.txt & type out.txt
Start
Stop

Этот подход в основном одобрен - если вы закроете его в летучей мыши, вы можете сохранить код ошибки и так далее. Одно огромное падение состоит в том, что вы получаете результат после завершения приложения, т.е. Вы не можете отслеживать прогресс, вам нужно подождать, пока что закончится.

Поэтому мой вопрос: Как вывести на родительскую консоль из приложения WPF Windows?. Почему так сложно захватить stdout/stderr из WPF?

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

РЕДАКТИРОВАТЬ: я хочу, чтобы мое приложение выводилось в консоль существующей, если есть один и не, чтобы создать новый, если он отсутствует.

Ответ 1

После того, как вы немного разобрались, я нашел этот ответ. Код теперь:

namespace WpfConsoleTest
{
    public partial class App : Application
    {
        [DllImport("Kernel32.dll")]
        public static extern bool AttachConsole(int processId);

        protected override void OnStartup(StartupEventArgs e)
        {
            AttachConsole(-1);
            Console.WriteLine("Start");
            System.Threading.Thread.Sleep(1000);
            Console.WriteLine("Stop");
            Shutdown(0);
        }
    }
}

Вызов exe напрямую по-прежнему имеет неприятный побочный эффект, связанный с немедленным вызовом:

C:\test>WpfConsoleTest.exe

C:\test>Start
Stop

^^^^
The cursor will stay here waiting for the user to press enter!

Решение снова использовать start:

C:\test>start /wait WpfConsoleTest.exe
Start
Stop

Спасибо за ввод!

Ответ 2

То, что я делал в прошлом, - это сделать консольное приложение и вызвать P/Invoke, чтобы скрыть окно консоли, связанное с приложением, как только я покажу свое окно WPF:

internal sealed class Win32
    {
        [DllImport("user32.dll")]
        static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);

        public static void HideConsoleWindow()
        {
            IntPtr hWnd = Process.GetCurrentProcess().MainWindowHandle;

            if (hWnd != IntPtr.Zero)
            {
                ShowWindow(hWnd, 0); // 0 = SW_HIDE
            }
        }
    }

Ответ 3

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

public class ConsoleHelper
{
    /// <summary>
    /// Allocates a new console for current process.
    /// </summary>
    [DllImport("kernel32.dll")]
    public static extern Boolean AllocConsole();

    /// <summary>
    /// Frees the console.
    /// </summary>
    [DllImport("kernel32.dll")]
    public static extern Boolean FreeConsole();
}

Чтобы использовать это в своем примере, вы должны это сделать:

namespace WpfConsoleTest
{
    public partial class App : Application
    {
        protected override void OnStartup(StartupEventArgs e)
        {
            ConsoleHelper.AllocConsole(); 
            Console.WriteLine("Start");
            System.Threading.Thread.Sleep(1000);
            Console.WriteLine("Stop");
            ConsoleHelper.FreeConsole();
            Shutdown(0);
        }
    }
}

И теперь, если вы запустите его из командной строки, вы должны увидеть "Пуск" и "Остановить", записанные на консоль.

Ответ 4

В свойствах проекта вы можете изменить тип проекта из приложения Windows в приложение консоли, а AFAIK - единственное отличие в том, что вы получаете консоль для использования в течение всей жизни приложения. Я даже не пробовал это с WPF.

Если вы хотите открыть и закрыть консоль, вы должны использовать Alloc/FreeConsole, но обычно проще изменить тип проекта.