在使用联合调用 PInvoke 结构时,我在使用 StructLayout( LayoutKind.Explicit ) 时做错了啥?
Posted
技术标签:
【中文标题】在使用联合调用 PInvoke 结构时,我在使用 StructLayout( LayoutKind.Explicit ) 时做错了啥?【英文标题】:What am I doing wrong with this use of StructLayout( LayoutKind.Explicit ) when calling a PInvoke struct with union?在使用联合调用 PInvoke 结构时,我在使用 StructLayout( LayoutKind.Explicit ) 时做错了什么? 【发布时间】:2010-12-08 19:38:12 【问题描述】:以下是完整的程序。只要您不取消注释顶部的“#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 位结构对齐,从而防止联合从偏移 2 开始。
同样,我的问题根本不在于 PInvoke 工作,它试图弄清楚为什么这些额外的结构(假设在相同的偏移量处)通过简单地添加它们来收集数据。
【问题讨论】:
【参考方案1】:如果您将 bSetFocus 和 dwCommandId 设为 uint 类型,我相信它会起作用。
以后,请查看PInvoke wiki 以获取正确的签名。它通常是准确的,或者至少是一个很好的起点。
【讨论】:
【参考方案2】:
//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;
【讨论】:
我是正确的,但不明白为什么。将 bool 更改为 uint 确实有效。我想这是一个新问题...... 来自后面的链接:“bool 是 1 字节类型,但它默认编组为 4 字节类型。”但我一开始不就是这样吗???另见:***.com/questions/1602899 也许用简单的英语回答?问题是布尔而不是整数吗? System.Boolean 的编组方式是否不同或与 BOOL 不兼容? tldr.【参考方案3】:1) public bool bKeyDown 应该是 Int32 bKeyDown 因为它在 c++ 中是一个 BOOL (4bytes)
2) public bool bSetFocus 应该是 Int32
【讨论】:
【参考方案4】:想一想,如果最后声明最大字段会发生什么?也许 P/Invoke 正在复制到最后一个字段,该字段在前面的字段之前结束。
【讨论】:
这个也试过了,没效果。【参考方案5】:尝试将手动计算的 Size 字段添加到 StructLayout 属性,如下所示:
[StructLayout(LayoutKind.Explicit, Size=...)]
【讨论】:
试过了,没用。【参考方案6】:带有布尔值的原始代码包含 13 个字节,从 FieldOffset(4) 开始 ...
以相同偏移量开始的MOUSE_EVENT_RECORD
包含从相同偏移量开始的 16 个字节。
当您将 bool (1byte) 更改为 uint(4bytes) 时,您组成了 3 个字节。
【讨论】:
以上是关于在使用联合调用 PInvoke 结构时,我在使用 StructLayout( LayoutKind.Explicit ) 时做错了啥?的主要内容,如果未能解决你的问题,请参考以下文章