Reputation: 1075
In C#, I use this to get the icon of a window:
IntPtr IconHandle = SendMessage(hwnd, WM_GETICON ... );
Of cause, SendMessage is from
DllImport("user32.dll").
AFAIK, this is needed to clean up:
DestroyIcon(iconHandle);
(again DestroyIcon via DllImport("user32.dll").)
Things seem to work fine, but
what I want to know is:
How do I determine a memory leak is taking place if
I commented out the call to DestroyIcon()?
What I planned to do is to
put the get icon code inside a long loop
without calling DestroyIcon().
To check if memory is leaking, my naive way is to
check if the "commit charge" is accumulating in
"Window Task Manager".
However, after a loop with 100000 iterations ...
Nothing blows up.
Windows XP still runs happily.
I need to find out the way to test this out, because
I want to make sure that my code is correctly
releasing the unmanaged resources, in my development machine and
also in the future end users'.
How do I test it? Or is it that I didn't test it hard enough
(e.g. test with 10^10 iterations instead)?
I post the testing code below:
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;
}
}
Upvotes: 4
Views: 4903
Reputation: 34401
When reading the MSDN Page for WM_GETICON it doesn't say anything about you being required to destroy the icon. It is not stated on that page, but the two most likely implementations are:
Neither of these approaches would actually allocate a new icon, but if the second approach is actually taken, your failure to release it might lead to one leaked icon per window class.
Upvotes: 0
Reputation: 66681
You have an actual GDI Objects
column which you can display in Task Manager (by going to View
/Select columns...
), which you can monitor.
You also have a Handles
counter you can use to monitor USER objects IIRC.
You can typically use the VM Size
counter as an indicator of application memory leaks (it tracks how much address space the process has hogged.) This is not the same as handle leaks, and you may not necessarily see an increase in VM Size
if you leak handles.
I don't think you are leaking GDI handles as Windows will typically blow up after ~4k GDI handles system0wide (limit can be increased via registry IIRC, but you get my point.)
Upvotes: 4
Reputation: 6465
To be accurate you should use memory profiler and study the memory handles. There are several commercial products available such as Redgate memory profiler, AutomatedQA, DevParner memory profiler or intel VTune Analazer. Alternatively try using CLR profiler from microsoft and watch the memory and handle allocation and reclaimed.
Other than that the poor man approach is to watch the the GDI object allocation in Task Manager. Make sure you tick to show that column in the processes view. Another option is to use process explorer from sysinternal and you can customize to view a whole range of mnanaged/unmanaged resources to be displayed along with your process. The number of iterations you have currently would be more than sufficient to highlight the problem with resource leak.
Upvotes: 0