Code optimization causes null reference exception when using PeekMessage

673 views Asked by At

I'm using the game loop discussed in this gamedev.stackexchange thread: https://gamedev.stackexchange.com/questions/67651/what-is-the-standard-c-windows-forms-game-loop

Everything is working great if I'm using Debug build type, but when I go to do Release, I get a null reference exception. It looks like it only happens if I enable code optimization. This is a barebone example that does the same thing. The form is totally blank, there are no buttons/controls on it in this example.

using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace Sharp8
{
    public partial class DebugForm : Form
    {
        public DebugForm()
        {
            InitializeComponent();
            Application.Idle += GameLoop;
        }

        private void GameLoop(object sender, EventArgs e)
        {
            while (IsApplicationIdle())
            {
                Console.WriteLine("Game Updates/Rendering!"); 
            }
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct NativeMessage
        {
            public IntPtr Handle;
            public uint Message;
            public IntPtr WParameter;
            public IntPtr LParameter;
            public uint Time;
            public Point Location;
        }

        [DllImport("user32.dll")]
        static extern bool PeekMessage(out Message message, IntPtr window, uint messageFilterMinimum, uint messageFilterMaximum, uint shouldRemoveMessage);

        private bool IsApplicationIdle()
        {
            Message result;
            return !PeekMessage(out result, IntPtr.Zero, 0, 0, 0);
        }
    }
}

When I run this, the exception is said to happen in external code inside forms.dll, and it's throw after my Application.Run("etc") that starts this form. The stack trace isn't really helpful, it's just the Application.Run and a bunch of external code.

I'm not sure what is causing this, but I know it has something to do with calling PeekMessage, because if I comment out the subscription to the Idle event the error does not happen.

As a side question, why do I need to declare the "NativeMessage" struct here? It doesn't seem to cause issues if I cut it, yet every example of using this game loop includes it.

2

There are 2 answers

1
shf301 On

The out on PeekMessage should be ref instead. PeekMessage doesn't allocation a message structure for you, it fills in a message structure that you pass in. The different would be that a ref parameter must be initialized before being passed into the method call, where an out parameter does not need to be initialized. You'll see that when changing out to ref the compiler will force you to add a new call to initialize result.

In playing with this I found that just adding the call to new Message() to initialize result and leaving the parameter as out was enough to prevent the crash. I would assume that when the code is optimized no memory for result is allocated causing the call to PeekMessage to fail.

[DllImport("user32.dll")]
static extern bool PeekMessage(ref Message message, IntPtr window, uint    messageFilterMinimum, uint messageFilterMaximum, uint shouldRemoveMessage);

private bool IsApplicationIdle()
{
    Message result = new Message();
    return !PeekMessage(ref result, IntPtr.Zero, 0, 0, 0);
}
1
noseratio On

While @shf301's answer correctly explains how to solve the problem with PeekMessage in your code, I'd recommend to not use PeekMessage at all for this purpose, as it comes with some unneeded overhead. Use GetQueueStatus instead:

public static bool IsApplicationIdle()
{
    // The high-order word of the return value indicates
    // the types of messages currently in the queue. 
    return 0 == (GetQueueStatus(QS_MASK) >> 16 & QS_MASK);
}

const uint QS_MASK = 0x1FF;

[System.Runtime.InteropServices.DllImport("user32.dll")]
static extern uint GetQueueStatus(uint flags);

For some more details, check my answer on "Winforms updates with high performance".