2010-03-03 86 views
3

在C#中,我使用它來得到一個窗口的圖標:如何測試由未發佈的GDI對象引起的內存泄漏?

IntPtr IconHandle = SendMessage(hwnd, WM_GETICON ...); 

原因,SendMessage函數是
的DllImport( 「user32.dll中」)。

AFAIK,這是需要清理:

DestroyIcon(iconHandle); 

(通過的DllImport( 「user32.dll中」)再次DestroyIcon。)

事情似乎做工精細,但
我想要什麼要知道的是:

如何確定內存泄漏正在如果
我註釋掉調用DestroyIcon地方()?

我打算做的是
把獲得圖標碼長的循環
內沒有調用DestroyIcon()。

要檢查內存泄漏,我天真的辦法是
檢查,如果「承諾費」,在
「窗口任務管理器」正在積累。

但是,經過100000次迭代循環後...
沒有什麼事情發生。 Windows XP仍然運行愉快。

我需要找出測試了這一點的方式,因爲
我想確保我的代碼是正確
釋放的非託管資源,在我的機器和
也是今後最終用戶。

我該如何測試?或者是我沒有足夠努力地測試
(例如改爲10^10次迭代測試)?

我張貼下面的測試代碼:

Form1.cs中

using System; 
using System.Collections.Generic; 
using System.ComponentModel; 
using System.Data; 
using System.Drawing; 
using System.Text; 
using System.Windows.Forms; 
using System.Runtime.InteropServices; 
using System.Threading; 
using System.Globalization; 
namespace TestLeak 
{ 
    public partial class Form1 : Form 
    { 
     Thread th; 
     public Form1() 
     { 
      InitializeComponent(); 

     } 
    private class CHwndItem 
    { 
     private IntPtr mHWnd; 
     private string m_Caption; 
     public string Caption 
     { 
      get { return m_Caption; } 
      set { m_Caption = value; } 
     } 

     [DllImport("user32.dll", SetLastError = true)] 
     static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); 
     [DllImport("shell32.dll", CharSet = CharSet.Auto)] 
     private static extern IntPtr SHGetFileInfo(string pszPath, uint dwFileAttributes, ref SHFILEINFO psfi, uint cbFileInfo, uint uFlags); 
     [DllImport("kernel32.dll", SetLastError = true)] 
     [PreserveSig] 
     public static extern uint GetModuleFileName 
     (
      [In] 
    IntPtr hModule, 
     [Out] 
    StringBuilder lpFilename, 
     [In] 
    [MarshalAs(UnmanagedType.U4)] 
    int nSize 
); 
     [DllImport("kernel32.dll", SetLastError = true)] 
     [return: MarshalAs(UnmanagedType.Bool)] 
     static extern bool CloseHandle(IntPtr hObject); 

     [System.Runtime.InteropServices.DllImport("user32.dll", CharSet = CharSet.Auto)] 
     extern static bool DestroyIcon(IntPtr handle); 

     private Icon m_Icon; 
     public Icon Icon 
     { 
      get { return m_Icon; } 
     } 
     [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] 
     struct SHFILEINFO 
     { 
      public IntPtr hIcon; 
      public IntPtr iIcon; 
      public uint dwAttributes; 
      [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] 
      public string szDisplayName; 
      [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)] 
      public string szTypeName; 
     } 
     [Flags] 
     public enum ProcessAccessFlags : uint 
     { 
      All = 0x001F0FFF, 
      Terminate = 0x00000001, 
      CreateThread = 0x00000002, 
      VMOperation = 0x00000008, 
      VMRead = 0x00000010, 
      VMWrite = 0x00000020, 
      DupHandle = 0x00000040, 
      SetInformation = 0x00000200, 
      QueryInformation = 0x00000400, 
      Synchronize = 0x00100000 
     } 
     [DllImport("kernel32.dll")] 
     static extern IntPtr OpenProcess(ProcessAccessFlags dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, int dwProcessId); 
     public const int GCL_HICONSM = -34; 
     public const int GCL_HICON = -14; 

     public const int ICON_SMALL = 0; 
     public const int ICON_BIG = 1; 
     public const int ICON_SMALL2 = 2; 

     private const Int32 ANYSIZE_ARRAY = 1; 
     private const UInt32 TOKEN_QUERY = 0x0008; 
     private const UInt32 TOKEN_ADJUST_PRIVILEGES = 0x0020; 
     private const string SE_SHUTDOWN_NAME = "SeShutdownPrivilege"; 
     private const UInt32 SE_PRIVILEGE_ENABLED = 0x00000002; 

     private const uint FILE_SHARE_READ = 0x00000001; 
     private const uint FILE_SHARE_WRITE = 0x00000002; 
     private const uint FILE_SHARE_DELETE = 0x00000004; 

     private const uint FILE_ATTRIBUTE_READONLY = 0x00000001; 
     private const uint FILE_ATTRIBUTE_HIDDEN = 0x00000002; 
     private const uint FILE_ATTRIBUTE_SYSTEM = 0x00000004; 
     private const uint FILE_ATTRIBUTE_DIRECTORY = 0x00000010; 
     private const uint FILE_ATTRIBUTE_ARCHIVE = 0x00000020; 
     private const uint FILE_ATTRIBUTE_DEVICE = 0x00000040; 
     private const uint FILE_ATTRIBUTE_NORMAL = 0x00000080; 
     private const uint FILE_ATTRIBUTE_TEMPORARY = 0x00000100; 
     private const uint FILE_ATTRIBUTE_SPARSE_FILE = 0x00000200; 
     private const uint FILE_ATTRIBUTE_REPARSE_POINT = 0x00000400; 
     private const uint FILE_ATTRIBUTE_COMPRESSED = 0x00000800; 
     private const uint FILE_ATTRIBUTE_OFFLINE = 0x00001000; 
     private const uint FILE_ATTRIBUTE_NOT_CONTENT_INDEXED = 0x00002000; 
     private const uint FILE_ATTRIBUTE_ENCRYPTED = 0x00004000; 

     private const uint GENERIC_READ = 0x80000000; 
     private const uint GENERIC_WRITE = 0x40000000; 
     private const uint GENERIC_EXECUTE = 0x20000000; 
     private const uint GENERIC_ALL = 0x10000000; 
     private const int SHGFI_SMALLICON = 0x1; 
     private const int SHGFI_LARGEICON = 0x0; 
     private const int SHGFI_ICON = 0x100; 
     private const int SHGFI_USEFILEATTRIBUTES = 0x10; 
     public IntPtr HWnd 
     { 
      get { return mHWnd; } 
      set 
      { 
       mHWnd = value; 

       m_Icon = GetAppIcon(mHWnd); 
       uint thID; 
       GetWindowThreadProcessId(value, out thID); 
       IntPtr processHwnd = OpenProcess(0, false, (int)thID); 
       StringBuilder path = new StringBuilder(' ', 255); 

       GetModuleFileName(processHwnd, path, path.Length); 
       SHFILEINFO fi = new SHFILEINFO(); 

       SHGetFileInfo(@"C:\Program Files\Mozilla Firefox\firefox.exe", FILE_ATTRIBUTE_NORMAL, ref fi, (uint)System.Runtime.InteropServices.Marshal.SizeOf(fi), SHGFI_LARGEICON | SHGFI_USEFILEATTRIBUTES); 

       //IntPtr hIcon = new IntPtr(
       //CloseHandle(processHwnd); 
       //m_Icon = Icon.FromHandle(hIcon); 
       //DestroyIcon(hIcon); 
      } 
     } 

     public static IntPtr GetClassLongPtr(IntPtr hWnd, int nIndex) 
     { 
      if (IntPtr.Size > 4) 
       return GetClassLongPtr64(hWnd, nIndex); 
      else 
       return // new IntPtr(
        GetClassLongPtr32(hWnd, nIndex); 
     } 

     [DllImport("user32.dll", EntryPoint = "GetClassLong")] 
     public static extern IntPtr GetClassLongPtr32(IntPtr hWnd, int nIndex); 

     [DllImport("user32.dll", EntryPoint = "GetClassLongPtr")] 
     public static extern IntPtr GetClassLongPtr64(IntPtr hWnd, int nIndex); 

     [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)] 
     static extern IntPtr SendMessage(IntPtr hWnd, int Msg, int wParam, int lParam); 
     public const int WM_GETICON = 0x7F; 
     public static Icon GetAppIcon(IntPtr hwnd) 
     { 
      int try_icon_type = ICON_SMALL2; 
      IntPtr iconHandle = SendMessage(hwnd, WM_GETICON, ICON_SMALL2, 0); 

      if (iconHandle == IntPtr.Zero) 
      { 
       try_icon_type = ICON_SMALL; 
       iconHandle = SendMessage(hwnd, WM_GETICON, try_icon_type, 0); 
      } 
      if (iconHandle == IntPtr.Zero) 
      { 
       try_icon_type = ICON_BIG; 
       iconHandle = SendMessage(hwnd, WM_GETICON, try_icon_type, 0); 
      } 
      //   if (iconHandle == IntPtr.Zero) 
      //   { 
      //try_icon_type = GCL_HICON; 
      //    iconHandle = GetClassLongPtr(hwnd, try_icon_type); 
      //   } 
      if (iconHandle == IntPtr.Zero) 
      { 
       try_icon_type = GCL_HICONSM; 
       iconHandle = GetClassLongPtr(hwnd, try_icon_type); 
      } 
      if (iconHandle == IntPtr.Zero) 
       return null; 
      System.Diagnostics.Debug.WriteLine(try_icon_type); 
      Icon icn = Icon.FromHandle(iconHandle); 
      DestroyIcon(iconHandle); 
      return icn; 
     } 
    } 
     int GetHandle() 
     { 
      if (txt_Dec.Text.Trim().Length > 0) 
      { 
       return int.Parse(txt_Dec.Text); 
      } 
      else 
      { 
       return int.Parse(txt_Hex.Text, NumberStyles.HexNumber); 
      } 
     } 
     private void button1_Click(object sender, EventArgs e) 
     { 
      th = new Thread(new ThreadStart(ThreadProc)); 
      th.IsBackground = true; 
      th.Start(); 
     } 

     private void ThreadProc() 
     { 
      for (int i = 0; i < int.Parse(textBox1.Text); i++) 
      { 

       CHwndItem hi = new CHwndItem(); 
       hi.HWnd = new IntPtr(GetHandle()); 
       Invoke(new MethodInvoker(delegate() 
       { 
        lbl_incr.Text = i.ToString(); 
       })); 


      } 
      MessageBox.Show("Done"); 
     } 

     private void button2_Click(object sender, EventArgs e) 
     { 
      CHwndItem hi = new CHwndItem(); 
      hi.HWnd = new IntPtr(GetHandle()); 
      pictureBox1.Image = hi.Icon.ToBitmap(); 
     } 

     private void button3_Click(object sender, EventArgs e) 
     { 
      if (th.ThreadState == ThreadState.Running) 
      { 
       btn_Pause.Text = "Resume"; 
       th.Suspend(); 
      } 
      else 
      { 
       btn_Pause.Text = "Pause"; 
       th.Resume(); 
      } 
     } 
    } 
} 

Form1.Designer.cs

namespace TestLeak 
{ 
    partial class Form1 
    { 
     /// <summary> 
     /// Required designer variable. 
     /// </summary> 
     private System.ComponentModel.IContainer components = null; 

     /// <summary> 
     /// Clean up any resources being used. 
     /// </summary> 
     /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param> 
     protected override void Dispose(bool disposing) 
     { 
      if (disposing && (components != null)) 
      { 
       components.Dispose(); 
      } 
      base.Dispose(disposing); 
     } 

     #region Windows Form Designer generated code 

     /// <summary> 
     /// Required method for Designer support - do not modify 
     /// the contents of this method with the code editor. 
     /// </summary> 
     private void InitializeComponent() 
     { 
      this.button1 = new System.Windows.Forms.Button(); 
      this.textBox1 = new System.Windows.Forms.TextBox(); 
      this.txt_Dec = new System.Windows.Forms.TextBox(); 
      this.label1 = new System.Windows.Forms.Label(); 
      this.label2 = new System.Windows.Forms.Label(); 
      this.button2 = new System.Windows.Forms.Button(); 
      this.pictureBox1 = new System.Windows.Forms.PictureBox(); 
      this.lbl_incr = new System.Windows.Forms.Label(); 
      this.btn_Pause = new System.Windows.Forms.Button(); 
      this.txt_Hex = new System.Windows.Forms.TextBox(); 
      this.label3 = new System.Windows.Forms.Label(); 
      ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).BeginInit(); 
      this.SuspendLayout(); 
      // 
      // button1 
      // 
      this.button1.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(136))); 
      this.button1.Location = new System.Drawing.Point(15, 99); 
      this.button1.Name = "button1"; 
      this.button1.Size = new System.Drawing.Size(75, 23); 
      this.button1.TabIndex = 0; 
      this.button1.Text = "Start"; 
      this.button1.UseVisualStyleBackColor = true; 
      this.button1.Click += new System.EventHandler(this.button1_Click); 
      // 
      // textBox1 
      // 
      this.textBox1.Location = new System.Drawing.Point(90, 64); 
      this.textBox1.Name = "textBox1"; 
      this.textBox1.Size = new System.Drawing.Size(81, 20); 
      this.textBox1.TabIndex = 1; 
      // 
      // txt_Dec 
      // 
      this.txt_Dec.Location = new System.Drawing.Point(90, 23); 
      this.txt_Dec.Name = "txt_Dec"; 
      this.txt_Dec.Size = new System.Drawing.Size(81, 20); 
      this.txt_Dec.TabIndex = 2; 
      // 
      // label1 
      // 
      this.label1.AutoSize = true; 
      this.label1.Location = new System.Drawing.Point(13, 29); 
      this.label1.Name = "label1"; 
      this.label1.Size = new System.Drawing.Size(86, 13); 
      this.label1.TabIndex = 3; 
      this.label1.Text = "Handle (decimal)"; 
      // 
      // label2 
      // 
      this.label2.AutoSize = true; 
      this.label2.Location = new System.Drawing.Point(12, 67); 
      this.label2.Name = "label2"; 
      this.label2.Size = new System.Drawing.Size(31, 13); 
      this.label2.TabIndex = 3; 
      this.label2.Text = "Loop"; 
      // 
      // button2 
      // 
      this.button2.Location = new System.Drawing.Point(167, 153); 
      this.button2.Name = "button2"; 
      this.button2.Size = new System.Drawing.Size(103, 23); 
      this.button2.TabIndex = 4; 
      this.button2.Text = "Test Handle Icon"; 
      this.button2.UseVisualStyleBackColor = true; 
      this.button2.Click += new System.EventHandler(this.button2_Click); 
      // 
      // pictureBox1 
      // 
      this.pictureBox1.Location = new System.Drawing.Point(167, 182); 
      this.pictureBox1.Name = "pictureBox1"; 
      this.pictureBox1.Size = new System.Drawing.Size(100, 50); 
      this.pictureBox1.TabIndex = 5; 
      this.pictureBox1.TabStop = false; 
      // 
      // lbl_incr 
      // 
      this.lbl_incr.AutoSize = true; 
      this.lbl_incr.Location = new System.Drawing.Point(23, 166); 
      this.lbl_incr.Name = "lbl_incr"; 
      this.lbl_incr.Size = new System.Drawing.Size(10, 13); 
      this.lbl_incr.TabIndex = 3; 
      this.lbl_incr.Text = "-"; 
      // 
      // btn_Pause 
      // 
      this.btn_Pause.Location = new System.Drawing.Point(15, 182); 
      this.btn_Pause.Name = "btn_Pause"; 
      this.btn_Pause.Size = new System.Drawing.Size(75, 23); 
      this.btn_Pause.TabIndex = 6; 
      this.btn_Pause.Text = "Pause"; 
      this.btn_Pause.UseVisualStyleBackColor = true; 
      this.btn_Pause.Click += new System.EventHandler(this.button3_Click); 
      // 
      // txt_Hex 
      // 
      this.txt_Hex.Location = new System.Drawing.Point(236, 23); 
      this.txt_Hex.Name = "txt_Hex"; 
      this.txt_Hex.Size = new System.Drawing.Size(81, 20); 
      this.txt_Hex.TabIndex = 2; 
      // 
      // label3 
      // 
      this.label3.AutoSize = true; 
      this.label3.Location = new System.Drawing.Point(189, 29); 
      this.label3.Name = "label3"; 
      this.label3.Size = new System.Drawing.Size(32, 13); 
      this.label3.TabIndex = 3; 
      this.label3.Text = "(Hex)"; 
      // 
      // Form1 
      // 
      this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 
      this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 
      this.ClientSize = new System.Drawing.Size(318, 266); 
      this.Controls.Add(this.btn_Pause); 
      this.Controls.Add(this.pictureBox1); 
      this.Controls.Add(this.button2); 
      this.Controls.Add(this.lbl_incr); 
      this.Controls.Add(this.label3); 
      this.Controls.Add(this.label2); 
      this.Controls.Add(this.label1); 
      this.Controls.Add(this.txt_Hex); 
      this.Controls.Add(this.txt_Dec); 
      this.Controls.Add(this.textBox1); 
      this.Controls.Add(this.button1); 
      this.Name = "Form1"; 
      this.Text = "Form1"; 
      ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).EndInit(); 
      this.ResumeLayout(false); 
      this.PerformLayout(); 

     } 

     #endregion 

     private System.Windows.Forms.Button button1; 
     private System.Windows.Forms.TextBox textBox1; 
     private System.Windows.Forms.TextBox txt_Dec; 
     private System.Windows.Forms.Label label1; 
     private System.Windows.Forms.Label label2; 
     private System.Windows.Forms.Button button2; 
     private System.Windows.Forms.PictureBox pictureBox1; 
     private System.Windows.Forms.Label lbl_incr; 
     private System.Windows.Forms.Button btn_Pause; 
     private System.Windows.Forms.TextBox txt_Hex; 
     private System.Windows.Forms.Label label3; 
    } 
} 

回答

4

你有一個實際GDI Objects列其中你可以在任務管理器中顯示(通過去View/Select columns...),您可以監控。

您還可以使用Handles計數器來監視USER對象IIRC。

您通常可以使用VM Size計數器作爲應用程序內存泄漏的指示符(它跟蹤進程已佔用多少地址空間。)這與處理泄漏不一樣,如果泄漏手柄,您可能未必會看到VM Size的增加。

我不認爲你正在泄漏GDI處理與Windows通常會炸燬後〜4K GDI處理system0wide(限制可以通過註冊表IIRC增加,但你明白我的意思。)

0

要準確,你應該使用內存分析器並研究內存句柄。有幾種商業產品可用,如Redgate內存分析器,AutomatedQA,DevParner內存分析器或Intel VTune Analazer。或者,嘗試使用Microsoft的CLR分析器並觀察內存並處理分配和回收。

除此之外,窮人的方法是觀察任務管理器中的GDI對象分配。確保您打勾以在流程視圖中顯示該列。另一種選擇是使用sysinternal中的進程管理器,您可以自定義以查看與您的進程一起顯示的一整套mnanaged/unmanaged資源。您目前的迭代次數足以突出資源泄漏問題。

+0

你說「內存句柄」,它是KB還是每單元計數? 此外,我已經檢查了任務管理器的「選擇列」中的「GDI對象」,它仍然保持在4x ... 我會發布測試代碼,以便你們測試... – 2010-03-03 09:43:24

+2

GDI對象是在單元。內存句柄指的是創建和使用多少個GDI句柄。如果你繼續創造並現在釋放,你會看到它的數量不斷增加。我猜如果你沒有看到它增加,WM_GETICON可能只給你一個參考地址,但不會創建新的對象。它是創建圖標的對象必須負責銷燬它 – 2010-03-03 09:49:43

+0

其實,如果我泄漏GDI對象, 是否明顯是 我不需要專門測試它? 象例如,XP會出現不穩定等 – 2010-03-03 10:08:37

0

在閱讀WM_GETICON的MSDN Page時,並沒有說明你需要銷燬圖標。這並不是說網頁上的說明,但兩個最有可能的實現是:

  1. 增量圖標上的裁判櫃檯,或
  2. 只返回圖標。

這兩種方法都不會實際分配一個新圖標,但如果實際採用第二種方法,則未能釋放它可能會導致每個窗口類出現一個泄漏圖標。

+0

實際上,圖標句柄用於 Icon.FromHandle(.NET System.Drawing中) MSDN中的例子確實使用DestroyIcon(): http://msdn.microsoft .com/en-us/library/system.drawing.icon.fromhandle%28VS.80%29.aspx – 2010-03-03 10:17:04

+0

在這種情況下,您不會泄漏,因爲Icon終結符會爲您執行此操作。 – erikkallen 2010-03-03 12:54:01