【问题标题】:Code optimization causes null reference exception when using PeekMessage代码优化导致使用PeekMessage时空引用异常
【发布时间】:2014-03-08 16:53:57
【问题描述】:

我正在使用这个 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”结构?如果我剪掉它似乎不会引起问题,但是使用此游戏循环的每个示例都包含它。

【问题讨论】:

  • 您能否将代码缩减为展示该行为的简短可编译示例?
  • 完成,这是符合我的意思的最精简的例子。 Console.WriteLine 是我的模拟器更新/渲染所在。

标签: c# nullreferenceexception peekmessage


【解决方案1】:

虽然@shf301's answer 正确解释了如何在您的代码中使用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);

更多详情,请查看我的answer on "Winforms updates with high performance"

【讨论】:

  • 这很有趣,在仿真器中不会忽略任何性能提升。感谢您提供替代解决方案,我会仔细研究您提供的链接。
【解决方案2】:

PeekMessage 上的 out 应改为 refPeekMessage 不会为您分配消息结构,它会填充您传入的消息结构。不同的是,ref 参数必须在传递到方法调用之前进行初始化,其中 out参数不需要初始化。您会看到,当将out 更改为ref 时,编译器将强制您添加new 调用来初始化result

在玩这个的过程中,我发现只需添加对new Message() 的调用以初始化result 并将参数保留为out 就足以防止崩溃。我假设在优化代码时没有为result 分配内存,导致对PeekMessage 的调用失败。

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

【讨论】:

  • 经过仔细检查,这实际上并不能解决问题。它编译得很好,但仍然在运行时抛出 nullreference 异常。我尝试将其保留为“out”并事先初始化对象,并通过 ref 尝试。我仍然遇到同样的崩溃。
猜你喜欢
  • 2016-03-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-06-13
  • 2014-08-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多