Что я делаю не так с этим использованием StructLayout( LayoutKind.Explicit ) при вызове структуры PInvoke с объединением?
-
05-07-2019 - |
Вопрос
Ниже приведена полная программа.Это работает нормально до тех пор, пока вы не раскомментируете '#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;
}
}
Обновить:
Для тех, кто беспокоится о самих объявлениях структуры:
- bool обрабатывается как 32-битное значение
- причина смещения (4) для данных заключается в том, чтобы разрешить выравнивание 32-разрядной структуры, которое предотвращает начало объединения со смещением 2.
Опять же, моя проблема вообще не в том, чтобы заставить PInvoke работать, она пытается выяснить, почему эти дополнительные структуры (предположительно с одинаковым смещением) собирают данные, просто добавляя их.
Решение
//UNCOMMENT THIS LINE TO BREAK IT:
//#define BROKEN
использование Системы;использование System.Runtime.Службы взаимодействия;
класс Coniobrokened
{
статическая пустота Main()
{
int nRead = 0;Дескриптор IntPtr = GetStdHandle(-10 /STD_INPUT_HANDLE/);Консоль.Написать("Нажмите на букву:"а":");
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;
}
}
Другие советы
Я считаю, что это сработает, если вы сделаете bSetFocus и dwCommandId типа uint.
В будущем ознакомьтесь с PInvoke вики за надлежащими подписями.Обычно это точная или, по крайней мере, хорошая отправная точка.
1) общедоступный bool bKeyDown должен быть Int32 bKeyDown, потому что это BOOL (4 байта) в c ++
2) public bool bSetFocus должен быть Int32
Просто подумайте, что произойдет, если вы объявите самое большое поле последним?Возможно, P / Invoke копирует до последнего поля, которое заканчивается перед более ранними полями.
Попробуйте добавить поле размера, рассчитанное вручную, к атрибуту StructLayout, например:
[StructLayout(LayoutKind.Explicit, Size=...)]
Исходный код с bool содержал 13 байт, начиная с FieldOffset(4) ...
Тот самый MOUSE_EVENT_RECORD
начиная с того же смещения, содержит 16 байт, начиная с того же смещения.
Когда вы изменили bool (1 байт) на uint (4 байта), вы составили 3 байта.