在使用联合调用 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 ) 时做错了啥?的主要内容,如果未能解决你的问题,请参考以下文章

PInvoke 使用 wchar16_t 参数调用函数

PInvoke 在返回结构时仅适用于 64 位

“对 PInvoke 函数的调用使堆栈不平衡”

如何检查检查PInvoke签名的调用约定和参数与非托管的目标签名是不是匹配?

VS2010中的元帅结构指针

使用平台调用(PInvoke)实现C#调用非托管C++代码