我正在使用此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”结构?如果我剪掉它似乎不会引起问题,但是使用这个游戏循环的每个例子都包括它。
答案 0 :(得分:2)
尽管@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"。
答案 1 :(得分:1)
out
上的PeekMessage
应该是ref
。 PeekMessage
不会为您分配消息结构,它会填充您传入的消息结构。不同的是,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);
}