.net-corepinvokewndprocwindow-messagesmessage-loop

Window message loop ( WndProc | While (GetMEssage)) thread in .NET Core


I am trying to subscribe to window messages using .Net Core

I am able to receive initial messages to create a window ( through pinvoke) and destroy messages. But after that my created windows gets blocked and does not receive any other messages.

 public class CustomWindow : IDisposable
{
    delegate IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);

    [StructLayout(LayoutKind.Sequential,CharSet = CharSet.Unicode)]
    struct WNDCLASS
    {
        public uint style;
        public IntPtr lpfnWndProc;
        public int cbClsExtra;
        public int cbWndExtra;
        public IntPtr hInstance;
        public IntPtr hIcon;
        public IntPtr hCursor;
        public IntPtr hbrBackground;
        [MarshalAs(UnmanagedType.LPWStr)]
        public string lpszMenuName;
        [MarshalAs(UnmanagedType.LPWStr)]
        public string lpszClassName;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct MSG
    {
        public IntPtr hwnd;
        public uint message;
        public IntPtr wParam;
        public IntPtr lParam;
        public uint time;
    }

    [DllImport("user32.dll", SetLastError = true)]
    static extern UInt16 RegisterClassW([In] ref WNDCLASS lpWndClass);

    [DllImport("user32.dll", SetLastError = true)]
    static extern IntPtr CreateWindowExW(
       UInt32 dwExStyle,
       [MarshalAs(UnmanagedType.LPWStr)]string lpClassName,
       [MarshalAs(UnmanagedType.LPWStr)]string lpWindowName,
       UInt32 dwStyle,
       Int32 x,
       Int32 y,
       Int32 nWidth,
       Int32 nHeight,
       IntPtr hWndParent,
       IntPtr hMenu,
       IntPtr hInstance,
       IntPtr lpParam
    );

    [DllImport("user32.dll", SetLastError = true)]
    static extern IntPtr DefWindowProcW(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);

    [DllImport("user32.dll", SetLastError = true)]
    static extern bool DestroyWindow(IntPtr hWnd);

    private const int ERROR_CLASS_ALREADY_EXISTS = 1410;

    private bool _mDisposed;
    public IntPtr Hwnd;

    public List<uint> Messages { get; set; }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    private void Dispose(bool disposing)
    {
        if (!_mDisposed)
        {
            if (disposing)
            {
                // Dispose managed resources
            }

            // Dispose unmanaged resources
            if (Hwnd != IntPtr.Zero)
            {
                DestroyWindow(Hwnd);
                Hwnd = IntPtr.Zero;
            }
        }
    }

    public CustomWindow()
    {
        Messages = new List<uint>();
        var className = "InvisibleWindow";

        _mWndProcDelegate = CustomWndProc;

        // Create WNDCLASS
        WNDCLASS windClass = new WNDCLASS
        {
            lpszClassName = className,
            lpfnWndProc = Marshal.GetFunctionPointerForDelegate(_mWndProcDelegate)
        };

        UInt16 classAtom = RegisterClassW(ref windClass);

        int lastError = Marshal.GetLastWin32Error();

        if (classAtom == 0 && lastError != ERROR_CLASS_ALREADY_EXISTS)
        {
            throw new Exception("Could not register window class");
        }

        const UInt32 WS_OVERLAPPEDWINDOW = 0xcf0000;
        const UInt32 WS_VISIBLE = 0x10000000;


        // Create window
        Hwnd = CreateWindowExW(
            0,
            className,
            "My WIndow",
            WS_OVERLAPPEDWINDOW | WS_VISIBLE, 0, 0, 300, 400,
            IntPtr.Zero,
            IntPtr.Zero,
            IntPtr.Zero,
            IntPtr.Zero
        );

        Importer.ShowWindow(Hwnd, 1);
        Importer.UpdateWindow(Hwnd);
    }

    private  IntPtr CustomWndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
    {
        Messages.Add(msg);
        return DefWindowProcW(hWnd, msg, wParam, lParam);
    }

    private WndProc _mWndProcDelegate;
}

Class to create a custom window CustomWndProc receives messages. I am not performing any logic there yet, just trying to get things working.

I am trying to figure out how I could do this from a separate thread, to keep listening to messages while also not blocking main api/gui/console interface

Can I create this class in separate thread and still use dependency injection and have access to its data/messages/events on a whim. I know there are many ways of doing this in .net, but this is .net Core.

What's up with downvotes, there are literally no resources how to implement this in Net Core, I have no access to any of the .Net forms/controls that simplify this, I've also provided full working class, If there is lack of details in my post, leave a comment. Don't down vote people randomly without a good reason. If there is another thread that explains how to do it, link it and then downvote this one.

 private void GetMessage()
    {
       IntPtr hwnd = _customWindow.Hwnd;
        int bRet;
        while ((bRet = Importer.GetMessage(out var msg, _customWindow.GetHandle, 0, 0)) !=0 )
        {
            if (bRet == -1)
            {
                Console.WriteLine("Error");
            }
            else
            {
                Importer.TranslateMessage(ref msg);
                Importer.DispatchMessage(ref msg);
                Console.WriteLine(_customWindow.Messages.LastOrDefault());
            }
        }
    }

Implemented GetMessage Loop, my window now receives all messages and does not get blocked, but it now blocks main thread. I'll see if I can create window on separate thread and run message loop on that thread.


Solution

  • Eventually managed to find a working solution.

    While in C++ created window works in its own thread automatically without any input, and is able to handle its own message queue. Also because its primarily GUI, that's how input is received anyway.

    In .Net you cannot assign objects to a specific thread. Thus even if you start GetMessage Loop in new thread or task, window itself will be blocked, due the fact that its in main thread. To overcome this, run C++ Window and GetMessage loop in main Thread and run your console/Gui/APi in second thread.