1

我正在使用这个 gamedev.stackexchange 线程中讨论的游戏循环: https ://gamedev.stackexchange.com/questions/67651/what-is-the-standard-c-windows-forms-game-loop

如果我使用 Debug 构建类型,一切都很好,但是当我去做 Release 时,我得到一个空引用异常。看起来只有当我启用代码优化时才会发生这种情况。这是一个做同样事情的准系统示例。表单是完全空白的,在这个例子中没有按钮/控件。

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);
        }
    }
}

当我运行它时,据说异常发生在 forms.dll 内的外部代码中,它是在启动此表单的 Application.Run("etc") 之后抛出的。堆栈跟踪并没有真正的帮助,它只是 Application.Run 和一堆外部代码。

我不确定是什么原因造成的,但我知道这与调用 PeekMessage 有关,因为如果我注释掉对 Idle 事件的订阅,则不会发生错误。

作为一个附带问题,为什么我需要在这里声明“NativeMessage”结构?如果我剪掉它似乎不会引起问题,但是使用此游戏循环的每个示例都包含它。

4

2 回答 2

3

虽然@shf301 的回答正确解释了如何PeekMessage在您的代码中解决问题,但我建议您根本不要PeekMessage为此目的使用它,因为它会带来一些不必要的开销。改用GetQueueStatus

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);

有关更多详细信息,请查看我对“高性能 Winforms 更新”的回答

于 2014-02-11T05:17:31.523 回答
1

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);
}
于 2014-02-11T04:33:12.373 回答