Implementing a Win32 message loop and creating a Window object with P/Invoke

tamasf picture tamasf · Jan 24, 2012 · Viewed 12.7k times · Source

My main goal is to implement a proper message loop purely with P/Invoke calls that is able to handle USB HID events. Definitely its functionality should be identical with the following code that works well in Windows Forms. This NativeWindow descendant receives the events:

public class Win32EventHandler : NativeWindow
{
    public const int WM_DEVICECHANGE = 0x0219;

    public Win32EventHandler()
    {
        this.CreateHandle(new CreateParams());
    }

    protected override void OnHandleChange()
    {
        base.OnHandleChange();

        IntPtr handle = UsbHelper.RegisterForUsbEvents(this.Handle);
    }

    protected override void WndProc(ref Message m)
    {
        if (m.Msg == WM_DEVICECHANGE)
        {
            // Handle event
        }

        base.WndProc(ref m);
    }
}

... powered by this event loop:

Win32EventHandler handler = new Win32EventHandler();

var context = new ApplicationContext();
Application.Run(context);

// Other thread calls:
// context.ExitThread()

I found out that implementing the event loop is rather easy:

while (true)
{
    res = Win32.GetMessage(out msg, IntPtr.Zero, 0, 0);

    if (res == 0)
    {
        break;
    }

    Win32.TranslateMessage(ref msg);
    Win32.DispatchMessage(ref msg);

    if (msg.message == WM_DEVICECHANGE)
    {
        // Handle event
    }
}

But I have no idea how the underlying Window object should be created. The implementation of the NativeWindow class seems too complex for me.

This is my solution at the moment:

public void CustomLoop()
{
    string clsName = "Class";
    string wndName = "Window";

    Win32.WNDCLASSEX wndClassEx = new Win32.WNDCLASSEX();

    wndClassEx.cbSize = (uint)Marshal.SizeOf(wndClassEx);
    wndClassEx.lpszClassName = clsName;
    wndClassEx.lpfnWndProc = WndProc;

    Win32.RegisterClassEx(ref wndClassEx);

    IntPtr windowHandle = Win32.CreateWindowEx(0, clsName, wndName, 0, 0, 0, 0, 0, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);

    IntPtr usbEventHandle = UsbHelper.RegisterForUsbEvents(windowHandle);

    Win32.MSG msg;
    sbyte res = 0;

    while (true)
    {
        res = Win32.GetMessage(out msg, IntPtr.Zero, 0, 0);

        if (res == 0)
        {
            break;
        }

        if (msg.message == WM.DEVICECHANGE)
        {
            // Handle event (does not fire)
        }
        else
        {
            Win32.TranslateMessage(ref msg);
            Win32.DispatchMessage(ref msg);
        }
    }

    Win32.DestroyWindow(windowHandle);
    Win32.UnregisterClass(clsName, IntPtr.Zero);
}

[AllowReversePInvokeCalls]
private IntPtr WndProc(IntPtr hWnd, WM msg, IntPtr wParam, IntPtr lParam)
{
    switch (msg)
    {
        case WM.DEVICECHANGE:
            // Handle event (fires)
            break;

        default:
            return Win32.DefWindowProc(hWnd, msg, wParam, lParam);
    }

    return IntPtr.Zero;
}

Answer

Ben Voigt picture Ben Voigt · Jan 24, 2012

That's an very under-powered event loop. Consider using something like MsgWaitForMultipleObjectsEx instead of GetMessage.

Anyway, creating a window requires you to first register a window class (RegisterClassEx) and then create the window (CreateWindow). Neither one is particularly difficult. And instead of using base.WndProc(), you'll need to call DefWindowProc.

Trying to handle all messages directly inside the message loop is going to be overly difficult, that's why window procedures were created. And don't call TranslateMessage or DispatchMessage for any message you choose to process directly.