I have an application that logs messages to the screen using a TextBox. The update function uses some Win32 functions to ensure that the box automatically scrolls to the end unless the user is viewing another line. Here is the update function:
private bool logToScreen = true;
// Constants for extern calls to various scrollbar functions
private const int SB_HORZ = 0x0;
private const int SB_VERT = 0x1;
private const int WM_HSCROLL = 0x114;
private const int WM_VSCROLL = 0x115;
private const int SB_THUMBPOSITION = 4;
private const int SB_BOTTOM = 7;
private const int SB_OFFSET = 13;
[DllImport("user32.dll")]
static extern int SetScrollPos(IntPtr hWnd, int nBar, int nPos, bool bRedraw);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern int GetScrollPos(IntPtr hWnd, int nBar);
[DllImport("user32.dll")]
private static extern bool PostMessageA(IntPtr hWnd, int nBar, int wParam, int lParam);
[DllImport("user32.dll")]
static extern bool GetScrollRange(IntPtr hWnd, int nBar, out int lpMinPos, out int lpMaxPos);
private void LogMessages(string text)
{
if (this.logToScreen)
{
bool bottomFlag = false;
int VSmin;
int VSmax;
int sbOffset;
int savedVpos;
// Make sure this is done in the UI thread
if (this.txtBoxLogging.InvokeRequired)
{
this.txtBoxLogging.Invoke(new TextBoxLoggerDelegate(LogMessages), new object[] { text });
}
else
{
// Win32 magic to keep the textbox scrolling to the newest append to the textbox unless
// the user has moved the scrollbox up
sbOffset = (int)((this.txtBoxLogging.ClientSize.Height - SystemInformation.HorizontalScrollBarHeight) / (this.txtBoxLogging.Font.Height));
savedVpos = GetScrollPos(this.txtBoxLogging.Handle, SB_VERT);
GetScrollRange(this.txtBoxLogging.Handle, SB_VERT, out VSmin, out VSmax);
if (savedVpos >= (VSmax - sbOffset - 1))
bottomFlag = true;
this.txtBoxLogging.AppendText(text + Environment.NewLine);
if (bottomFlag)
{
GetScrollRange(this.txtBoxLogging.Handle, SB_VERT, out VSmin, out VSmax);
savedVpos = VSmax - sbOffset;
bottomFlag = false;
}
SetScrollPos(this.txtBoxLogging.Handle, SB_VERT, savedVpos, true);
PostMessageA(this.txtBoxLogging.Handle, WM_VSCROLL, SB_THUMBPOSITION + 0x10000 * savedVpos, 0);
}
}
}
Now the strange thing is that the text box consumes at least double the memory that I would expect it to. For example, when there are ~1MB of messages in the TextBox, the application can consume up to 6MB of memory (in addition to what it uses when the logToScreen is set to false). The increase is always at least double what I expect, and (as in my example) sometimes more.
What is more strange is that using:
this.txtBoxLogging.Clear();
for (int i = 0; i < 3; i++)
{
GC.Collect();
GC.WaitForPendingFinalizers();
}
Does not free the memory (in fact, it increases slightly).
Any idea where the memory is going as I'm logging these messages? I don't believe it has anything to do with the Win32 calls, but I've included it to be thorough.
EDIT:
The first couple of responses I got were related to how to track a memory leak, so I thought I should share my methodology. I used a combination of WinDbg and perfmon to track the memory use over time (from a couple hours to days). The total number of bytes on all CLR heaps does not increase by more than I expect, but the total number of private bytes steadily increases as more messages are logged. This makes WinDbg less useful, as it's tools (sos) and commands (dumpheap, gcroot, etc.) are based on .NET's managed memory.
This is likely why GC.Collect() can not help me, as it is only looking for free memory on the CLR heap. My leak appears to be in un-managed memory.