2014-02-11 48 views
1

我使用这个gamedev.stackexchange线程讨论的游戏循环时: https://gamedev.stackexchange.com/questions/67651/what-is-the-standard-c-windows-forms-game-loop代码优化导致空引用异常使用的PeekMessage

一切是伟大的工作,如果我使用调试版本类型,但是当我去要做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

你能否将代码缩减为一个可展示行为的简短可编译示例? –

+0

完成,这是我所说的最简单的例子。 Console.WriteLine是我的模拟器更新/渲染的地方。 – ALLCAPS

回答

1

out on PeekMessage应改为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); 
} 
+0

经仔细检查后,这实际上并未解决问题。它编译得很好,但仍然在运行时抛出nullreference异常。我试着把它作为“out”并且事先初始化对象,并且通过ref来尝试。我仍然遇到同样的崩溃。 – ALLCAPS

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

这非常有趣,在模拟器中不应该忽略性能增益。感谢您提供替代解决方案,我会仔细研究您提供的链接。 – ALLCAPS