16

I need to read text from a particular location in the console, say 5,5.

If I were to need to write to this location, it would simply be:

Console.SetCursorPosition(5, 5);
Console.Write("My text");

Is there any way i can read in a similar way?

Just to clarify: I don't want to stop to take an input from the user, there's a chance even that the input won't be from the user, but something previously printed out. I literally want some sort of: Console.GetCharAtLocation(5,5) or something similar.

Joel
  • 1,580
  • 4
  • 20
  • 33
  • Is this console application something you can modify? – Cypher Sep 10 '12 at 16:22
  • Yes, i am developing the console application, if that's what you're asking. – Joel Sep 10 '12 at 16:24
  • Do you need to read from the actual window or from the buffer? – Simon Mourier Sep 10 '12 at 17:22
  • Usually, the need to do this is rare. If the user is providing input that you need to reference later, you should probably be storing that input in memory somewhere. Maybe if you can be more specific in a new question about your problem someone can offer an alternative solution. – Cypher Sep 10 '12 at 17:35
  • Guys, I am trying to read data from a console application that is hosted inside a WPF application. So far I am able to read data from Console Application outside wpf by using ReadConsoleOutput but it only reads from ActiveConsole Buffer. How can i read from console that is hosted inside WPF application. Please help! – Faran Saleem Jun 27 '19 at 13:38

6 Answers6

17

Here is a C# code utility that can read what's currently in the Console buffer (not the window, the buffer):

Sample usage:

class Program
{
    static void Main(string[] args)
    {
        // read 10 lines from the top of the console buffer
        foreach (string line in ConsoleReader.ReadFromBuffer(0, 0, (short)Console.BufferWidth, 10))
        {
            Console.Write(line);
        }
    }
}

Utility:

public class ConsoleReader
{
    public static IEnumerable<string> ReadFromBuffer(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;

            const int STD_OUTPUT_HANDLE = -11;
            if (!ReadConsoleOutput(GetStdHandle(STD_OUTPUT_HANDLE), 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);
        }
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct CHAR_INFO
    {
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
        public byte[] charData;
        public short attributes;
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct COORD
    {
        public short X;
        public short Y;
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct SMALL_RECT
    {
        public short Left;
        public short Top;
        public short Right;
        public short Bottom;
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct CONSOLE_SCREEN_BUFFER_INFO
    {
        public COORD dwSize;
        public COORD dwCursorPosition;
        public short wAttributes;
        public SMALL_RECT srWindow;
        public COORD dwMaximumWindowSize;
    }

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern bool ReadConsoleOutput(IntPtr hConsoleOutput, IntPtr lpBuffer, COORD dwBufferSize, COORD dwBufferCoord, ref SMALL_RECT lpReadRegion);

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern IntPtr GetStdHandle(int nStdHandle);
}
Simon Mourier
  • 132,049
  • 21
  • 248
  • 298
  • 1
    I've tried this, but got an error: "System.IO.IOException: 'The handle is invalid." – András Geiszl May 14 '19 at 15:54
  • 1
    @AndrásGeiszl - I've justed tested it and it still works fine, with .NET 4 to 4.7, or .NET Core, win10 x64 or x32, with cmd.exe or other console (like ConEmu). – Simon Mourier May 14 '19 at 16:30
  • Old thread, but still it might help someone. Found your and other articles about this and ended putting together a small library for just this. Put in on https://github.com/jrnker/Proxmea.ConsoleHelper if anyone else would like to get a jumpstart in doing this – Chris.J Nov 20 '20 at 12:25
12

A simplified demo that works in Windows 10 for reading a single character from the specified (X, Y) position on the screen. Tested with .NET 4.7.2.

First, here's a line of code which populates the Console with a demo grid. Note that it should render in the top left corner of your screen in order for the demo to work.

static void Populate_Console()
{
    Console.Clear();
    Console.Write(@"
 ┌───────┐
1│C D E F│
2│G H I J│
3│K L M N│
4│O P Q R│
 └───────┘
  2 4 6 8          ".Trim());
}

It should look like this:

enter image description here

Now let's read some characters back. To start you'll need the native console handle for stdout. Here's the P/Invoke method for getting it from Win32:

[DllImport("kernel32", SetLastError = true)]
static extern IntPtr GetStdHandle(int num);

Now for the cool part; this seems to be the only answer on this page so far which uses the ReadConsoleOutputCharacter Win32 function. Although it doesn't let you get the character color attributes, this approach does save all the trouble of dealing with copy rectangles and having to use CreateConsoleScreenBuffer to allocate screen buffers and copy between them.

There are separate Ansi and Unicode versions, and you need to call the proper one depending on the code page that's active in the Console window. I show both P/Invoke signatures here, but for simplicity, in the example I'll just continue with the Ansi version:

    [DllImport("kernel32", SetLastError = true, CharSet = CharSet.Ansi)]
    [return: MarshalAs(UnmanagedType.Bool)] //   ̲┌──────────────────^
    static extern bool ReadConsoleOutputCharacterA(
        IntPtr hStdout,   // result of 'GetStdHandle(-11)'
        out byte ch,      // A̲N̲S̲I̲ character result
        uint c_in,        // (set to '1')
        uint coord_XY,    // screen location to read, X:loword, Y:hiword
        out uint c_out);  // (unwanted, discard)

    [DllImport("kernel32", SetLastError = true, CharSet = CharSet.Unicode)]
    [return: MarshalAs(UnmanagedType.Bool)] //   ̲┌───────────────────^
    static extern bool ReadConsoleOutputCharacterW(
        IntPtr hStdout,   // result of 'GetStdHandle(-11)'
        out Char ch,      // U̲n̲i̲c̲o̲d̲e̲ character result
        uint c_in,        // (set to '1')
        uint coord_XY,    // screen location to read, X:loword, Y:hiword
        out uint c_out);  // (unwanted, discard)

You may notice I've stripped down the marshaling on these to the bare minimum needed for the purposes of my example code, which is designed to only fetch one character at a time. Therefore, you will probably find that c_in must always be 1, due to the managed pointer declarations ‘out byte ch’ and ‘out Char ch’.

That's really all you need; calling the appropriate P/Invoke function as described above is mostly self-explanatory if you limit yourself to reading a single character. To show this with a trivial example, I'll finish with the cute demo program, that reads four characters back from the Console, along a diagonal of the grid we drew above.

static void Windows_Console_Readback()
{
    var stdout = GetStdHandle(-11);

    for (uint coord, y = 1; y <= 4; y++)
    {
        coord = (5 - y) * 2;        // loword  <-- X coord to read
        coord |= y << 16;           // hiword  <-- Y coord to read

        if (!ReadConsoleOutputCharacterA(
                stdout,
                out byte chAnsi,    // result: single ANSI char
                1,                  // # of chars to read
                coord,              // (X,Y) screen location to read (see above)
                out _))             // result: actual # of chars (unwanted)
            throw new Win32Exception();

        Console.Write(" " + (Char)chAnsi + " ");
    }
}

And there you have it...

enter image description here

Glenn Slayden
  • 17,543
  • 3
  • 114
  • 108
  • I am getting error at out byte chAnsi ==> Invalid expression term byte. I am using Visual Studio 2015 .Net Framework 4.6.2 – Faran Saleem Jun 26 '19 at 08:02
  • @FaranSaleem The example uses C# 7 features. You can declare the ‘out’ variables separately, instead of inline, or upgrade to VS2017 or VS2019. – Glenn Slayden Jun 26 '19 at 08:16
  • Okay got it. I also have a console app embedded in another WPF Application. I am passing it the handle but it says invalid handle. With -11 I am only able to write on current console window. How can I fix this? – Faran Saleem Jun 26 '19 at 12:57
  • @FaranSaleem If you are spawning the console as a new process, you need to use the **stdio** handles explicitly requested and captured through the `ProcessStartInfo` options. This, in turn, will also only work if the `ProcessStartInfo.UseShellExecute` option is set to `false`. – Glenn Slayden Jun 27 '19 at 03:26
  • Ummm can you show me an example please? Basically, i do not have much about it. I am using a wpf window and inside it I am hosting Console application and if i use any other handle other than std_output_handle to -11 it gives me an exception saying invalid handle. SO, Can you please help me with an example? – Faran Saleem Jun 27 '19 at 04:59
  • @FaranSaleem In a WPF application, the standard IO handles are [not established](/a/8711036/147511). Is the WPF app spawning the console process itself (from within WPF)? If so, you need to specify whether new handles should be generated for the console process, and whether you want to hook them. – Glenn Slayden Jun 27 '19 at 05:08
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/195605/discussion-between-faran-saleem-and-glenn-slayden). – Faran Saleem Jun 27 '19 at 05:13
9

This functionality doesn't exist. It's theoretically possible for you to override the input and output streams on the console to keep your own copy of the console buffer that you could read from, but it would be non-trivial (and probably couldn't support all of the edge cases such as an external program hooking into your console and reading/writing to it).

Servy
  • 202,030
  • 26
  • 332
  • 449
  • That is just a tad annoying. Oh well, disappointing answer (not your fault of course), but i guess not every rainbow has a pot of gold at the end. Thanks heaps for the help, i think i'll start considering other options now :P – Joel Sep 10 '12 at 17:26
1

Forget about it, too much trouble, you could read from buffer and get all the current console output, but that would be too much.

My suggestion is to create a ConsoleWriter delegation, you choose how, could be a class or just an static method, and this writer would keep the last line in a property so each time you would Console.WriteLine something you just call your delegation, with your implementation and at the end it calls Console.WriteLine.

0

What about:

class Program {
    static void Main( string[ ] args ) {
        CustomizedConsole.WriteLine( "Lorem Ipsum" ); //Lorem Ipsum
        Console.WriteLine( CustomizedConsole.ReadContent( 6, 5 ) ); //Ipsum
        Console.WriteLine( CustomizedConsole.GetCharAtLocation( 0, 0 ) ); //L
    }
}

static class CustomizedConsole {
    private static List<char> buffer = new List<char>();
    private static int lineCharCount = 0;

    public static void Write(string s){
        lineCharCount += s.Length;
        buffer.AddRange( s );
        Console.Write( s );
    }

    public static void WriteLine(string s ) {
        for ( int i = 0; i < Console.BufferWidth - lineCharCount - s.Length; i++ )
            s += " ";

        buffer.AddRange( s );
        Console.WriteLine( s );
        lineCharCount = 0;
    }

    public static string ReadContent( int index, int count ) {
        return new String(buffer.Skip( index ).Take( count ).ToArray());
    }

    public static char GetCharAtLocation( int x, int y ) {
        return buffer[ Console.BufferHeight * x + y ];
    }
}

EDIT :

As said the others this is just a trivial case where there are a lot of other things to improve. But I wrote this only as a starting point.

Omar
  • 16,329
  • 10
  • 48
  • 66
  • What happens when the CustomizedConsole buffer gets larger than the actual Console's buffer? – Cypher Sep 10 '12 at 16:53
  • 1
    You don't handle cases where the cursor is manually moved around, cases where the user is moving the cursor, backspace characters, reading input in general, you don't account for the fact that there are null characters throughout the buffer (your code gets out of bounds errors trying to read where you haven't read). etc. It really only covers the most trivial case. – Servy Sep 10 '12 at 17:26
0

As @Servy has stated, there isn't any built-in functionality (that I know of, or can find) that can do what you want. However, there is a work-around (it's a bit of a hack, but it worked).

You can create your own buffer in memory, or on disk. Whenever you output to the Console, also output to your buffer. You can then use your buffer to read from in ways that you couldn't with the Console.

There are two ways to buffer: on disk or in memory. You can use the Console.BufferWidth and Console.BufferHeight properties to find out your buffer size. I found it simpler to do this in memory using an array of strings (each string was a line of output, and there were a number of strings in the array equal to the BufferHeight, if I remember correctly). A colleague ended up doing the same thing on disk.

You'll want to create a method to replace Console.Write and Console.WriteLine, so that you can write to both buffers at once. Something like:

public void MyWrite( string output ) {
    Console.Write( output );
    Array.Write( output );  // obvious pseudo-code
}

I found it helpful to wrap a Class around the array and implement methods to support it ... you could then implement your GetCharAtLocation( int i, int j ) method, as well as any other functionality you need there.

Cypher
  • 2,608
  • 5
  • 27
  • 41
  • As I've mentioned, you need to be able to handle cases where the cursor is moved (by the user and in code), you need to be able to handle user input in general, the buffer being resized, the existence of null characters throughout the buffer, potential race conditions in asynchronous programming (if there are two writes at about the same time will your internal buffer always be the same as the console buffer?) etc. This is why I have said that this is a highly non-trivial problem. – Servy Sep 10 '12 at 17:28
  • I think you mean "**if** you need to be able to handles cases where..." ;-) But yes, if that's the case this becomes non-trivial to say the least. A lot of these conditions are solvable and if the code I wrote that handles many of these problems weren't private property, I would post it. I do agree that there's probably a better solution out there for whatever situation he's dealing with. – Cypher Sep 10 '12 at 17:33
  • Well, just reading the OP's post he's clearly setting the cursor programatically in code, and he's dealing with user input as well as programmatic output (users also will frequently be moving the cursor when they are providing input; not doing so would be a significant annoyance). It's not clear if the app is single threaded or not, or if there will be enough text written to conceivably fill the buffer, but that seems to be a likely enough case to be worth coding for. – Servy Sep 10 '12 at 17:37