问题
I am writing a c# console tetris game. Once I got to the part that the application was ready. I got to the part where I had to solve lagging. I am writing out like this:
static void writeCol(string a, ConsoleColor b)
{
ConsoleColor c = Console.ForegroundColor;
Console.ForegroundColor = b;
Console.Write(a);
Console.ForegroundColor = c;
}
So when a new block comes/i want to move somehing:
writeCol(blokk, ConsoleColor.Magenta);
Where blokk is:
private const string blokk = "█";
I have found a way to "write" to the console faster:
using System;
using System.IO;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
namespace ConsoleApplication1
{
class Program
{
[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)]
static extern bool WriteConsoleOutput(
SafeFileHandle hConsoleOutput,
CharInfo[] lpBuffer,
Coord dwBufferSize,
Coord dwBufferCoord,
ref SmallRect lpWriteRegion);
[StructLayout(LayoutKind.Sequential)]
public struct Coord
{
public short X;
public 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
{
public short Left;
public short Top;
public short Right;
public short Bottom;
}
[STAThread]
static void Main(string[] args)
{
SafeFileHandle h = CreateFile("CONOUT$", 0x40000000, 2, IntPtr.Zero, FileMode.Open, 0, IntPtr.Zero);
if (!h.IsInvalid)
{
CharInfo[] buf = new CharInfo[80 * 25];
SmallRect rect = new SmallRect() { Left = 0, Top = 0, Right = 80, Bottom = 25 };
for (byte character = 65; character < 65 + 26; ++character)
{
for (short attribute = 0; attribute < 15; ++attribute)
{
for (int i = 0; i < buf.Length; ++i)
{
buf[i].Attributes = attribute;
buf[i].Char.AsciiChar = character;
}
bool b = WriteConsoleOutput(h, buf,
new Coord() { X = 80, Y = 25 },
new Coord() { X = 0, Y = 0 },
ref rect);
}
}
}
Console.ReadKey();
}
}
}
(This code prints out all the characters from A-Z). So finnaly the question: How can i modify this code to take advantage of it?
Thanks in advance. Have a nice day.
EDIT: I found 1 way but it gives me buggy text. Any ideas?
public static void Writetocol(string s)
{
var kiir = s;
byte[] barr;
kiir = Convert.ToString(kiir);
barr = Encoding.ASCII.GetBytes(kiir);
SafeFileHandle h = CreateFile("CONOUT$", 0x40000000, 2, IntPtr.Zero, FileMode.Open, 0, IntPtr.Zero);
if (!h.IsInvalid)
{
CharInfo[] buf = new CharInfo[80 * 25];
SmallRect rect = new SmallRect() { Left = 0, Top = 0, Right = 80, Bottom = 25 };
for (short attribute = 0; attribute < 15; ++attribute)
{
for (int i = 0; i < barr.Length; ++i)
{
buf[i].Attributes = attribute;
buf[i].Char.AsciiChar = barr[i];
}
bool b = WriteConsoleOutput(h, buf,
new Coord() { X = 80, Y = 25 },
new Coord() { X = 0, Y = 0 },
ref rect);
}
}
}
It gives me this:
When it should give me this:(It's written Hungarian if anyone wonders)
回答1:
You could parse the string you provide to fill the buffer by handling your own linefeeds and position, like so:
static void writeCol(string a, ConsoleColor b)
{
byte x = 0, y = 0;
// parsing to make it fly
// fill the buffer with the string
for(int ci=0; ci<a.Length;ci++)
{
switch (a[ci])
{
case '\n': // newline char, move to next line, aka y=y+1
y++;
break;
case '\r': // carriage return, aka back to start of line
x = 0;
break;
case ' ': // a space, move the cursor to the right
x++;
break;
default:
// calculate where we should be in the buffer
int i = y * 80 + x;
// color
buf[i].Attributes= (short) b;
// put the current char from the string in the buffer
buf[i].Char.AsciiChar = (byte) a[ci];
x++;
break;
}
}
// we handled our string, let's write the whole screen at once
bool success = WriteConsoleOutput(h, buf,
new Coord() { X = 80, Y = 25 },
new Coord() { X = 0, Y = 0 },
ref rect);
}
Notice that I already refactored the safehandle h
and the native buffer buf
to the static state so we only have this once in the app:
static IntPtr h= GetStdHandle(STD_OUTPUT_HANDLE);
static CharInfo[] buf = new CharInfo[80 * 25];
static SmallRect rect = new SmallRect() { Left = 0, Top = 0, Right = 80, Bottom = 25 };
I have added the GetStdHandle
//http://www.pinvoke.net/default.aspx/kernel32/GetStdHandle.html
const int STD_OUTPUT_HANDLE = -11;
[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr GetStdHandle(int nStdHandle);
You need to change the method signature on WriteConsoleOutput
to accept an IntPtr
instead of SafeFileHandle
in that case.
I tested this method with the following test call:
writeCol(@"
TEST
======
1 test
FuBar", ConsoleColor.Blue);
which gives this result:
So keep in mind to fill the buffer buf
at the correct positions first and then call WriteConsoleOutput
to copy the buffer to the screen at once. If you call that very often you are back to square one...
Notice that you don't need to write the whole screen. By using different rectangles you can write only parts of the screen.
For this demo I left out all error-checking. That is up to you to check.
You might want to read up on the native calls used from the msdn documentation
来源:https://stackoverflow.com/questions/28398636/pinvoke-speed-up-console-write