Использование WriteConsoleOutput для записи Unicode с c# - PullRequest
1 голос
/ 13 февраля 2020

Я пытаюсь использовать функцию WriteConsoleOutput из kernel32.dll, однако мне не удается правильно отобразить символы Юникода, они всегда отображаются как неправильные символы.

Я пытался использовать:

Console.OutputEncoding = System.Text.Encoding.UTF8;

Изменение этого значения на Encoding.Unicode также не работает.

[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool SetConsoleOutputCP(uint wCodePageID);

public void SetCP(){
   SetConsoleOutputCP(65001);
}

Я попытался использовать оба из вышеперечисленных, каждый по отдельности, и ни один из них не содержал почти все комбинации значений.

Я также переключился между всеми шрифтами (включая шрифты истинного типа), однако ни один из них, кажется, не отображает символы правильно.

Вот код, который я использую для использования WriteConsoleOutput

[DllImport("kernel32.dll", SetLastError = true, EntryPoint = "WriteConsoleOutputW", CharSet = CharSet.Unicode)]
static extern bool WriteConsoleOutputW(SafeFileHandle hConsoleOutput, CharInfo[] lpBuffer, Coord dwBufferSize, Coord dwBufferCoord, ref SmallRect lpWriteRegion);

[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
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);

private static readonly SafeFileHandle h = CreateFile("CONOUT$", 0x40000000, 2, IntPtr.Zero, FileMode.Open, 0, IntPtr.Zero);

public static void RegionWrite(string s, int x, int y, int width, int height)
{           
    if (!h.IsInvalid)
    {
        int length = width * height;

        // Pad any extra space we have
        string fill = s + new string(' ', length - s.Length);

        // Grab the background and foreground as integers
        int bg = (int) Console.BackgroundColor;
        int fg = (int) Console.ForegroundColor;

        // Make background and foreground into attribute value
        short attr = (short)(fg | (bg << 4));

        CharInfo[] buf = fill.Select(c => 
        {
            CharInfo info = new CharInfo();

            // Give it our character to write
            info.Char.UnicodeChar = c;

            // Use our attributes
            info.Attributes = attr;

            // Return info for this character
            return info;

        }).ToArray();

        // Make everything short so we don't have to cast all the time
        short sx = (short) x;
        short sy = (short) y;
        short swidth = (short) width;
        short sheight = (short) height;

        // Make a buffer size out our dimensions
        Coord bufferSize = new Coord(swidth, sheight);

        // Not really sure what this is but its probably important
        Coord pos = new Coord(0, 0);

        // Where do we place this?
        SmallRect rect = new SmallRect() { Left = sx, Top = sy, Right = (short) (sx + swidth), Bottom = (short) (sy + sheight) };

        bool b = WriteConsoleOutputW(h, buf, bufferSize, pos, ref rect);
    }
    else
    {
        throw new Exception("Console handle is invalid.");
    }

}

Использование этого со стандартными символами ASCII прекрасно работает:

RegionWrite("Hello world", 4, 4, 10, 10);

Однако, когда я использую что-либо выше стандартного диапазона ASCII, оно не отображается правильно:

RegionWrite("┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬", 4, 4, 10, 10); Это выводит в виде двух строк символов ',', это имеет некоторый смысл, так как символ "┬" имеет значение 9516, 9516% 128 равно 44, что является кодом ascii для ','. * 102 6 *

Я знаю, что физически возможно вывести эти символы, так как Console.Write("┬┬┬┬") работает правильно. Я переключаюсь с Console.Write на WriteConsoleOutput, поскольку наблюдается значительное повышение производительности.

Вот код, который я использую для установки кодовых страниц:

public void Setup()
{
    Console.BufferHeight = Console.WindowHeight;
    Console.BufferWidth = Console.WindowWidth;

    Console.OutputEncoding = System.Text.Encoding.UTF8;

    SetConsoleOutputCP(65001);

    DefaultColor();
    Console.Clear();

    Console.ReadLine();

    RegionWrite("┬┬┬┬", 4, 4, 10, 10);

    Console.WriteLine("┬┬┬┬");

    Console.ReadLine();
}

Вот мои структуры:

[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;
}

Я предполагаю, что облажался с одной из переменных WriteConsoleOutput, но после нескольких часов поиска ответов я действительно не уверен, где я ошибся. Есть ли какая-то внутренняя функция кодирования набора, которую мне нужно использовать?

nvm исправил это

1 Ответ

0 голосов
/ 13 февраля 2020

Простое решение, измените

[StructLayout(LayoutKind.Explicit)]
public struct CharUnion
{
    [FieldOffset(0)] public char UnicodeChar;
    [FieldOffset(0)] public byte AsciiChar;
}

на

[StructLayout(LayoutKind.Explicit, CharSet=CharSet.Unicode)]
public struct CharUnion
{
    [FieldOffset(0)] public char UnicodeChar;
    [FieldOffset(0)] public byte AsciiChar;
}

Это связано с тем, что по умолчанию используется ANSI, то есть символы юникода автоматически превращаются в ANSI, следовательно, ┬ в,

...