При запуске программы на С# все сообщения поступают на стандартный вывод, но стандартная ошибка не содержит ничего

Мой вопрос отличается от выбранного. Очевидно, что я назвал метод BeginErrorReadLine (я отмечаю его в коде ниже).

Я хочу проанализировать результат, созданный Handle


Командная строка

При запуске в среде командной строки он выводит что-то вроде:

> handle64 -p [PID]

Nthandle v4.11 - просмотрщик ручек

Copyright (C) 1997-2017 Марк Руссинович

Sysinternals - www.sysinternals.com

10: Файл C:\Windows

1C: Файл C:\Windows\SysWOW64

[PID] - любой идентификатор текущего процесса

Выход разделен.

Первые 5 строк (включая пустые строки) переходят к стандартной ошибке, последние 2 строки идут на стандартный вывод.

Поэтому я могу удалить заголовок, перенаправляя:

> handle64 -p [PID] 2> nul

10: Файл C:\Windows

1C: Файл C:\Windows\SysWOW64


Приложение Winform

Затем я пытаюсь реализовать эту команду в приложении winform С#:

Stream streamOut, streamErr;

var p = Process.Start(new ProcessStartInfo
{
    FileName = "handle64.exe",
    Arguments = "-p [PID]",
    CreateNoWindow = true,
    UseShellExecute = false,
    RedirectStandardOutput = true,
    RedirectStandardError = true,
});

p.OutputDataReceived += (sender, e) =>
{
    streamOut.Write("Output => " + e.Data);
};

p.ErrorDataReceived += (sender, e) =>
{
    streamErr.Write("Error => " + e.Data);
};

p.BeginOutputReadLine();
p.BeginErrorReadLine(); // !!!
p.WaitForExit();

Затем я нахожу, что все идет на стандартный вывод.


Вопрос

Хорошо, я могу отделить заголовок и тело кодом.

Вопрос в том, почему выход программы ведет себя по-разному между двумя средами?

Могу ли я заставить результат в приложении winform вести себя как в командной строке?


Обновить

Для комментария Damien, я пытаюсь запустить программу через 'cmd', к сожалению, я получаю тот же результат:

var p = Process.Start(new ProcessStartInfo
{
    FileName = "cmd",
    Arguments = "/C handle64.exe -p [PID]",
    CreateNoWindow = true,
    UseShellExecute = false,
    RedirectStandardOutput = true,
    RedirectStandardError = true,
});

...

В окне вывода:

Выход =>

Выход => Nthandle v4.11 - просмотрщик ручек

Выход => Авторское право (C) 1997-2017 гг. Марк Руссинович

Выход => Sysinternals - www.sysinternals.com

Выход =>

Вывод => 10: Файл C:\Windows

Output => 1C: Файл C:\Windows\SysWOW64

Ошибка =>

Ответы

Ответ 1

Это всего лишь образец, иллюстрирующий проблему, о которой я упоминал в своих комментариях. Это не проблема, так как я не верю, что есть тривиальный способ исправить это. Я создал Main в моей программе с нуля (называется PlayAreaCSCon). Если он вызвал без параметров, он действует так же, как я подозреваю, Handle64.exe делает Handle64.exe. Когда вызывается с параметром, он содержит код, похожий на ваш, но затем запускает собственную копию без параметров:

using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;

namespace PlayAreaCSCon
{
    class Program
    {
        [DllImport("kernel32.dll")]
        static extern IntPtr GetConsoleWindow();
        static void Main(string[] args)
        {
            if (args.Length == 0)
            {
                Console.Out.WriteLine("Hello");
                if (GetConsoleWindow() == IntPtr.Zero)
                {
                    Console.Out.WriteLine("No Console window");
                }
                else
                {
                    Console.Error.WriteLine("We have a console window");
                }
            }
            else
            {
                Process p = Process.Start(new ProcessStartInfo
                {
                    FileName = "PlayAreaCSCon.exe",
                    Arguments = "",
                    CreateNoWindow = true,
                    UseShellExecute = false,
                    RedirectStandardOutput = true,
                    RedirectStandardError = true,
                });

                TextWriter streamOut = Console.Out;
                TextWriter streamErr = Console.Error;
                p.OutputDataReceived += (sender, e) =>
                {
                    streamOut.WriteLine("Output => " + e.Data);
                };

                p.ErrorDataReceived += (sender, e) =>
                {
                    streamErr.WriteLine("Error => " + e.Data);
                };

                p.BeginOutputReadLine();
                p.BeginErrorReadLine(); // !!!
                p.WaitForExit();
            }
        }
    }
}

В командной строке у меня есть следующий сеанс:

C:\Dev\PlayAreaCSCon\PlayAreaCSCon\bin\Debug>PlayAreaCSCon.exe
Hello
We have a console window

C:\Dev\PlayAreaCSCon\PlayAreaCSCon\bin\Debug>PlayAreaCSCon.exe a
Error =>
Output => Hello
Output => No Console window
Output =>

Поэтому даже здесь, если Handle64.exe вызывает GetConsoleWindow или любую морально эквивалентную функцию, он может обнаружить, что он не подключен к консоли и отличается поведением. Единственный способ, которым вы могли бы позволить ему получить консольное окно, - установить значение CreateNoWindow на false, которое, я думаю, вам вряд ли захочется.

Поскольку Handle64 является закрытым источником, трудно подтвердить, что это конкретная проверка, которую она выполняет. Там нет нетривиального исправления от вызывающей стороны.

Ответ 2

Не ответ на ваш вопрос, а просто предложение достичь того, что вы пытаетесь сделать (т.е. Получить информацию о дескрипторе в приложении Winform):

ручка имеет переключатель -nobanner, вы можете использовать это, чтобы пропускать информацию об авторских сообщениях.

handle64.exe -pid 11624 -nobanner

Ответ 3

Как заявил Дэмиен: CreateNoWindow = false,

Пусть это создаст окно, которое немедленно скроет его. Мы могли бы создать его вне экрана, но он все равно будет отображаться на панели задач.

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

В верхней части класса добавьте:

[DllImport("user32.dll")]
static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);

Тогда ваш код будет выглядеть следующим образом:

var p = Process.Start(new ProcessStartInfo
{
    FileName = "cmd",
    Arguments = "/C handle64.exe -p [PID]",
    CreateNoWindow = false,
    UseShellExecute = false,
    RedirectStandardOutput = true,
    RedirectStandardError = true,
});
p.WaitForInputIdle();
IntPtr windowHandle = p.MainWindowHandle;
if(windowHandle == 0) throw new Exception("This did not work");
// use win32 API to hide window (May still flicker)
ShowWindow(windowHandle,0);
// ...

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

Другой способ, который я знаю, заключается в том, чтобы вставлять обработчики в насос сообщений Win32 и отвечать на конкретный процесс, сообщая ему, что ему нужно знать, чтобы думать, что у него есть подходящее окно, когда оно этого не делает. Я не буду публично публиковать какой-либо код, связанный с этой техникой. Любые ошибки приведут к тому, что Windows станет нестабильной.

Ответ 4

Я внесла некоторые изменения в ваш код:

Stream streamOut, streamErr;

var p = Process.Start(new ProcessStartInfo
{
    FileName = "handle64.exe",
    Arguments = "-p [PID]",
    CreateNoWindow = true,
    UseShellExecute = false,
    RedirectStandardOutput = true,
    RedirectStandardInput = true, // even if no writing to std::in, still need this
    RedirectStandardError = true,
});

p.OutputDataReceived += (sender, e) =>
{
    streamOut.Write("Output => " + e.Data);
};
p.BeginOutputReadLine();

p.ErrorDataReceived += (sender, e) =>
{
    streamErr.Write("Error => " + e.Data);
};

p.BeginErrorReadLine(); 

p.WaitForExit();
p.StandardInput.Close(); // call this before WaitForExit
p.WaitForExit();