2014-02-11 50 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