Что я делаю не так с этим использованием StructLayout (LayoutKind.Explicit) при вызове структуры PInvoke с объединением? - PullRequest
1 голос
/ 21 октября 2009

Ниже приведена полная программа. Он отлично работает, если вы не раскомментируете '#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;
    }
}

UPDATE:

Для тех, кто беспокоится о самих объявлениях структуры:

  1. bool рассматривается как 32-битное значение
  2. причина смещения (4) в данных состоит в том, чтобы допустить выравнивание 32-битной структуры, которое препятствует началу объединения со смещением 2.

Опять же, моя проблема не в том, чтобы вообще заставить PInvoke работать, а в том, чтобы выяснить, почему эти дополнительные структуры (предположительно с тем же смещением) заполняют данные, просто добавляя их.

Ответы [ 6 ]

2 голосов
/ 21 октября 2009

Полагаю, это сработает, если вы сделаете bSetFocus и dwCommandId типа uint.

В будущем проверьте PInvoke wiki на наличие правильных подписей. Это обычно точно или, по крайней мере, хорошая отправная точка.

1 голос
/ 21 октября 2009


//UNCOMMENT THIS LINE TO BREAK IT:
//#define BROKEN</p>

<p>using System;
using System.Runtime.InteropServices;</p>

<p>class ConIOBroken
{
    static void Main()
    {
        int nRead = 0;
        IntPtr handle = GetStdHandle(-10 /<em>STD_INPUT_HANDLE</em>/);
        Console.Write("Press the letter: 'a': ");</p>

<pre><code>    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;
}

}

1 голос
/ 21 октября 2009

1) public bool bKeyDown должен быть Int32 bKeyDown, поскольку это BOOL (4 байта) в c ++

2) public bool bSetFocus должен быть Int32

0 голосов
/ 02 апреля 2010

Исходный код с bool содержал 13 байтов, начиная с FieldOffset (4) ...
MOUSE_EVENT_RECORD, начинающийся с того же смещения, содержит 16 байтов, начинающихся с того же смещения.

Когда вы изменили bool (1 байт) на uint (4 байт), вы составили 3 байта.

0 голосов
/ 21 октября 2009

Попробуйте добавить вычисляемое вручную поле Размер в атрибут StructLayout, например:

[StructLayout(LayoutKind.Explicit, Size=...)]
0 голосов
/ 21 октября 2009

Просто мысль, что произойдет, если вы объявите самое большое поле последним? Возможно, P / Invoke копирует до последнего поля, которое заканчивается перед более ранними полями.

...