c# - 如何测试未释放的 GDI 对象导致的内存泄漏?

标签 c#

在 C# 中,我使用它来获取窗口的图标:

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

当然,SendMessage 来自
DllImport("user32.dll")。

AFAIK,这是清理所需要的:

DestroyIcon(iconHandle);

(再次通过 DllImport("user32.dll") 销毁图标。)

一切似乎都很好,但是
我想知道的是:

How do I determine a memory leak is taking place if
I commented out the call to DestroyIcon()?

我打算做的是
将获取图标代码放在一个长循环中
无需调用 DestroyIcon()。

要检查内存是否泄漏,我天真的方法是
检查“提交费用”是否在
中累积 “窗口任务管理器”。

然而,经过 100000 次迭代的循环之后......
什么都没有爆炸。 Windows XP 仍然可以愉快地运行。

我需要找到测试的方法,因为
我想确保我的代码是正确的
释放非托管资源,在我的开发机器和
也是 future 最终用户的。

我如何测试它?还是我测试不够努力
(例如,改为使用 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;
    }
}

最佳答案

您有一个实际的 GDI 对象 列,您可以在任务管理器中显示它(通过转到 View/Select columns...) ,您可以对其进行监控。

您还有一个 Handles 计数器,可用于监视 USER 对象 IIRC。

您通常可以使用 VM Size 计数器作为应用程序内存泄漏的指示器(它跟踪进程占用了多少地址空间。)这与句柄泄漏不同,您可以如果泄漏句柄,则不一定会看到 VM 大小 的增加。

我不认为你在泄露 GDI 句柄,因为 Windows 通常会在 ~4k GDI 句柄系统范围内崩溃(限制可以通过注册表 IIRC 增加,但你明白我的意思。)

关于c# - 如何测试未释放的 GDI 对象导致的内存泄漏?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2369965/

相关文章:

c# - XPath 绑定(bind)以读取 wpf 中的 InnerXml

c# - 如何确定构建程序集的 MSBuild 版本?

C# - 如何使用帮助提供程序显示帮助文件?

c# - 在 SQL Server 远程访问方面需要帮助

c# - 反射找不到实现的方法

c# - 如何从 SortedDictionary 获取上一个键?

c# - Linq 和字典的范围

c# - 如何平衡多个处理器内核之间的 UI 负载?

c# - LINQ to XML 设置值,如果它不为空,否则使用构造函数的默认值

c# - 格式化文本框内容