Search code examples
c#.net-corepinvoke

Invalid CONSOLE_SCREEN_BUFFER_INFOEX; GetConsoleScreenBufferInfoEx (kernel32)


GetConsoleScreenBufferInfoEx invalid return value

I am trying to change the console's color palette. To get this done I first need to Get my ConsoleScreenBufferInfoEx and then Set it. The problem is that I can't even get a valid ConsoleScreenBufferInfoEx from the STD_OUTPUT_HANDLE. The code below throws this error message:
System.ArgumentException: 'Value does not fall within the expected range.'
The handle is valid and yet I get this error. I have quadruple-checked every data type and related pinvoke entry - everything is looking good to me. There is no sample code for GetConsoleScreenBufferInfoEx and I haven't been able to find a working solution yet.

My sources:

Example App (.NET Core 3.1):
For this code to work, the project's build properties must allow unsafe code.
[Properties -> Build -> Allow unsafe code]

using Microsoft.Win32.SafeHandles;
using System;
using System.Drawing;
using System.Runtime.InteropServices;

namespace ScreenBufferInfoExample
{
    class Program
    {
        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern SafeFileHandle GetStdHandle(int nStdHandle);

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern unsafe bool GetConsoleScreenBufferInfoEx(SafeFileHandle hConsoleOutput,
                                                                      out CONSOLE_SCREEN_BUFFER_INFO_EX ConsoleScreenBufferInfo);

        static void Main()
        {
            SafeFileHandle stdOut = GetStdHandle(-11);
            if(stdOut.IsInvalid)
                Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
            CONSOLE_SCREEN_BUFFER_INFO_EX info = new CONSOLE_SCREEN_BUFFER_INFO_EX();
            info.cbSize = (uint)Marshal.SizeOf(info);
            if(!GetConsoleScreenBufferInfoEx(stdOut, out info)) {
                Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());// <- this gets thrown
                // System.ArgumentException: 'Value does not fall within the expected range.'
            }

            Console.ReadKey(true);
        }
    }

    [StructLayout(LayoutKind.Sequential)]
    struct CONSOLE_SCREEN_BUFFER_INFO_EX
    {
        public uint cbSize;
        public COORD dwSize;
        public COORD dwCursorPosition;
        public ushort wAttributes;
        public SMALL_RECT srWindow;
        public COORD dwMaximumWindowSize;

        public ushort wPopupAttributes;
        public bool bFullscreenSupported;

        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
        public COLORREF[] ColorTable;
    }

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

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

    [StructLayout(LayoutKind.Sequential)]
    struct COLORREF
    {
        public uint ColorDWORD;

        public COLORREF(int r, int g, int b)
            : this(Color.FromArgb(r, g, b)) { }
        public COLORREF(Color color)
        {
            ColorDWORD = (uint)color.R
                         + (((uint)color.G) << 8)
                         + (((uint)color.B) << 16);
        }

        public Color GetColor()
        {
            return Color.FromArgb((int)(0x000000FFU & ColorDWORD),
                                  (int)(0x0000FF00U & ColorDWORD) >> 8,
                                  (int)(0x00FF0000U & ColorDWORD) >> 16);
        }

        public void SetColor(Color color)
        {
            ColorDWORD = (uint)color.R
                         + (((uint)color.G) << 8)
                         + (((uint)color.B) << 16);
        }
    }
}

Solution

  • pinvoke.net is often useful, but not always right.

    As you need to pass information in and out of the GetConsoleScreenBufferInfoEx method, the parameter cannot be out, but needs to be ref.

    So the correct declaration would be

    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern unsafe bool GetConsoleScreenBufferInfoEx(SafeFileHandle hConsoleOutput, 
        ref CONSOLE_SCREEN_BUFFER_INFO_EX ConsoleScreenBufferInfo);
    

    called as GetConsoleScreenBufferInfoEx(stdOut, ref info).

    (I just corrected the page on pinvoke.net accordingly)