如何从控制台应用程序(如Far)接收彩色输出?

5

我有一些代码:

cmdProcess = new Process();
var procStartInfo = new ProcessStartInfo( "cmd", "/k "C:\\Program Files (x86)\\Far Manager\\Far.exe"" );

procStartInfo.RedirectStandardOutput = true;
procStartInfo.RedirectStandardInput = true;
procStartInfo.UseShellExecute = false;

procStartInfo.CreateNoWindow = true;
cmdProcess.OutputDataReceived += ( s, e ) => {
       callbackFn(e.Data + "\n");
};
cmdProcess.StartInfo = procStartInfo;

cmdProcess.Start();
cmdProcess.BeginOutputReadLine();

但是,使用此代码我只能启动进程并获得一些内容,但不完整而且没有颜色。我还尝试了 ReceiveConsoleOutput 函数,但是我只收到空白缓冲区。 使用 WinAPI,我只能启动控制台,其他什么也不能做 - 我对它不太理解。但我不反对 WinAPI 的示例,因为我认为我的问题可能可以通过它来解决。 如果有人能帮助我,我将不胜感激。
P.S. 对于糟糕的英语,我深感抱歉。

我怀疑你能否读取输出的颜色。 - David Heffernan
在这些项目中,它们都有一定的可能性: 链接 链接 - Undermuz
我没有看到任何证据表明这些项目创建子进程,然后捕获输出的每个字符的颜色。无论如何,如果我错了,那么我猜你可以从这些项目的代码中读取。 - David Heffernan
@DavidHeffernan 我也看到了这个Python示例,它可以做到这一点,但是这个示例中的代码并不太容易理解。而且在那个Python示例中也使用了WinAPI。 - Undermuz
@DavidHeffernan ConEmu和控制台项目有很多C++代码,我无法理解。如果我能够理解,我就不会问了。 - Undermuz
我想现在是学习的时候了。如果你无法理解需要编写的代码,那么实际上你就是在要求我们替你完成工作。这不是本网站的宗旨。无论如何,我仍然没有看到任何证据表明你链接的任何程序正在捕获输出并重新显示它。我认为它们是托管实际进程。 - David Heffernan
2个回答

5
你谈论了两件不同的事情。ConEmu和原始控制台都支持颜色,但是这是通过控制台缓冲区API这里有一个完整的C#库)实现的。控制台不仅支持着色,还支持光标和鼠标;然而,它们都与标准输出无关。

但是,如果您想在标准输出中接收颜色信息,您可以使用ANSI转义序列,这是终端通信中的标准(也用于ANSI图形艺术),支持着色和光标定位,并且可以编码为字符流。

但是,如果您调用的进程不转储ANSI序列(cmd不会这样做),则不会接收到任何颜色信息。


那么,你能解释一下如何在C#中使用控制台缓冲区API吗?或者请给我几个附有可行教程/示例的链接呢? - Undermuz
我已经编辑了答案,添加了C#库的链接。 - György Kőszeg
这个库用于创建控制台应用程序,比C#标准更低级,但不适用于已经创建控制台进程的数据,还是感谢您的回答! - Undermuz

0

使用ReadConsoleOutput代码不像应该的那样工作,因为它没有调用FreeConsole函数 - 用于从其自身的控制台应用程序中豁免

证明

        public static IEnumerable<string> ReadFromBuffer(IntPtr hOutput, short x, short y, short width, short height)
        {
        IntPtr buffer = Marshal.AllocHGlobal(width * height * Marshal.SizeOf(typeof(CHAR_INFO)));
        if (buffer == null)
            throw new OutOfMemoryException();

        try
        {
            COORD coord = new COORD();
            SMALL_RECT rc = new SMALL_RECT();
            rc.Left = x;
            rc.Top = y;
            rc.Right = (short)(x + width - 1);
            rc.Bottom = (short)(y + height - 1);

            COORD size = new COORD();
            size.X = width;
            size.Y = height;

            if (!ReadConsoleOutput(hOutput, buffer, size, coord, ref rc))
            {
                // 'Not enough storage is available to process this command' may be raised for buffer size > 64K (see ReadConsoleOutput doc.)
                throw new Win32Exception(Marshal.GetLastWin32Error());
            }

            IntPtr ptr = buffer;
            for (int h = 0; h < height; h++)
            {
                StringBuilder sb = new StringBuilder();
                for (int w = 0; w < width; w++)
                {
                    CHAR_INFO ci = (CHAR_INFO)Marshal.PtrToStructure(ptr, typeof(CHAR_INFO));
                    char[] chars = Console.OutputEncoding.GetChars(ci.charData);
                    sb.Append(chars[0]);
                    ptr += Marshal.SizeOf(typeof(CHAR_INFO));
                }
                yield return sb.ToString();
            }
        }
        finally
        {
            Marshal.FreeHGlobal(buffer);
        }
    }

...

        Process proc = new Process();
        proc.StartInfo.FileName = "cmd.exe";
        proc.StartInfo.Arguments = command; 
        //proc.StartInfo.UseShellExecute = false;
        proc.Start();

        Thread.Sleep(1000);

        bool resultFree = ConsoleApi.FreeConsole();

        if (resultFree)
        {
            Debug.WriteLine("FreeConsole: {0}", true);
        }
        else
        {
            Debug.WriteLine("FreeConsole: {0}", false);
        }

        Debug.WriteLine("Process ID: {0}", Convert.ToUInt32(proc.Id));

        bool result = ConsoleApi.AttachConsole( Convert.ToUInt32(proc.Id) );

        Debug.WriteLine("AttachConsole: {0}", result);

        IntPtr _consoleH = ConsoleApi.GetStdHandle(ConsoleApi.STD_OUTPUT_HANDLE);

        ConsoleApi.CONSOLE_SCREEN_BUFFER_INFO _bufferInfo;

        bool getInfo = ConsoleApi.GetConsoleScreenBufferInfo(_consoleH, out _bufferInfo);

        if (getInfo)
        {
            Debug.WriteLine("GetConsoleScreenBufferInfo: {0}x{1}", _bufferInfo.dwSize.X, _bufferInfo.dwSize.Y);
        }
        else
        {
            Debug.WriteLine("GetConsoleScreenBufferInfo: {0}", false);
        }

        short _widthConsole = _bufferInfo.dwSize.X;
        short _heightConsole = _bufferInfo.dwSize.Y;

        IEnumerable<string> rows = ConsoleApi.ReadFromBuffer(_consoleH, 0, 0, _widthConsole,_heightConsole);

        foreach (string row in rows)
        {
            Debug.WriteLine(row);
        }

读取颜色:

                    [DllImport("kernel32.dll")]
                    public static extern bool ReadConsoleOutputAttribute(IntPtr hConsoleOutput,
       [Out] ushort[] lpAttribute, uint nLength, COORD dwReadCoord,
       out uint lpNumberOfAttrsRead);

...

                    string[] colors = new string[]{
                        "black",
                        "darkblue",
                        "darkgreen",
                        "darkcyan",
                        "darkred", 
                        "darkmagenta",
                        "brown",
                        "white",
                        "lightgrey",
                        "blue",
                        "green",
                        "cyan",
                        "red",
                        "magenta",
                        "yellow",
                        "white"
                    };

                for (int i = 0; i < _rowsList.Length; i++)
                {

                    ushort[] lpAttr = new ushort[_widthConsole];

                    ConsoleApi.COORD _coordReadAttr = new ConsoleApi.COORD(0,(short)i);

                    uint lpReadALast;

                    bool readAttr = ConsoleApi.ReadConsoleOutputAttribute(_consoleH, lpAttr, Convert.ToUInt32(_widthConsole), _coordReadAttr, out lpReadALast);

                    string[] attrText = new string[_widthConsole];

                    for (int _attr = 0; _attr < lpAttr.Length; _attr++)
                    {
                        string _text = colors[lpAttr[_attr] & 0x0F];
                        string _background = colors[((lpAttr[_attr] & 0xF0) >> 4) & 0x0F];

                        attrText[_attr] = _text + "|" + _background;
                    }
                }

可以使用以下方法获取背景颜色和文本:ReadConsoleOutputAttribute。 - Undermuz
@David Heffernan 是的 - Undermuz
真的吗?你在哪里读取外部进程输出的文本颜色? - David Heffernan
啊,你刚刚编辑添加了缺失的信息。 - David Heffernan
1
你能把整个代码发布到Github的gist上吗?这将不胜感激。 - z3nth10n
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接