4

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;
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
tamasf
  • 1,068
  • 2
  • 10
  • 22

2 Answers2

4

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.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • This is what I was doing but, it doesn't seem to work. But now I found out that the **lpfnWndProc** callback method (RegisterClassEx) is invoked for the USB event, but the **GetMessage** call doesnt return. Is this the proper behavior? – tamasf Jan 24 '12 at 01:47
  • @tamasf: Did you check all your calls for errors? If you don't handle messages properly (by passing them to `DefWindowProc`) your `CreateWindow` call will return `NULL`, `RegisterDeviceNotification` will fail, and you won't get messages. – Ben Voigt Jan 24 '12 at 01:54
  • 2
    @tamasf: You might work on getting [Raymond Chen's skeleton Win32 app](http://blogs.msdn.com/b/oldnewthing/archive/2003/07/23/54576.aspx) up and running, first in C, then in C# with P/Invoke. – Ben Voigt Jan 24 '12 at 01:57
  • @tamasf: I don't see any error checking. Have you stepped through in a debugger to make sure the return values are all ok? Also, "Class" is a terrible window class name, it's required to be unique. Use a GUID, or your name, or something else that a lazy developer wouldn't have already used. Also, zeros for all window styles isn't likely to turn out well. – Ben Voigt Jan 24 '12 at 02:01
  • The code is working now (`WndProc`), my only concern is about the `GetMessage` call that does not return when the USB event is raised. Should it? Btw thanks for the advices: I will complete the code with error checks. The window is never shown (`ShowWindow`), so I did not find any style flag appropriate. – tamasf Jan 24 '12 at 02:10
  • @tamasf: Not sure, maybe the message is sent and not posted. Sent messages go directly to the window procedure. `GetMessage` is described as "The function dispatches incoming sent messages until a posted message is available for retrieval" and "During this call, the system delivers pending, nonqueued messages, that is, messages sent to windows owned by the calling thread using the SendMessage, SendMessageCallback, SendMessageTimeout, or SendNotifyMessage function." – Ben Voigt Jan 24 '12 at 02:25
  • Okey, last question: I found this in the .NET implementation: `hInstance = UnsafeNativeMethods.GetModuleHandle(null)`. Is it necessary? I use `IntPtr.Zero` at the moment. – tamasf Jan 24 '12 at 02:40
  • @tamasf: For `RegisterClassEx`? It'd be safer to pass the right `HINSTANCE`. – Ben Voigt Jan 24 '12 at 03:33
  • @Ben Why would you recommend `MsgWaitForMultipleObjectsEx` rather than `GetMessage` here. I see nothing wrong with `GetMessage`. Care to expand on that to justify it. – David Heffernan Jan 24 '12 at 10:36
  • @DavidHeffernan: `GetMessage` (and `PeekMessage`) don't enter an alertable wait, so APCs aren't run. – Ben Voigt Jan 24 '12 at 14:48
0

You may want to check out how this guy detect USB devices: A USB Library to Detect USB Devices

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Chris Shain
  • 50,833
  • 6
  • 93
  • 125
  • This is not the information I miss. I know how to listen on USB events. The provided sample does exactly the same as me. My problem is that I am not able to create Win32 Window objects like the NativeWindow class. It would be essential, because Silverlight does not expose the handle of the underlying Window object and does not provide any way to hook on the event loop. – tamasf Jan 24 '12 at 01:30