views:

412

answers:

6

The following is a complete program. It works fine as long as you don't uncomment the '#define BROKEN' at the top. The break is due to a PInvoke failing to marshal a union correctly. The INPUT_RECORD structure in question has a number of substructures that might be used depending on the value in EventType.

What I don't understand is that when I define only the single child structure of KEY_EVENT_RECORD it works with the explicit declaration at offset 4. But when I add the other structures at the same offset the structure's content get's totally hosed.

//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:

For those worried about the struct declarations themselves:

  1. bool is treated as a 32-bit value
  2. the reason for offset(4) on the data is to allow for the 32-bit structure alignment which prevents the union from beginning at offset 2.

Again, my problem isn't making PInvoke work at all, it's trying to figure out why these additional structures (supposedly at the same offset) are fowling up the data by simply adding them.

A: 

Just a thought, what happens if you declare the largest field last? Maybe P/Invoke is copying up to the last field, which ends before earlier fields.

Snarfblam
Tried this also, didn't make a difference.
csharptest.net
+2  A: 

I believe it will work if you make bSetFocus and dwCommandId of type uint.

In the future, check out the PInvoke wiki for the proper signatures. It's usually accurate or, at the very least, a good starting point.

Special Touch
A: 

Try adding a manually calculated Size field to the StructLayout attribute, like this:

[StructLayout(LayoutKind.Explicit, Size=...)]
Blindy
Tried it, didn't make a difference.
csharptest.net
+1  A: 

1) public bool bKeyDown should be Int32 bKeyDown because it's a BOOL (4bytes) in c++

2) public bool bSetFocus should be Int32

hjb417
I tested the code and was wrong about 3.
hjb417
+1  A: 
hjb417
I stand corrected but don't understand why. Changing the bool to uint did work. I guess that is a new question...
csharptest.net
see http://blogs.msdn.com/oldnewthing/archive/2004/12/22/329884.aspx
hjb417
and also http://blogs.msdn.com/oldnewthing/archive/2009/08/13/9867383.aspx
hjb417
From the later link: "The bool is a 1-byte type, but it marshals as a 4-byte type by default." But isn't that what I was doing in the first place??? see also: http://stackoverflow.com/questions/1602899
csharptest.net
Maybe an answer in plain English? Is the problem a bool instead of an int? Is System.Boolean marshalled differently or incompatible with a BOOL? tldr.
Snarfblam
A: 

The original code with the bool contained 13 bytes, starting at FieldOffset(4) ... The MOUSE_EVENT_RECORD starting at the same offset conatianed 16 bytes starting at the same offset.

When you changed the bool (1byte) to an uint(4bytes) you made up the 3 bytes.

George