当运行一个C#程序,所有的消息都输出到标准输出,但是标准错误不包含任何内容

When run a program in C#, all the messages go to the standard output, but the standard error contains nothing

我的问题与 不同。显然我已经调用了"BeginErrorReadLine"方法(我在下面的代码中标记了它)。

我想解析Handle

产生的结果

命令行

当运行在命令行环境下,它会输出类似:

> handle64 -p [PID]

 

Nthandle v4.11 - Handle viewer

Copyright (C) 1997-2017 Mark Russinovich

Sysinternals - www.sysinternals.com

 

  10: File     C:\Windows

  1C: File     C:\Windows\SysWOW64

[PID] 是任何 运行ning 进程 ID

输出是分开的。

前 5 行(包括空行)转到标准错误,最后 2 行转到标准输出。

所以我可以通过重定向去除 header:

> handle64 -p [PID] 2>nul

  10: File     C:\Windows

  1C: File     C:\Windows\SysWOW64


Winform应用程序

然后我尝试在 C# 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();

然后我发现一切都进入了标准输出。


问题

好的,我可以通过代码将 header 和 body 分开。

问题是为什么 the program 的输出在 2 个环境中表现不同?

我能否使 winform 应用程序中的结果与命令行中的结果一样?


更新

对于 Damien 的评论,我尝试通过 'cmd' 运行 the program,不幸的是我得到了相同的结果:

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

...

在输出中window:

Output =>

Output => Nthandle v4.11 - Handle viewer

Output => Copyright (C) 1997-2017 Mark Russinovich

Output => Sysinternals - www.sysinternals.com

Output =>

Output =>   10: File     C:\Windows

Output =>   1C: File     C:\Windows\SysWOW64

Error =>

这只是一个示例,用于说明我在评论中提到的问题。这不是解决方法,因为我不相信有一种简单的方法可以解决这个问题。我在我的 scratch 程序中创建了 Main(称为 PlayAreaCSCon)。如果它在没有参数的情况下被调用,它的行为方式类似于我怀疑 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 或任何道德上等效的函数,它也可以检测到它没有连接到控制台并表现出不同的行为。让它获得控制台 window 的唯一方法是将 CreateNoWindow 设置为 false,我猜你可能不想这样做。

由于 Handle64 是封闭源代码,因此很难确认这是它正在执行的特定检查。 调用 方面对此没有重要的修复。

不是你问题的答案,只是一个实现你正在尝试做的事情的建议(即只在 Winform 应用程序中获取句柄信息):

句柄工具有-nobanner开关,您可以使用它来跳过版权消息信息。

handle64.exe -pid 11624 -nobanner

正如达米安所说: CreateNoWindow = false,

让它创建 window 并立即隐藏它。我们可以在屏幕外创建它,但它仍会显示在任务栏上。

注意:此代码可能并不比让 Window 自然出现和消失更好。

在 class 的顶部添加:

[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's to hide window (May still flicker)
ShowWindow(windowHandle,0);
// ...

我无法测试这个,因为我现在只有 运行 Linux。
如果异常没有触发,您可能会看到 window 的闪烁,但您应该有正确的输出。

我知道的另一种方法是将处理程序插入 Win32 消息泵并响应特定进程,告诉它需要知道什么才能认为它具有适当的 window,而实际上它没有。我不会公开 post 与此技术相关的任何代码。任何错误都会导致 Windows 变得不稳定。

我对你的代码做了一些修改:

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();