Recently I was working on a mobile project that was experiencing some serious memory leak issues. It turns out that I wasn’t disposing for a SqlCeResultSet properly and on each query (of which there was substantial amount) I was calling New() without Dispose().
After a day of feeling like my project was falling apart before my eyes and getting weird and unreliable results from the .NETCF Remote Profiler I searched far a wide for some way to determine the available memory on a device. On MSDN I found the following code (slightly modified by me) to get relevant information.
Basically two P/Invokes later I had the information I needed, saw that I was in fact leaking memory, quickly determined where, and restored sanity to my project. Within thirty minutes of implementing this class the world was a happier place for me.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
| Imports System.Text
Public Class MEMORYSTATUSINFO
Public Structure MEMORYSTATUS
Public dwLength As UInt32
Public dwMemoryLoad As UInt32
Public dwTotalPhys As UInt32
Public dwAvailPhys As UInt32
Public dwTotalPageFile As UInt32
Public dwAvailPageFile As UInt32
Public dwTotalVirtual As UInt32
Public dwAvailVirtual As UInt32
End Structure
Public Declare Function GlobalMemoryStatus Lib "CoreDll.Dll" _
(ByRef ms As MEMORYSTATUS) As Integer
Public Declare Function GetSystemMemoryDivision Lib "CoreDll.Dll" _
(ByRef lpdwStorePages As UInt32, _
ByRef ldpwRamPages As UInt32, _
ByRef ldpwPageSize As UInt32) As Integer
Public Shared Function ShowMemory() As String
Dim storePages As UInt32
Dim ramPages As UInt32
Dim pageSize As UInt32
Dim res As Integer = _
GetSystemMemoryDivision(storePages, ramPages, pageSize)
' Call the native GlobalMemoryStatus method
' with the defined structure.
Dim memStatus As New MEMORYSTATUS
GlobalMemoryStatus(memStatus)
Dim load As Integer = memStatus.dwMemoryLoad / (1024 * 1024)
Dim totPhys As Integer = memStatus.dwTotalPhys / (1024 * 1024)
Dim availPhys As Integer = memStatus.dwAvailPhys / (1024 * 1024)
Dim totalPageFile As Integer = memStatus.dwTotalPageFile
Dim availPageFile As Integer = memStatus.dwAvailPageFile
Dim totVirtual As Integer = memStatus.dwTotalVirtual / (1024 * 1024)
Dim availVirtual As Integer = memStatus.dwAvailVirtual / (1024 * 1024)
' Use a StringBuilder for the message string.
Dim MemoryInfo As New StringBuilder
MemoryInfo.Append("Memory Load: " _
& load.ToString() & "Mb" & vbCrLf)
MemoryInfo.Append("Total Physical: " _
& totPhys & "Mb" & vbCrLf)
MemoryInfo.Append("Avail Physical: " _
& availPhys & "Mb" & vbCrLf)
MemoryInfo.Append("Total Page File: " _
& totalPageFile.ToString() & vbCrLf)
MemoryInfo.Append("Avail Page File: " _
& availPageFile.ToString() & vbCrLf)
MemoryInfo.Append("Total Virtual: " _
& totVirtual.ToString() & "Mb" & vbCrLf)
MemoryInfo.Append("Avail Virtual: " _
& availVirtual.ToString() & "Mb" & vbCrLf)
' Show the available memory.
Return MemoryInfo.ToString()
End Function
End Class |
Hope this helps someone else find greedy memory hog code in their apps.
I am a large fan of Linq when coding. I think it is a really clean and concise way to access large sets of data (whether in a database, xml, or a IList) quickly, efficiently, and in a clearly readable way.
I have been developing almost exclusively within the compact framework and while the compact framework (3.5) does not include Linq to Sql it does include Linq to XML and Linq to Objects. The problem I had was getting it to work in the compact framework.
While Imports System.Linq always failed to a lack of reference it was because there is no System.Linq.dll. The linq parts of the language are included within System.Core. Add that reference to your project and Linq will work for you.
Don’t know why it took me so long to realize this. But now I’m Linq free.
I also hear that Compact Framework 4.0 will support Linq to Sql.
My favorite logging library log4net work not only on desktop and web applications but can be used on mobile devices with the compact framework.
Granted not all of the Appenders make sense within the compact framework. Since mobile devices have neither a system log nor a console it doesn’t make much sense to have these appenders. For a complete list of supported frameworks and appenders see here.
Applications written in the compact framework don’t have app.config files. This presents an issue when attempting to create an ILog. Here is my work around.
First create an XML that will be copied to source on deployment. Here is an example:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<log4net debug="false">
<appender name="logger" type="log4net.Appender.RollingFileAppender">
<file value="\SD Card\Assessment\DamageAssessmentLog.txt" />
<appendToFile value="true" />
<maxSizeRollBackups value="2" />
<maximumFileSize value="1048576" />
<rollingStyle value="Composite" />
<staticLogFileName value="false" />
<layout type="log4net.Layout.PatternLayout">
<header value="[Header] "/>
<footer value="[Footer] "/>
<conversionPattern value="%newline Date: %date ,Application: %a ,Logger: %logger ,Thread: [%thread] ,Level: %level %newline Message: %message%newline Exception: %exception%newline" />
</layout>
</appender>
<!-- Setup the root category, add the appenders and set the default level -->
<root>
<level value="DEBUG" />
<appender-ref ref="logger" />
</root>
</log4net>
</configuration>
This should all be familiar so far. Basically whatever was going to go into app.config will now be in this file.
Then within your application’s startup you need to explicitly point log4net to that configuration file.
Shared Sub Main()
Try
Dim logconfig = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetName().CodeBase), "logging.config")
log4net.Config.XmlConfigurator.Configure(New FileInfo(logconfig))
Dim logger As ILog = log4net.LogManager.GetLogger("logger")
Using map As formMap = New formMap()
logger.Debug("Staring application")
map.ShowDialog()
logger.Debug("Application closed")
End Using
AssessmentConnector.Instance.CloseConnection()
Catch ex As Exception
MessageBox.Show(ex.ToString)
Finally
log4net.LogManager.Shutdown()
End Try
End Sub
Here you can see that I get the application’s executing path and append the xml file’s name. Then I call the log4net.Config.XmlConfigurator to initialize the logging service from the provided xml file.
Note: Due to limitations of the compact framework it is important that you call log4net.LogManager.Shutdown() to explicitly close your log files. Otherwise you’ll end up with a hanging process and open log files.