Что я делаю неправильно с этим использованием StructLayout(LayoutKind.Explicit) при вызове структуры PInvoke с объединением?

Ниже представлена ​​полная программа. Это работает нормально, пока вы не раскомментируете «#define BROKEN» вверху. Разрыв происходит из-за того, что PInvoke не может правильно маршалировать объединение. Рассматриваемая структура INPUT_RECORD имеет ряд подструктур, которые могут использоваться в зависимости от значения в EventType.

Чего я не понимаю, так это того, что когда я определяю только одну дочернюю структуру KEY_EVENT_RECORD, она работает с явным объявлением по смещению 4. Но когда я добавляю другие структуры по тому же смещению, содержимое структуры становится полностью закрытым.

//UNCOMMENT THIS LINE TO BREAK IT:
//#define BROKEN

using System;
using System.Runtime.InteropServices;

class ConIOBroken
{
    static void Main()
    {
        int nRead = 0;
        IntPtr handle = GetStdHandle(-10 /*STD_INPUT_HANDLE*/);
        Console.Write("Press the letter: 'a': ");

        INPUT_RECORD record = new INPUT_RECORD();
        do
        {
            ReadConsoleInputW(handle, ref record, 1, ref nRead);
        } while (record.EventType != 0x0001/*KEY_EVENT*/);

        Assert.AreEqual((short)0x0001, record.EventType);
        Assert.AreEqual(true, record.KeyEvent.bKeyDown);
        Assert.AreEqual(0x00000000, record.KeyEvent.dwControlKeyState & ~0x00000020);//strip num-lock and test
        Assert.AreEqual('a', record.KeyEvent.UnicodeChar);
        Assert.AreEqual((short)0x0001, record.KeyEvent.wRepeatCount);
        Assert.AreEqual((short)0x0041, record.KeyEvent.wVirtualKeyCode);
        Assert.AreEqual((short)0x001e, record.KeyEvent.wVirtualScanCode);
    }

    static class Assert { public static void AreEqual(object x, object y) { if (!x.Equals(y)) throw new ApplicationException(); } }

    [DllImport("Kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    public static extern IntPtr GetStdHandle(int nStdHandle);

    [DllImport("Kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    public static extern bool ReadConsoleInputW(IntPtr hConsoleInput, ref INPUT_RECORD lpBuffer, int nLength, ref int lpNumberOfEventsRead);

    [StructLayout(LayoutKind.Explicit)]
    public struct INPUT_RECORD
    {
        [FieldOffset(0)]
        public short EventType;
        //union {
        [FieldOffset(4)]
        public KEY_EVENT_RECORD KeyEvent;
#if BROKEN
        [FieldOffset(4)]
        public MOUSE_EVENT_RECORD MouseEvent;
        [FieldOffset(4)]
        public WINDOW_BUFFER_SIZE_RECORD WindowBufferSizeEvent;
        [FieldOffset(4)]
        public MENU_EVENT_RECORD MenuEvent;
        [FieldOffset(4)]
        public FOCUS_EVENT_RECORD FocusEvent;
        //}
#endif
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct KEY_EVENT_RECORD
    {
        public bool bKeyDown;
        public short wRepeatCount;
        public short wVirtualKeyCode;
        public short wVirtualScanCode;
        public char UnicodeChar;
        public int dwControlKeyState;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct MOUSE_EVENT_RECORD
    {
        public COORD dwMousePosition;
        public int dwButtonState;
        public int dwControlKeyState;
        public int dwEventFlags;
    };

    [StructLayout(LayoutKind.Sequential)]
    public struct WINDOW_BUFFER_SIZE_RECORD
    {
        public COORD dwSize;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct MENU_EVENT_RECORD
    {
        public int dwCommandId;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct FOCUS_EVENT_RECORD
    {
        public bool bSetFocus;
    }

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

ОБНОВЛЕНИЕ:

Для тех, кто беспокоится о самих объявлениях структур:

  1. bool обрабатывается как 32-битное значение
  2. причина смещения (4) для данных заключается в том, чтобы обеспечить выравнивание 32-битной структуры, которое предотвращает начало объединения со смещением 2.

Опять же, моя проблема не в том, чтобы заставить PInvoke работать вообще, а в том, чтобы выяснить, почему эти дополнительные структуры (предположительно, с тем же смещением) перехватывают данные, просто добавляя их.


person csharptest.net    schedule 21.10.2009    source источник


Ответы (6)



//UNCOMMENT THIS LINE TO BREAK IT:
//#define BROKEN

using System; using System.Runtime.InteropServices;

class ConIOBroken { static void Main() { int nRead = 0; IntPtr handle = GetStdHandle(-10 /STD_INPUT_HANDLE/); Console.Write("Press the letter: 'a': ");

    INPUT_RECORD record = new INPUT_RECORD();
    do
    {
        ReadConsoleInputW(handle, ref record, 1, ref nRead);
    } while (record.EventType != 0x0001/*KEY_EVENT*/);

    Assert.AreEqual((short)0x0001, record.EventType);
    Assert.AreEqual(1u, record.KeyEvent.bKeyDown);
    Assert.AreEqual(0x00000000, record.KeyEvent.dwControlKeyState & ~0x00000020);//strip num-lock and test
    Assert.AreEqual('a', record.KeyEvent.UnicodeChar);
    Assert.AreEqual((short)0x0001, record.KeyEvent.wRepeatCount);
    Assert.AreEqual((short)0x0041, record.KeyEvent.wVirtualKeyCode);
    Assert.AreEqual((short)0x001e, record.KeyEvent.wVirtualScanCode);
    return;
}

static class Assert { public static void AreEqual(object x, object y) { if (!x.Equals(y)) throw new ApplicationException(); } }

[DllImport("Kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern IntPtr GetStdHandle(int nStdHandle);

[DllImport("Kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern bool ReadConsoleInputW(IntPtr hConsoleInput, ref INPUT_RECORD lpBuffer, int nLength, ref int lpNumberOfEventsRead);

[StructLayout(LayoutKind.Explicit)]
public struct INPUT_RECORD
{
    [FieldOffset(0)]
    public short EventType;
    //union {
    [FieldOffset(4)]
    public KEY_EVENT_RECORD KeyEvent;
    [FieldOffset(4)]
    public MOUSE_EVENT_RECORD MouseEvent;
    [FieldOffset(4)]
    public WINDOW_BUFFER_SIZE_RECORD WindowBufferSizeEvent;
    [FieldOffset(4)]
    public MENU_EVENT_RECORD MenuEvent;
    [FieldOffset(4)]
    public FOCUS_EVENT_RECORD FocusEvent;
}

[StructLayout(LayoutKind.Sequential)]
public struct KEY_EVENT_RECORD
{
    public uint bKeyDown;
    public short wRepeatCount;
    public short wVirtualKeyCode;
    public short wVirtualScanCode;
    public char UnicodeChar;
    public int dwControlKeyState;
}

[StructLayout(LayoutKind.Sequential)]
public struct MOUSE_EVENT_RECORD
{
    public COORD dwMousePosition;
    public int dwButtonState;
    public int dwControlKeyState;
    public int dwEventFlags;
};

[StructLayout(LayoutKind.Sequential)]
public struct WINDOW_BUFFER_SIZE_RECORD
{
    public COORD dwSize;
}

[StructLayout(LayoutKind.Sequential)]
public struct MENU_EVENT_RECORD
{
    public int dwCommandId;
}

[StructLayout(LayoutKind.Sequential)]
public struct FOCUS_EVENT_RECORD
{
    public uint bSetFocus;
}

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

}

person Hasani Blackwell    schedule 21.10.2009
comment
Я исправляюсь, но не понимаю, почему. Изменение bool на uint сработало. Я думаю, это новый вопрос... - person csharptest.net; 21.10.2009
comment
Из более поздней ссылки: bool - это 1-байтовый тип, но по умолчанию он маршалируется как 4-байтовый тип. Но разве это не то, что я делал в первую очередь??? см. также: stackoverflow.com/questions/1602899 - person csharptest.net; 22.10.2009
comment
Может быть, ответ на простом английском языке? Является ли проблема bool вместо int? Является ли System.Boolean упорядоченным по-другому или несовместимым с BOOL? тлдр. - person snarf; 23.10.2009

Я считаю, что это сработает, если вы сделаете bSetFocus и dwCommandId типа uint.

В будущем проверяйте вики PInvoke, чтобы узнать о правильных подписях. Обычно это точная или, по крайней мере, хорошая отправная точка.

person Special Touch    schedule 21.10.2009

1) public bool bKeyDown должен быть Int32 bKeyDown, потому что это BOOL (4 байта) в С++

2) публичный логический параметр bSetFocus должен быть Int32

person Hasani Blackwell    schedule 21.10.2009

Просто подумайте, что произойдет, если вы объявите самое большое поле последним? Возможно, P/Invoke копирует до последнего поля, которое заканчивается перед предыдущими полями.

person snarf    schedule 21.10.2009
comment
Тоже пробовал, разницы нет. - person csharptest.net; 21.10.2009

Попробуйте добавить рассчитанное вручную поле Size к атрибуту StructLayout, например:

[StructLayout(LayoutKind.Explicit, Size=...)]
person Blindy    schedule 21.10.2009
comment
Пробовал, разницы нет. - person csharptest.net; 21.10.2009

Исходный код с bool содержал 13 байтов, начиная с FieldOffset(4) ...
MOUSE_EVENT_RECORD, начиная с того же смещения, содержал 16 байтов, начиная с того же смещения.

Когда вы изменили bool (1 байт) на uint (4 байта), вы составили 3 байта.

person George    schedule 02.04.2010