以下は完全なプログラムです。上部の「#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) の理由は、ユニオンがオフセット 2 で始まるのを防ぐ 32 ビット構造のアラインメントを可能にするためです。
繰り返しますが、私の問題は PInvoke をまったく機能させていません。これらの追加の構造 (おそらく同じオフセットにある) がデータを単純に追加することで、データを混乱させている理由を理解しようとしています。