ユニオンでPInvoke構造体を呼び出すときに、このStructLayout(LayoutKind.Explicit)の使用で何が間違っていますか?
-
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ビット値として扱われます
- データのoffset(4)の理由は、32ビットの構造体のアライメントを可能にするためです。
繰り返しますが、私の問題はPInvokeをまったく機能させないことです。これらの追加の構造(おそらく同じオフセットにある)が単純に追加するだけでデータを増やしている理由を見つけようとしています。
解決
//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;
}
}
他のヒント
タイプuintのbSetFocusおよびdwCommandIdを作成すれば機能すると信じています。
今後、適切な署名については、 PInvoke wiki をご覧ください。通常は正確であるか、少なくとも最低限の良い出発点です。
1)public bool bKeyDownは、C ++のBOOL(4バイト)であるため、Int32 bKeyDownである必要があります
2)public bool bSetFocusはInt32でなければなりません
念のために、最後に最大フィールドを宣言するとどうなりますか?おそらく、P / Invokeは、前のフィールドの前に終了する最後のフィールドまでコピーしています。
次のように、手動で計算されたSizeフィールドをStructLayout属性に追加してみてください:
[StructLayout(LayoutKind.Explicit, Size=...)]
boolを含む元のコードには、FieldOffset(4)から始まる13バイトが含まれていました...
同じオフセットで始まる MOUSE_EVENT_RECORD
は、同じオフセットで始まる16バイトに対応しました。
ブール(1バイト)をuint(4バイト)に変更すると、3バイトになります。