-2

I'm creating a window through unmanaged CreateWindowEx using PInvoke to work as a server in order to dispatch SendMessage calls from a different process. This should be wrapped in a synchronous function (class registration + window creation), something like this:

public bool Start()
{
    if (!Running)
    {
        var processHandle = Process.GetCurrentProcess().Handle;

        var windowClass = new WndClassEx
        {
            lpszMenuName = null,
            hInstance = processHandle,
            cbSize = WndClassEx.Size,
            lpfnWndProc = WndProc,
            lpszClassName = Guid.NewGuid().ToString()
        };

        // Register the dummy window class
        var classAtom = RegisterClassEx(ref windowClass);

        // Check whether the class was registered successfully
        if (classAtom != 0u)
        {
            // Create the dummy window
            Handle = CreateWindowEx(0x08000000, classAtom, "", 0, -1, -1, -1, -1, IntPtr.Zero, IntPtr.Zero, processHandle, IntPtr.Zero);
            Running = Handle != IntPtr.Zero;

            // If window has been created
            if (Running)
            {
                // Launch the message loop thread
                taskFactory.StartNew(() =>
                {
                    Message message;

                    while (GetMessage(out message, IntPtr.Zero, 0, 0) != 0)
                    {
                        TranslateMessage(ref message);
                        DispatchMessage(ref message);
                    }
                });
            }
        }
    }

    return Running;
}

However, the MSDN states that GetMessage retrieves a message from the calling thread's message queue, therefore that wouldn't be possible as it's wrapped within a different thread/task. I can't simply move the CreateWindowEx function call to be within taskFactory.StartNew() scope.

Any ideas on how to achieve this? Perhaps change from GetMessage to PeekMessage maybe (the second might use a lot of the CPU, though)?

REQUIREMENTS:

  1. Start should be synchronous
  2. A new class should be registered every Start call
  3. Message loop should be dispatched within a different thread along GetMessage
Yves Calaci
  • 1,019
  • 1
  • 11
  • 37

1 Answers1

1

I can't simply move the CreateWindowEx function call to be within taskFactory.StartNew() scope.

Sorry, but you are going to have to do exactly that. Although you can SEND/POST a message to a window that resides in another thread, retrieving and dispatching messages DOES NOT work across thread boundaries. Creating a window, destroying that window, and running a message loop for that window, MUST all be done in the same thread context.

In your case, that means all of that logic has to be inside of the callback that you are passing to taskFactory.StartNew().

Any ideas on how to achieve this? Perhaps change from GetMessage to PeekMessage maybe (the second might use a lot of the CPU, though)?

That won't solve your issue. Both GetMessage() and PeekMessage() pull messages only from the message queue of the calling thread, and that pull can only return window messages for windows that are owned by the calling thread. This is explicitly stated in their documentations:

GetMessage

hWnd

Type: HWND

A handle to the window whose messages are to be retrieved. The window must belong to the current thread.

If hWnd is NULL, GetMessage retrieves messages for any window that belongs to the current thread, and any messages on the current thread's message queue whose hwnd value is NULL (see the MSG structure). Therefore if hWnd is NULL, both window messages and thread messages are processed.

If hWnd is -1, GetMessage retrieves only messages on the current thread's message queue whose hwnd value is NULL, that is, thread messages as posted by PostMessage (when the hWnd parameter is NULL) or PostThreadMessage.

PeekMessage

hWnd

Type: HWND

A handle to the window whose messages are to be retrieved. The window must belong to the current thread.

If hWnd is NULL, PeekMessage retrieves messages for any window that belongs to the current thread, and any messages on the current thread's message queue whose hwnd value is NULL (see the MSG structure). Therefore if hWnd is NULL, both window messages and thread messages are processed.

If hWnd is -1, PeekMessage retrieves only messages on the current thread's message queue whose hwnd value is NULL, that is, thread messages as posted by PostMessage (when the hWnd parameter is NULL) or PostThreadMessage.

The only differences between GetMessage() and PeekMessage() are that:

  • GetMessage() waits for a message if the queue is empty, whereas PeekMessage() does not.

  • PeekMessage() can return a message without removing it from the queue, whereas GetMessage() cannot.

Now, with that said, try something like the following. It will maintain the same semantics as your original code, ie it waits for the new window to be created before exiting. The window creation is just performed in the task thread instead of the calling thread:

public bool Start()
{
    if (!Running)
    {
        Handle = IntPtr.Zero;

        var readyEvent = new ManualResetEventSlim();

        // Launch the message loop thread
        taskFactory.StartNew(() =>
        {
            var processHandle = Process.GetCurrentProcess().Handle;

            var windowClass = new WndClassEx
            {
                lpszMenuName = null,
                hInstance = processHandle,
                cbSize = WndClassEx.Size,
                lpfnWndProc = WndProc,
                lpszClassName = Guid.NewGuid().ToString()
            };

            // Register the dummy window class
            var classAtom = RegisterClassEx(ref windowClass);

            // Check whether the class was registered successfully
            if (classAtom != 0u)
            {
                // Create the dummy window
                Handle = CreateWindowEx(0x08000000, classAtom, "", 0, -1, -1, -1, -1, IntPtr.Zero, IntPtr.Zero, processHandle, IntPtr.Zero);
                Running = Handle != IntPtr.Zero; 
            }

            readyEvent.Set();

            if (Handle != IntPtr.Zero)
            {
                Message message;

                while (GetMessage(out message, IntPtr.Zero, 0, 0) != 0)
                {
                    TranslateMessage(ref message);
                    DispatchMessage(ref message);
                }

                // if the message queue received WM_QUIT other than
                // from the window being destroyed, for instance by
                // a corresponding Stop() method posting WM_QUIT
                // to the window, then destroy the window now...
                if (IsWindow(Handle))
                {
                    DestroyWindow(Handle);
                }

                Handle = IntPtr.Zero;
            }
        });

        readyEvent.Wait();
    }

    return Running;
}
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • I know this would make it "work", but then `Start` would be no longer synchronous, as it would return before the window handle has even been created. I need the window handle RIGHT AFTER a `Start` call. Currently, the only way to achieve what I'm trying to do is to create the window wihin a class constructor and have a flag to manage start/stop to ignore messages on WndProc, but that's not something I'm willing to do either. – Yves Calaci Mar 11 '21 at 20:53
  • After moving `CreateWindowEx()` into the task thread, you can get `Start()` to have similar behavior as before by simply making `Start()` wait for the task thread to create the window, before the thread enters its message loop. I have updated my answer to show an example of that. – Remy Lebeau Mar 11 '21 at 21:10
  • Excellent, Remy! `ManualResetEvent[Slim]` is what I was really looking for! Sorry for the delayed reply. – Yves Calaci Apr 06 '21 at 19:37
  • Remy, ideas by any means on how to dispatch messages asynchronously within `WndProc`? Every message (request) would be wrapped in a separate thread. What I'm looking for is apparently something like `ReplyMessage`, but I have no idea whether it's even possible. – Yves Calaci Apr 07 '21 at 07:22
  • Message is sent > WndProc receives the message > WndProc creates a thread for that message > a value is returned through the recently created thread. – Yves Calaci Apr 07 '21 at 07:31
  • @Henri why do you want to do that? A window can receive a LOT of message traffic, especially with system-generated messages. Using a separate thread per message would be a bad idea. Even with thread polling, it is still not a good idea. What would be the goal exactly? – Remy Lebeau Apr 07 '21 at 07:35
  • Those messages are generated within a different process). That process hangs untill the given window processes the message and returns back. I know that further messages will be queued, but that would just hang upcoming messages, probably causing process to crash. The idea is to make some sort of an application server, just like `Tomcat`, for example. – Yves Calaci Apr 07 '21 at 23:39