Pinvoke. Speed up Console.Write();

旧城冷巷雨未停 提交于 2019-12-11 05:42:50

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!