Reputation: 1060
I have been having an issue when passing in unicode codes into my double buffer. Now I have tried stepping through the code, and I will point out where the unicode character is still correct. (Look at the draw method)
I think the issue lies somewhere in the final "print" method.
To give a quick overview of what happens:
- The buffer is created, allowing the draw function to insert characters into the buffer
- The print function will send the buffer to the console so that it is displayed
From the example I provide, I pass in the unicode character '\u2580', but it prints the ascii character '80'.
Using this link:
http://www.kreativekorp.com/charset/font.php?font=Consolas
I can correctly print out Basic Latin and Latin 1, but nothing else.
After doing further research, I do not believe the issue lies with the console code page. In addition to doing testing switching code pages (which had no effect), I can still do Console.Out.WriteLine("\u2580") and get the correct unicode character.
To provide some additional information... below is the final function that is called (as a result of the print method)
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool WriteConsoleOutput(
SafeFileHandle hConsoleOutput,
CharInfo[] lpBuffer,
Coord dwBufferSize,
Coord dwBufferCoord,
ref SmallRect lpWriteRegion);
The documentation for this can be found here:
https://msdn.microsoft.com/en-us/library/windows/desktop/ms687404(v=vs.85).aspx
Now I can guarantee that when the lpbuffer is passed in, it has a multidimensional array, of which the first value (in this case) is composed of the attribute, and the character - which IS still correct at this time, checked using debugger.
I have provided the full code to allow it to be run.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
using System.IO;
using System.Text;
namespace DoubleBuffer
{
///<summary>
///This class allows for a double buffer in Visual C# cmd promt.
///The buffer is persistent between frames.
///</summary>
class buffer
{
private int width;
private int height;
private int windowWidth;
private int windowHeight;
private SafeFileHandle h;
private CharInfo[] buf;
private SmallRect rect;
[DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern SafeFileHandle CreateFile(
string fileName,
[MarshalAs(UnmanagedType.U4)] uint fileAccess,
[MarshalAs(UnmanagedType.U4)] uint fileShare,
IntPtr securityAttributes,
[MarshalAs(UnmanagedType.U4)] FileMode creationDisposition,
[MarshalAs(UnmanagedType.U4)] int flags,
IntPtr template);
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern bool WriteConsoleOutput(
SafeFileHandle hConsoleOutput,
CharInfo[] lpBuffer,
Coord dwBufferSize,
Coord dwBufferCoord,
ref SmallRect lpWriteRegion);
[StructLayout(LayoutKind.Sequential)]
public struct Coord
{
private short X;
private short Y;
public Coord(short X, short Y)
{
this.X = X;
this.Y = Y;
}
};
[StructLayout(LayoutKind.Explicit)]
public struct CharUnion
{
[FieldOffset(0)]
public char UnicodeChar;
[FieldOffset(0)]
public byte AsciiChar;
}
[StructLayout(LayoutKind.Explicit)]
public struct CharInfo
{
[FieldOffset(0)]
public CharUnion Char;
[FieldOffset(2)]
public short Attributes;
}
[StructLayout(LayoutKind.Sequential)]
public struct SmallRect
{
private short Left;
private short Top;
private short Right;
private short Bottom;
public void setDrawCord(short l, short t)
{
Left = l;
Top = t;
}
public void setWindowSize(short r, short b)
{
Right = r;
Bottom = b;
}
}
/// <summary>
/// Consctructor class for the buffer. Pass in the width and height you want the buffer to be.
/// </summary>
/// <param name="Width"></param>
/// <param name="Height"></param>
public buffer(int Width, int Height, int wWidth, int wHeight) // Create and fill in a multideminsional list with blank spaces.
{
if (Width > wWidth || Height > wHeight)
{
throw new System.ArgumentException("The buffer width and height can not be greater than the window width and height.");
}
h = CreateFile("CONOUT$", 0x40000000, 2, IntPtr.Zero, FileMode.Open, 0, IntPtr.Zero);
width = Width;
height = Height;
windowWidth = wWidth;
windowHeight = wHeight;
buf = new CharInfo[width * height];
rect = new SmallRect();
rect.setDrawCord(0, 0);
rect.setWindowSize((short)windowWidth, (short)windowHeight);
Clear();
Console.OutputEncoding = System.Text.Encoding.Unicode;
}
/// <summary>
/// This method draws any text to the buffer with given color.
/// To chance the color, pass in a value above 0. (0 being black text, 15 being white text).
/// Put in the starting width and height you want the input string to be.
/// </summary>
/// <param name="str"></param>
/// <param name="x"></param>
/// <param name="y"></param>
/// <param name="attribute"></param>
public void Draw(String str, int x, int y, short attribute) //Draws the image to the buffer
{
if (x > windowWidth - 1 || y > windowHeight - 1)
{
throw new System.ArgumentOutOfRangeException();
}
if (str != null)
{
Char[] temp = str.ToCharArray(); //From testing I know the unicode character is still correct here
int tc = 0;
foreach (Char le in temp)
{
buf[(x + tc) + (y * width)].Char.UnicodeChar = le; //Height * width is to get to the correct spot (since this array is not two dimensions).
System.Console.Out.WriteLine(buf[(x + tc) + (y * width)].Char.UnicodeChar); //once again, a simple test to see if the unicode character is working. Enter debugging and you will see the value is correct.
if (attribute != 0)
buf[(x + tc) + (y * width)].Attributes = attribute;
tc++;
}
}
}
/// <summary>
/// Prints the buffer to the screen.
/// </summary>
public void Print() //Paint the image
{
if (!h.IsInvalid)
{
bool b = WriteConsoleOutput(h, buf, new Coord((short)width, (short)height), new Coord((short)0, (short)0), ref rect); //This is the point where I think it is messing up, but I am at a loss at to what is happening.
}
}
/// <summary>
/// Clears the buffer and resets all character values back to 32, and attribute values to 1.
/// </summary>
public void Clear()
{
for (int i = 0; i < buf.Length; i++)
{
buf[i].Attributes = 1;
buf[i].Char.UnicodeChar = '\u0020';
}
}
/// <summary>
/// Pass in a buffer to change the current buffer.
/// </summary>
/// <param name="b"></param>
public void setBuf(CharInfo[] b)
{
if (b == null)
{
throw new System.ArgumentNullException();
}
buf = b;
}
/// <summary>
/// Set the x and y cordnants where you wish to draw your buffered image.
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
public void setDrawCord(short x, short y)
{
rect.setDrawCord(x, y);
}
/// <summary>
/// Clear the designated row and make all attribues = 1.
/// </summary>
/// <param name="row"></param>
public void clearRow(int row)
{
for (int i = (row * width); i < ((row * width + width)); i++)
{
if (row > windowHeight - 1)
{
throw new System.ArgumentOutOfRangeException();
}
buf[i].Attributes = 0;
buf[i].Char.UnicodeChar = '\u0020';
}
}
/// <summary>
/// Clear the designated column and make all attribues = 1.
/// </summary>
/// <param name="col"></param>
public void clearColumn(int col)
{
if (col > windowWidth - 1)
{
throw new System.ArgumentOutOfRangeException();
}
for (int i = col; i < windowHeight * windowWidth; i += windowWidth)
{
buf[i].Attributes = 0;
buf[i].Char.UnicodeChar = '\u0020';
}
}
/// <summary>
/// This function return the character and attribute at given location.
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns>
/// byte character
/// byte attribute
/// </returns>
public KeyValuePair<byte, byte> getCharAt(int x, int y)
{
if (x > windowWidth || y > windowHeight)
{
throw new System.ArgumentOutOfRangeException();
}
return new KeyValuePair<byte, byte>((byte)buf[((y * width + x))].Char.UnicodeChar, (byte)buf[((y * width + x))].Attributes);
}
}
}
And the example to run is:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DoubleBuffer
{
class ExampleClass
{
static int width = 80;
static int height = 30;
public static void Main(string[] args)
{
Console.CursorVisible = false;
Console.Title = "Double buffer example";
System.Console.SetBufferSize(width, height);
System.Console.SetWindowSize(width, height);
Console.Clear();
buffer myBuf = new buffer(width, height, width, height);
backgroundbuf.Draw("\u2580", 0, 0, 2);
myBuf.Print();
Console.ReadLine();
}
}
}
Let me know if I need to provide any more information! (Note: This code is directly from a code example I have on msdn. Was not aware of this bug until now, I want to make sure this is fixed!!!)
Upvotes: 1
Views: 1116
Reputation: 1060
Here are links that helped me solve this issue!
In addition, the Microsoft Dev Center has some great resources for questions like this as well.
OK! I found the solution! It is actually quite a simple one. First off, I need to get rid of "CharUnion" and just put that in the "CharInfo" (not sure why, but it works - If you know how to fix CharUnion, please post!). In addition, I need to declare CharSet = CharSet.Auto, else it defaults to ascii. (Shout out to whoever posted their answer but deleted it - you had the right idea!). -- new struct CharInfo (replaces struct CharUnion):
[StructLayout(LayoutKind.Explicit, CharSet = CharSet.Auto)]
public struct CharInfo
{
[FieldOffset(0)]
public char UnicodeChar;
[FieldOffset(0)]
public byte bAsciiChar;
[FieldOffset(2)]
public short Attributes;
}
Next, not that I think it changes anything, but instead of creating a new file, I now get a handle on the current output buffer. This removes the need for SafeFileHandle CreateFile.
public const Int32 STD_OUTPUT_HANDLE = -11;
[DllImportAttribute("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern ConsoleHandle GetStdHandle(Int32 nStdHandle);
For ease of use, for any future readers, this is the code I am currently using. Note, there may be some random snippets that have no use to the buffer working, I took this directly out of the project I am working in. You should get the point though.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
using System.IO;
using System.Text;
namespace DoubleBuffer
{
/*
* Copyright [2012] [Jeff R Baker]
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* v 1.2.0
*/
///<summary>
///This class allows for a double buffer in Visual C# cmd promt.
///The buffer is persistent between frames.
///</summary>
class buffer
{
private int width;
private int height;
private int windowWidth;
private int windowHeight;
private ConsoleHandle h;
private CharInfo[] buf;
private SmallRect rect;
public const Int32 STD_OUTPUT_HANDLE = -11;
[DllImportAttribute("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern ConsoleHandle GetStdHandle(Int32 nStdHandle);
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern bool WriteConsoleOutput(
ConsoleHandle hConsoleOutput,
CharInfo[] lpBuffer,
Coord dwBufferSize,
Coord dwBufferCoord,
ref SmallRect lpWriteRegion);
[StructLayout(LayoutKind.Sequential)]
public struct Coord
{
private short X;
private short Y;
public Coord(short X, short Y)
{
this.X = X;
this.Y = Y;
}
};
[StructLayout(LayoutKind.Explicit, CharSet = CharSet.Auto)]
public struct CharInfo
{
[FieldOffset(0)]
public char UnicodeChar;
[FieldOffset(0)]
public byte bAsciiChar;
[FieldOffset(2)]
public short Attributes;
}
[StructLayout(LayoutKind.Sequential)]
public struct SmallRect
{
private short Left;
private short Top;
private short Right;
private short Bottom;
public void setDrawCord(short l, short t)
{
Left = l;
Top = t;
}
public short DrawCordX()
{
return Left;
}
public short DrawCordY()
{
return Top;
}
public void setWindowSize(short r, short b)
{
Right = r;
Bottom = b;
}
}
/// <summary>
/// Consctructor class for the buffer. Pass in the width and height you want the buffer to be.
/// </summary>
/// <param name="Width"></param>
/// <param name="Height"></param>
public buffer(int Width, int Height, int wWidth, int wHeight) // Create and fill in a multideminsional list with blank spaces.
{
if (Width > wWidth || Height > wHeight)
{
throw new System.ArgumentException("The buffer width and height can not be greater than the window width and height.");
}
h = GetStdHandle(STD_OUTPUT_HANDLE);
width = Width;
height = Height;
windowWidth = wWidth;
windowHeight = wHeight;
buf = new CharInfo[width * height];
rect = new SmallRect();
rect.setDrawCord(0, 0);
rect.setWindowSize((short)windowWidth, (short)windowHeight);
Console.OutputEncoding = System.Text.Encoding.Unicode;
Clear();
}
/// <summary>
/// This method draws any text to the buffer with given color.
/// To chance the color, pass in a value above 0. (0 being black text, 15 being white text).
/// Put in the starting width and height you want the input string to be.
/// </summary>
/// <param name="str"></param>
/// <param name="x"></param>
/// <param name="y"></param>
/// <param name="attribute"></param>
public void Draw(String str, int x, int y, short attribute) //Draws the image to the buffer
{
if (x > windowWidth - 1 || y > windowHeight - 1)
{
throw new System.ArgumentOutOfRangeException();
}
if (str != null)
{
Char[] temp = str.ToCharArray();
int tc = 0;
foreach (Char le in temp)
{
buf[(x + tc) + (y * width)].UnicodeChar = le; //Height * width is to get to the correct spot (since this array is not two dimensions).
if (attribute != 0)
buf[(x + tc) + (y * width)].Attributes = attribute;
tc++;
}
}
}
/// <summary>
/// Prints the buffer to the screen.
/// </summary>
public void Print() //Paint the image
{
if (!h.IsInvalid)
{
bool b = WriteConsoleOutput(h, buf, new Coord((short)width, (short)height), new Coord((short)0, (short)0), ref rect);
}
}
/// <summary>
/// Clears the buffer and resets all character values back to 32, and attribute values to 1.
/// </summary>
public void Clear()
{
for (int i = 0; i < buf.Length; i++)
{
buf[i].Attributes = 1;
buf[i].UnicodeChar = '\u0020';
}
}
/// <summary>
/// Pass in a buffer to change the current buffer.
/// </summary>
/// <param name="b"></param>
public void setBuf(CharInfo[] b)
{
if (b == null)
{
throw new System.ArgumentNullException();
}
buf = b;
}
/// <summary>
/// Set the x and y cordnants where you wish to draw your buffered image.
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
public void setDrawCord(short x, short y)
{
rect.setDrawCord(x, y);
}
/// <summary>
/// Clear the designated row and make all attribues = 1.
/// </summary>
/// <param name="row"></param>
public void clearRow(int row)
{
for (int i = (row * width); i < ((row * width + width)); i++)
{
if (row > windowHeight - 1)
{
throw new System.ArgumentOutOfRangeException();
}
buf[i].Attributes = 0;
buf[i].UnicodeChar = '\u0020';
}
}
/// <summary>
/// Clear the designated column and make all attribues = 1.
/// </summary>
/// <param name="col"></param>
public void clearColumn(int col)
{
if (col > windowWidth - 1)
{
throw new System.ArgumentOutOfRangeException();
}
for (int i = col; i < windowHeight * windowWidth; i += windowWidth)
{
buf[i].Attributes = 0;
buf[i].UnicodeChar = '\u0020';
}
}
/// <summary>
/// This function return the character and attribute at given location.
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns>
/// byte character
/// byte attribute
/// </returns>
public KeyValuePair<byte, byte> getCharAt(int x, int y)
{
if (x > windowWidth || y > windowHeight)
{
throw new System.ArgumentOutOfRangeException();
}
return new KeyValuePair<byte, byte>((byte)buf[((y * width + x))].UnicodeChar, (byte)buf[((y * width + x))].Attributes);
}
public class ConsoleHandle : SafeHandleMinusOneIsInvalid
{
public ConsoleHandle() : base(false) { }
protected override bool ReleaseHandle()
{
return true; //releasing console handle is not our business
}
}
public int X
{
get { return width; }
}
public int Y
{
get { return height; }
}
public int dX
{
get { return rect.DrawCordX(); }
}
public int dY
{
get { return rect.DrawCordY(); }
}
}
}
Any changes to this may be updated on the "Console Double Buffer C#" on MSDN.
Upvotes: 2