Share via


Advanced Basics

Reducing Memory Footprints, Gathering Process Info with MSDNMagProcessMonitor

Ken Spencer

Code download available at:AdvancedBasics0209.exe(209 KB)

Contents

Security

Q I have a question about the memory footprint in an application built using the Microsoft® .NET Framework. I built a little sticky notes utility as part of a learning exercise in a class on coding techniques for Visual Basic® .NET. It was a simple program that had one small hidden form and a Notify icon, but it allocated 12MB of memory! That wouldn't matter for a robust data-driven application, but is there any way to reduce the memory footprint for a small Visual Basic .NET utility?

Q I have a question about the memory footprint in an application built using the Microsoft® .NET Framework. I built a little sticky notes utility as part of a learning exercise in a class on coding techniques for Visual Basic® .NET. It was a simple program that had one small hidden form and a Notify icon, but it allocated 12MB of memory! That wouldn't matter for a robust data-driven application, but is there any way to reduce the memory footprint for a small Visual Basic .NET utility?

A To test your problem, I created a simple Visual Basic .NET Windows® Forms application that had no event code or controls. I ran the program and looked in Task Manager at the memory utilization. The memory used ranged from 7,980KB to 8,020KB, with the default settings for the project in Visual Studio® .NET.

A To test your problem, I created a simple Visual Basic .NET Windows® Forms application that had no event code or controls. I ran the program and looked in Task Manager at the memory utilization. The memory used ranged from 7,980KB to 8,020KB, with the default settings for the project in Visual Studio® .NET.

I recompiled the application in Release mode and checked its memory again. It then showed 6,328KB, so just changing to Release mode reduced the memory footprint considerably. The file size of the .exe also dropped, from 8KB to 6KB.

Next, I added two textboxes and a button control and recompiled and ran the application. This resulted in an application size of 6,724KB. At this point, the total memory size of the application was quite small. I decided to create a similar C# application and check that in order to compare. The C# application with no controls used 6,200KB, so there was very little difference between a C# or Visual Basic .NET application, at least at first glance.

I thought that Windows Forms would add a bit of overhead, so I created a simple console application that I could run at the command prompt. This application would simply write "Hello World" and wait for the user to press a key. No Windows Forms or other sophisticated code was involved. Running this application compiled with Debug resulted in a 4,312KB footprint. This simple console application showed a size of 3,748KB when compiled in Release mode.

So, it appeared that Windows Forms added a good bit of size to the application. That actually makes sense since adding GUI support to any application naturally should add some overhead. It's pretty clear that the Microsoft .NET Framework and Windows Forms architecture was playing a major role in the memory used by the console application.

Before doing any more experiments, I thought it would be good to get more information on memory usage, which became the answer to the next question.

Q Is there a way to find out what processes are running on a system using the .NET Framework?

Q Is there a way to find out what processes are running on a system using the .NET Framework?

A To answer this question, I created a new Windows Forms application named MSDNMagProcessMonitor which uses the Process class in the .NET Framework. To create this application I designed a new Visual Basic .NET app. I renamed Form1 to frmMain, and then added two other forms, frmProcessDetails and frmProcessListOverview. Next, I added a module named ProcessStuff (see Figure 1) which contains all of the functions that deal with processes.

A To answer this question, I created a new Windows Forms application named MSDNMagProcessMonitor which uses the Process class in the .NET Framework. To create this application I designed a new Visual Basic .NET app. I renamed Form1 to frmMain, and then added two other forms, frmProcessDetails and frmProcessListOverview. Next, I added a module named ProcessStuff (see Figure 1) which contains all of the functions that deal with processes.

Figure 1 ProcessStuff Excerpt

Module ProcessStuff #Region "Public Methods" Function GetProcesses() As DataSet Dim Processes() As Process Dim CurrentProcess As Process Dim dr As DataRow Dim ds As New DataSet("SystemInfo") Dim dt As DataTable = ds.Tables.Add("Processes") dt.Columns.Add("ProcessID", _ Type.GetType("System.Int32")) dt.Columns.Add("ProcessName", _ Type.GetType("System.String")) dt.Columns.Add("StartTime", _ Type.GetType("System.String")) dt.Columns.Add("BasePriority", _ Type.GetType("System.Int32")) dt.Columns.Add("UserProcessorTime", _ Type.GetType("System.String")) Processes = Process.GetProcesses() Try For Each CurrentProcess In Processes dr = dt.NewRow() dr("ProcessID") = CurrentProcess.Id dr("ProcessName") = _ CurrentProcess.ProcessName dr("StartTime") = _ CurrentProcess.StartTime dr("BasePriority") = _ CurrentProcess.BasePriority() dr("UserProcessorTime") = _ CurrentProcess.UserProcessorTime.TotalSeconds dt.Rows.Add(dr) Next Catch exc As Exception Err.Raise(23, , exc.Message) End Try Return ds End Function Function GetProcessNamesOnly() As DataSet Dim Processes() As Process Dim CurrentProcess As Process Dim dr As DataRow Dim ds As New DataSet("SystemProcesses") Dim dt As DataTable = ds.Tables.Add("ProcessNames") Dim aProcessNames(200) As String Dim ProcessFlag As Boolean = True Dim sCurrentName, sLastName As String Dim i As Integer Dim iUpperBoundSources As Integer dt.Columns.Add("ProcessName", Type.GetType("System.String")) Processes = Process.GetProcesses() Try For Each CurrentProcess In Processes iUpperBoundSources = UBound(aProcessNames) If i >= 200 Then ReDim Preserve aProcessNames(iUpperBoundSources + 1) End If aProcessNames(i) = CurrentProcess.ProcessName i += 1 Next Catch exc As Exception Err.Raise(23, , exc.Message) End Try Array.Sort(aProcessNames) For i = 0 To UBound(aProcessNames) sCurrentName = aProcessNames(i) If sCurrentName = sLastName Then ProcessFlag = False Else ProcessFlag = True sLastName = sCurrentName End If If ProcessFlag Then dr = dt.NewRow() dr("ProcessName") = sCurrentName dt.Rows.Add(dr) End If Next Return ds End Function

The Process class, part of the System.Diagnostics namespace, returns an array of the running processes on the system, each represented by its own process object in the array. It not only returns the processes, but can also return individual process information such as various aspects of memory usage, the amount of time the process has been running, and so forth. You can also use the process class to start and stop processes.

The features of this class used most often in this application are calls to the GetProcesses method or one of the other GetProcessXXX methods. These methods each build an array of process objects. Then the code loops through the array and pulls the information about the process from each object. This approach is illustrated in the code in Figure 2, which comes from the GetProcesses function of my ProcessStuff module. You can see from this code how to walk through the array of Process objects returned by the call to the GetProcesses method. A DataRow is then created for each Process object in the array and loaded with information about the process. In the final step, the Add method is called to add the row to the DataTable.

Figure 2 Getting Process Info

Processes = Process.GetProcesses() Try For Each CurrentProcess In Processes dr = dt.NewRow() dr("ProcessID") = CurrentProcess.Id dr("ProcessName") = CurrentProcess.ProcessName dr("StartTime") = CurrentProcess.StartTime dr("BasePriority") = _ CurrentProcess.BasePriority() dr("UserProcessorTime") = _ CurrentProcess.UserProcessorTime.TotalSeconds dt.Rows.Add(dr) Next Catch exc As Exception Err.Raise(23, , exc.Message) End Try Return ds

After the End Try exception handler statement, the DataSet function is returned. The other functions in the ProcessStuff module provide support for similar functions related to processes. For instance, the GetProcessNamesOnly function returns a list of process names in a DataSet. This list is generated to return only unique names so you can later return process details by name. This allows you to obtain a list of all instances of Notepad or any other process, which is accomplished in the GetProcessDetailsByProcessName function by a call to the Process.GetProcessesByName method.

I ran the sample application from the first question, which had no controls on the form. Then I ran the MSDNMagProcessMonitor and selected the sample application. Finally, I inspected the information for this process. The results are shown in Figure 3.

Figure 3 Process Information

The interesting thing I found with this tool is that when I executed my generic program from the first question, 32 modules (.dll or .exe) were loaded along with it, which might explain why the application is consuming a large amount of memory. Looking at the simple console application from the first question, you can see that it only has 32 modules loaded. It's also worth noting that the working set size from the Process class is the same size shown in Task Manager.

Figure 4 shows the MSDNMagProcessMonitor displaying a list of the modules. I sorted the module list by file path and you can see that the second set of 16 modules are from the .NET Framework.

Figure 4 Module List

Figure 4** Module List **

It is interesting to note that the module list shows the application's base memory size as 40960 bytes (40KB) while Task Manager (and the working set) shows it much larger, at 8308KB. So it appears that even a simple application with a GUI interface is going to run about 8000KB. When running the application over and over, the working set size changes somewhat. Much of the bulk of the working set size comes from the .NET Framework support for the application.

Security

You don't want to allow unauthorized users to access the process monitor because they could run the application, find out what resources are running on the system, and use this information to attempt a break-in. The operating system provides safeguards against certain activities related to processes, but it is best to protect your applications yourself.

To finish off this utility, I added a simple module to the project called SecurityStuff.vb. This module has a function called CheckRole that can determine if a user belongs to a certain security group. In this case, I want to make sure that all users of this application are administrators. To do this, you simply call CheckRole("Administrator") by user; if they are an administrator it returns True, and if not it returns False. I placed a call to this function in the Page_Load event of the main form like this:

If Not CheckRole("Administrator") Then MsgBox("You are not allowed to run this application") End End If

Now the application can only be run by administrators.

I continue to be amazed by all that can be done using the Microsoft .NET Framework. Once you get beyond the basics, you can do a lot without driving up the memory requirements a great deal. Furthermore, it is so easy to access process information and other operating system-specific data using the .NET Framework.

Send questions and comments for Ken to basics@microsoft.com.

Ken Spencerworks for 32X Tech (https://www.32X.com). 32X provides training, software development, and consulting services on Microsoft technologies.