Welcome to MSDN Blogs Sign in | Join | Help

What is your computer doing with all that memory? Write your own memory browser

 

What is your computer doing with all that memory? There are various kinds of memory allocated and used in each process. These include:

·         Managed memory (VB.Net, C#, managed C++)

·         Heap memory

·         Stacks

·         Images (files loaded into a process)

 

VirtualAlloc is the basis of these allocations. If a process needs more of these types of memory, VirtualAlloc is called.

 

You can inspect the various kinds of memory by running the sample code below. It offers a list of current processes, from which you dbl-click one to inspect.

 

Once you choose a process, a snapshot of all Virtual memory allocations is shown in a ListView, along with the size, type, etc. If it’s an image, the filename is shown too.  Hovering over addresses shows the memory contents in a tooltip. Double click to open another window with the entire memory contents. You can read strings in memory in these displays.

 

One can imagine this being the base of your own custom Task Manager. Perhaps you want to search for all strings in all processes. “Microsoft” occurs quite often!

 

Imagine taking a memory snapshot of a process, doing something in that process, then taking another snapshot and displaying the difference.

 

Caveats: you may not have rights to read some processes or some memory. Also, if you build to 32 bit (Project Options->Compile->AdvancedCompileOptions->TargetCPU->AnyCpu) and run on 64 bit, you might have problems.

 

It can examine both 32 and 64 bit processes.  If you get Access Denied on some processes, you might have better luck running it as Administrator

 

Start Visual Studio 2008

File->New Project->VB WPF Application.

 

Open Window1.xaml.vb, replace the contents (about 650 lines) with the sample below.

 

 

See also:

Create your own media browser: Display your pictures, music, movies in a XAML tooltip

How to Create dynamic XAML to display arbitrary XML

Remove double spaces from pasted code samples in blog

 

 

 

 

<code sample>

Imports System.Runtime.InteropServices

 

Class Window1

    Private WithEvents _btnRefresh As Button

 

    Private Sub Window1_Loaded(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles MyBase.Loaded

        Me.Width = 800

        Me.Height = 800

 

        RefreshContent()

    End Sub

 

    Sub RefreshContent()

        Dim q = From proc In System.Diagnostics.Process.GetProcesses _

                Select proc.Id, _

                       proc.ProcessName, _

                       proc.WorkingSet64, _

                       proc.PrivateMemorySize64, _

                       Is64 = ProcessType(proc) _

                Order By ProcessName

        Me.Title = "Choose a process. Count = " + q.Count.ToString

        Dim XAML = _

        <Grid

            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

            >

            <Grid.RowDefinitions>

                <RowDefinition MaxHeight="20"/>

                <RowDefinition/>

            </Grid.RowDefinitions>

            <StackPanel Orientation="Horizontal" Grid.Row="0">

                <Button Name="btnRefresh">Refresh</Button>

            </StackPanel>

            <DockPanel Grid.Row="1" Name="dp">

 

            </DockPanel>

        </Grid>

        Dim gr = CType(System.Windows.Markup.XamlReader.Load(XAML.CreateReader), Grid)

        _btnRefresh = CType(gr.FindName("btnRefresh"), Button)

        Dim dp = CType(gr.FindName("dp"), DockPanel)

        dp.Children.Add(New Browse(q, delDblClick:=AddressOf OnProcWindDblClick))

        Me.Content = gr

    End Sub

 

    Shared Function ProcessType(ByVal proc As Process) As String

        Dim is32 = False

        If IntPtr.Size = 4 Then ' 64 bit=8, 32=4

            is32 = True

        End If

        Dim retProcType = "64"

        Try

            Dim IsrunningUnderWow64 = False

            If IsWow64Process(proc.Handle, IsrunningUnderWow64) AndAlso IsrunningUnderWow64 Then

            End If

            If IsrunningUnderWow64 OrElse is32 Then

                retProcType = "32"

            End If

 

        Catch ex As Exception

            retProcType = ex.Message

        End Try

        Return retProcType

    End Function

 

    <DllImport("Kernel32.dll", SetLastError:=True, CallingConvention:=CallingConvention.Winapi)> _

        Public Shared Function IsWow64Process( _

        ByVal hProcess As IntPtr, _

        <MarshalAs(UnmanagedType.Bool)> ByRef wow64Process As Boolean) As <MarshalAs(UnmanagedType.Bool)> Boolean

    End Function

 

    Sub OnbtnRefresh_Click() Handles _btnRefresh.Click

        RefreshContent()

    End Sub

 

    Sub OnProcWindDblClick(ByVal sender As Object, ByVal e As RoutedEventArgs)

        Try

            Dim lv = TryCast(sender, Browse)

            If lv IsNot Nothing Then

                Dim tb = TryCast(e.OriginalSource, TextBlock)

                If tb IsNot Nothing Then

                    Dim dt = tb.DataContext

                    If dt IsNot Nothing Then

                        Dim procTarget = tb.Text

                        Dim q = From a In Process.GetProcessesByName(procTarget)

                        If q.Count > 0 Then

                            Dim oWin = New VirtMemWindow(q.First) With {.Owner = Me}

                        End If

                    End If

                End If

            End If

        Catch ex As Exception

            MessageBox.Show(ex.Message)

        End Try

    End Sub

 

    Class VirtMemWindow

        Inherits Window

        Private _proc As Process

 

        Private WithEvents _btnAggregate As Button

        Private _Is32 As Boolean

 

        Sub New(ByVal proc As Process)

            Dim mbi As New MEMORY_BASIC_INFORMATION

            _proc = proc

            Dim ptype = ProcessType(_proc)

 

            If ptype = "32" Then

                _Is32 = True

            ElseIf ptype = "64" Then

                _Is32 = False

            Else

                Throw New InvalidOperationException(ptype)

            End If

            If IntPtr.Size = 4 AndAlso Not _Is32 Then

                Throw New InvalidOperationException("32 bit app can't examine 64 bit process")

            End If

 

            Dim lpMem As UInt64 = 0

            Do While VirtualQueryEx(_proc.Handle, New UIntPtr(lpMem), mbi, Marshal.SizeOf(mbi)) = Marshal.SizeOf(mbi)

                _virtAllocs.Add(mbi)

                lpMem = mbi.BaseAddress.ToUInt64 + mbi.RegionSize.ToUInt64

            Loop

            Dim XAML = _

            <Grid

                xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

                >

                <Grid.RowDefinitions>

                    <RowDefinition MaxHeight="20"/>

                    <RowDefinition/>

                </Grid.RowDefinitions>

                <StackPanel Orientation="Horizontal" Grid.Row="0">

                    <Button Name="btnAgg">Aggregate</Button>

                </StackPanel>

                <DockPanel Grid.Row="1" Name="dp">

 

                </DockPanel>

            </Grid>

            Dim gr = CType(System.Windows.Markup.XamlReader.Load(XAML.CreateReader), Grid)

            _btnAggregate = CType(gr.FindName("btnAgg"), Button)

            Dim dp = CType(gr.FindName("dp"), DockPanel)

            If _virtAllocs.Count = 0 Then

                dp.Children.Add(New TextBlock With {.Text = "No items found. Perhaps 64/32 bit or Rights issue"})

            Else

                Dim q = From a In _virtAllocs _

                        Select AllocationBase = Format64or32(a.AllocationBase), _

                                BaseAddress = Format64or32(a.BaseAddress), _

                                RegionSize = Format64or32(a.RegionSize), _

                                Filename = GetFileNameFromMBI(a), _

                                AllocationProtect = a.AllocationProtect.ToString, _

                                AllocationState = a.State.ToString, _

                                Type = CType(a.lType, AllocationType).ToString _

                                Order By Filename.ToString.Length Descending

 

                Me.Title += String.Format("{0} Virtual Memory Use #items={1}  ", proc.ProcessName, q.Count)

 

                dp.Children.Add(New Browse(q, _

                                        delMouseMove:=AddressOf On_VirtmemMouseMove, _

                                        deldblclick:=AddressOf On_VirtmemDblClick))

 

            End If

            Me.Content = gr

            Me.Show()

        End Sub

 

        Function Format64or32(ByVal num As UIntPtr) As String

            Dim retval = ""

            If _Is32 Then

                retval = num.ToUInt32.ToString("x8")

            Else

                retval = num.ToUInt64.ToString("x16")

            End If

            Return retval

        End Function

 

        Sub OnbtnAggClick() Handles _btnAggregate.Click

            Dim qAgg = From a In _virtAllocs _

                       Group By AllocationType = a.lType.ToString Into Tot = Sum(a.RegionSize.ToUInt64), Cnt = Count(a.RegionSize.ToUInt64 > 0)

            Dim oWin = New Window With {.Title = "Aggregate"}

            oWin.Content = New Browse(qAgg)

            oWin.Owner = Me

            oWin.Show()

        End Sub

 

        Private _virtAllocs As New List(Of MEMORY_BASIC_INFORMATION)

 

        Sub On_VirtmemDblClick(ByVal sender As Object, ByVal e As RoutedEventArgs)

            Try

                Dim lv = TryCast(sender, Browse)

                If lv IsNot Nothing Then

                    Dim tb = TryCast(e.OriginalSource, TextBlock)

                    If tb IsNot Nothing Then

                        Select Case tb.Name

                            Case "AllocationBase", "BaseAddress"

                                ClearPriorToolTipIfAny()

                                Dim baseAddr = Convert.ToInt64(tb.Text, 16) ' convert from hex (base 16)

                                Dim allocQuery = From a In _virtAllocs _

                                        Where If(tb.Name = "AllocationBase", a.AllocationBase, a.BaseAddress) = baseAddr

 

 

                                If allocQuery.Count > 0 Then

                                    Dim virtAlloc = allocQuery.First

                                    Dim strAddrDump = GetAddressDump( _

                                        virtAlloc.BaseAddress, _

                                        virtAlloc.RegionSize, _

                                        nMax:=0 _

                                        )

 

                                    Dim tbxDump = New TextBox With { _

                                        .Text = strAddrDump, _

                                        .VerticalScrollBarVisibility = ScrollBarVisibility.Auto, _

                                        .BorderThickness = New Windows.Thickness(0), _

                                        .FontFamily = New FontFamily("Courier New"), _

                                    .FontSize = 10 _

                                    }

                                    Dim oWin = New Window With { _

                                        .Title = String.Format("Mem dump Address = {0:x8}, size = {1}", _

                                                                tb.Text, _

                                                                virtAlloc.RegionSize _

                                                                ) _

                                    }

                                    oWin.Content = tbxDump

                                    oWin.Owner = Me

                                    oWin.Show()

                                End If

                        End Select

                    End If

 

                End If

 

            Catch ex As Exception

 

            End Try

 

        End Sub

 

        Sub ClearPriorToolTipIfAny()

            If _LastTipObj IsNot Nothing Then

                Dim lastTip = CType(_LastTipObj.ToolTip, ToolTip)

                lastTip.IsOpen = False

                _LastTipObj = Nothing

            End If

        End Sub

 

        Private _LastTipObj As FrameworkElement

        Sub On_VirtmemMouseMove(ByVal sender As Object, ByVal e As RoutedEventArgs)

            Dim lv = TryCast(sender, Browse)

            If lv IsNot Nothing Then

                Dim tb = TryCast(e.OriginalSource, TextBlock)

                If tb IsNot Nothing Then

                    '                    Dim o = lv.ItemContainerGenerator.ContainerFromItem(tb)

                    If _LastTipObj IsNot Nothing Then

                        Dim lastTip = CType(_LastTipObj.ToolTip, ToolTip)

                        If tb Is _LastTipObj Then ' over same obj: don't create a new tip

                            Return

                        Else

                            ' different object: close the tip

                            lastTip.IsOpen = False

                        End If

                    End If

 

                    Dim dt = tb.DataContext

                    If dt IsNot Nothing Then

                        Dim ttipObj = New ToolTip

                        ttipObj.PlacementTarget = tb

                        ttipObj.Placement = Controls.Primitives.PlacementMode.Bottom

                        Select Case tb.Name

                            Case "AllocationBase", "BaseAddress"

                                Dim baseAddr = Convert.ToUInt64(tb.Text, 16) ' convert from hex (base 16)

                                Dim q = From a In _virtAllocs _

                                        Where If(tb.Name = "AllocationBase", a.AllocationBase, a.BaseAddress) = baseAddr

 

                                If q.Count > 0 Then

                                    Dim mbi = q.First

                                    Dim baddr = If(tb.Name = "AllocationBase", mbi.AllocationBase, mbi.BaseAddress)

                                    If CType(mbi.State, AllocationState) <> AllocationState.MEM_FREE AndAlso baddr.ToUInt64 > &H1000 Then

 

                                        Dim strAddrDump = GetAddressDump( _

                                            baddr, _

                                            mbi.RegionSize)

 

                                        Dim tbxDump = New TextBox With { _

                                            .Text = strAddrDump, _

                                            .BorderThickness = New Windows.Thickness(0), _

                                            .FontFamily = New FontFamily("Courier New"), _

                                            .FontSize = 10, _

                                            .Background = Brushes.LightYellow _

                                        }

                                        Dim sp = New StackPanel With {.Orientation = Orientation.Vertical}

                                        If CType(mbi.lType, AllocationType) = AllocationType.MEM_IMAGE Then

                                            Dim sbFilename As New Text.StringBuilder(300)

                                            Dim rr = GetFileNameFromMBI(mbi)

                                            GetModuleFileNameEx(_proc.Handle, New UIntPtr(mbi.AllocationBase.ToUInt64), sbFilename, sbFilename.Capacity)

                                            sp.Children.Add(New TextBlock With {.Text = "Filename = " + sbFilename.ToString})

                                        End If

                                        sp.Children.Add(tbxDump)

                                        ttipObj.Content = sp

                                    Else

                                        ttipObj.Content = baddr.ToString + " bad address for mem dump"

                                    End If

 

                                End If

                            Case "RegionSize"

                                ttipObj.Content = String.Format(" Base 10: {0}", Convert.ToInt32(tb.Text, 16))

                            Case "Where", "Filename"

                                ttipObj.Content = tb.Text

                            Case Else

                                Return

                        End Select

                        ToolTipService.SetShowDuration(tb, 100000)

 

                        ttipObj.IsOpen = True

                        tb.ToolTip = ttipObj

                        _LastTipObj = tb

                    End If

                End If

            End If

 

        End Sub

 

        Const BlockSize As Integer = 1024

        <StructLayout(LayoutKind.Sequential)> _

        Structure ProcMemBlock

            <MarshalAs(UnmanagedType.ByValArray, sizeconst:=BlockSize)> _

            Dim data() As Byte

        End Structure

 

        Public Function GetAddressDump(ByVal nAddress As UIntPtr, ByVal nTotalBytesToRead As UIntPtr, Optional ByVal nMax As Integer = 1024) As String

            Dim sbAddrDump As New Text.StringBuilder

            Try

                If nMax > 0 Then

                    nTotalBytesToRead = Math.Min(nMax, nTotalBytesToRead.ToUInt64)

                End If

                Dim blk = New ProcMemBlock

                Dim nBytesLeftToRead = nTotalBytesToRead

                Do While nBytesLeftToRead.ToUInt64 > 0

                    Dim nBytesToReadForThisBlock = Math.Min(nBytesLeftToRead.ToUInt64, BlockSize)

                    Dim nBytesRead As UIntPtr = 0

                    If ReadProcessMemory(_proc.Handle, nAddress, blk, nBytesToReadForThisBlock, nBytesRead) Then

                        nBytesLeftToRead = nBytesLeftToRead.ToUInt64 - nBytesRead.ToUInt64

                        Dim innerCnt = 0

                        Dim strBytes = ""

                        Dim strChars = ""

                        For i = 0 To nBytesRead.ToUInt64 - 1 Step 4

                            If innerCnt Mod 8 = 0 Then

                                If i > 0 Then ' if we're ending a line, dump out the bytes and chars

                                    sbAddrDump.AppendLine("  " + strBytes + "  " + strChars)

                                    strBytes = ""

                                    strChars = ""

                                Else

                                    sbAddrDump.AppendLine()

                                End If

                                If _Is32 Then

                                    sbAddrDump.Append(String.Format("{0:x8} : ", nAddress.ToUInt32 + CUInt(i)))

                                Else

                                    Dim offset = nAddress.ToUInt64 + CUInt(i)

                                    sbAddrDump.Append(String.Format("{0:x16} : ", nAddress.ToUInt64 + CUInt(i)))

                                End If

                            End If

                            innerCnt += 1

                            Dim dword = 0

                            For p = 0 To 3

                                dword += CInt(blk.data(i + p)) << shifts(3 - p)

                            Next

                            '                        Dim dword = Marshal.ReadIntPtr(New IntPtr(nAddress + i)).ToInt32

                            toBytes(dword, strBytes, strChars)

                            sbAddrDump.Append(String.Format("{0:x8} ", dword))

                        Next

                        ' some leftovers

                        Dim startLeftover = innerCnt

                        Do While innerCnt Mod 8 > 0

                            sbAddrDump.Append("         ")

                            innerCnt += 1

                        Loop

                        sbAddrDump.AppendLine("  " + strBytes + "  ")

                        innerCnt = startLeftover

                        Do While innerCnt Mod 8 > 0

                            sbAddrDump.Append("   ")

                            innerCnt += 1

                        Loop

                        sbAddrDump.AppendLine(strChars)

 

                        Do While innerCnt Mod 8 > 0

                            sbAddrDump.Append("         ")

                            innerCnt += 1

                        Loop

                    Else

                        Exit Do

                    End If

                    nAddress = nAddress.ToUInt64 + nBytesRead.ToUInt64

                Loop

            Catch ex As Exception

                sbAddrDump.Append("Exception when reading " + nAddress.ToString + " " + ex.Message)

 

            End Try

 

            Return sbAddrDump.ToString

        End Function

 

        Const badchars = vbCr + vbLf + vbTab + Chr(&HB) + Chr(&HC)

        Private Shared shifts As Integer() = {24, 16, 8, 0}

        Shared Sub toBytes(ByVal num As Integer, ByRef strBytes As String, ByRef strChars As String)

            For i = 3 To 0 Step -1

                Dim abyte = CByte((num >> shifts(i) And &HFF))

                strBytes += String.Format("{0:x2} ", abyte)

                Dim thechar = " "

                If abyte > 15 AndAlso abyte < 127 Then

                    If badchars.IndexOf(thechar) < 0 Then

                        thechar = Chr(abyte).ToString

                    End If

                End If

                strChars += thechar

            Next

        End Sub

 

        <DllImport("kernel32.dll", SetLastError:=True)> _

        Public Shared Function ReadProcessMemory( _

               ByVal hProcess As IntPtr, _

               ByVal lpBaseAddress As UIntPtr, _

               ByRef lpBuffer As ProcMemBlock, _

               ByVal dwSize As Integer, _

               ByRef lpNumberOfBytesRead As Integer _

         ) As Integer

        End Function

 

        <DllImport("psapi")> _

        Shared Function GetModuleFileNameEx(ByVal hProcess As IntPtr, ByVal hModule As UIntPtr, ByVal lpFileName As Text.StringBuilder, ByVal nSize As Integer) As Integer

        End Function

 

        <DllImport("kernel32")> _

        Shared Function VirtualQueryEx( _

                                    ByVal hProcess As IntPtr, _

                                    ByVal lpAddress As UIntPtr, _

                                    ByRef mbi As MEMORY_BASIC_INFORMATION, _

                                    ByVal dwLength As UInteger) As UInteger

        End Function

 

        <StructLayout(LayoutKind.Sequential)> _

        Structure MEMORY_BASIC_INFORMATION

            Dim BaseAddress As UIntPtr

            Dim AllocationBase As UIntPtr

            Dim AllocationProtect As AllocationProtect

            Dim RegionSize As UIntPtr

            Dim State As AllocationState

            Dim Protect As AllocationProtect

            Dim lType As AllocationType

        End Structure

 

        <Flags()> _

        Enum AllocationProtect

            PAGE_EXECUTE = &H10

            PAGE_EXECUTE_READ = &H20

            PAGE_EXECUTE_READWRITE = &H40

            PAGE_EXECUTE_WRITECOPY = &H80

            PAGE_NOACCESS = &H1

            PAGE_READONLY = &H2

            PAGE_READWRITE = &H4

            PAGE_WRITECOPY = &H8

            PAGE_GUARD = &H100

            PAGE_NOCACHE = &H200

            PAGE_WRITECOMBINE = &H400

        End Enum

 

        <Flags()> _

        Enum AllocationType

            MEM_IMAGE = &H1000000

            MEM_MAPPED = &H40000

            MEM_PRIVATE = &H20000

        End Enum

 

        <Flags()> _

        Enum AllocationState

            MEM_COMMIT = &H1000

            MEM_FREE = &H10000

            MEM_RESERVE = &H2000

        End Enum

 

        Function GetFileNameFromMBI(ByVal mbi As MEMORY_BASIC_INFORMATION) As String

            Dim retval = ""

            If CType(mbi.lType, AllocationType) = AllocationType.MEM_IMAGE Or True Then

                If mbi.AllocationBase.ToUInt64 > 0 Then

                    Dim sbFilename As New Text.StringBuilder(300)

                    If GetModuleFileNameEx(_proc.Handle, New UIntPtr(mbi.AllocationBase.ToUInt64), sbFilename, sbFilename.Capacity) > 0 Then

                        retval = sbFilename.ToString

                    End If

                End If

            End If

            Return retval

        End Function

 

    End Class

End Class

 

 

' see http://blogs.msdn.com/calvin_hsia/archive/2007/12/06/6684376.aspx

Public Class Browse

    Inherits ListView

 

    Private _Browse As Browse = Me

    Private _delDblClick As BrowEventDelegate

    Private _delMouseMove As BrowEventDelegate

    Public _query As IEnumerable

 

    Delegate Sub BrowEventDelegate(ByVal sender As Object, ByVal e As RoutedEventArgs)

 

    Sub New( _

               ByVal Query As IEnumerable, _

               Optional ByVal delDblClick As BrowEventDelegate = Nothing, _

               Optional ByVal delMouseMove As BrowEventDelegate = Nothing, _

               Optional ByVal fAllowHeaderClickSort As Boolean = True)

 

        _query = Query

        Dim gv As New GridView

 

        _Browse.View = gv

        _Browse.ItemsSource = Query

        If fAllowHeaderClickSort Then

            _Browse.AddHandler(GridViewColumnHeader.ClickEvent, New RoutedEventHandler(AddressOf HandleHeaderClick))

        End If

        If delDblClick IsNot Nothing Then

            _delDblClick = delDblClick

            _Browse.AddHandler(MouseDoubleClickEvent, New RoutedEventHandler(AddressOf OnRowDblClickEvent))

        End If

        If delMouseMove IsNot Nothing Then

            _delMouseMove = delMouseMove

            _Browse.AddHandler(MouseMoveEvent, New RoutedEventHandler(AddressOf OnMouseMoveEvent))

 

        End If

 

        If Query.GetType.GetInterface(GetType(IEnumerable(Of )).FullName).GetGenericArguments(0).Name = "XElement" Then ' It's XML

            Dim Elem1 = CType(Query, IEnumerable(Of XElement))(0).Elements ' Thanks Avner!

            For Each Item In Elem1

                Dim gvc As New GridViewColumn

                gvc.Header = Item.Name.LocalName

                gv.Columns.Add(gvc)

                Dim bind As New Binding("Element[" + Item.Name.LocalName + "].Value")

                gvc.DisplayMemberBinding = bind

                gvc.Width = 180

            Next

        Else ' it's some anonymous type like "VB$AnonymousType_1`3". Let's use reflection to get the column names

            For Each mem In From mbr In _

                    Query.GetType().GetInterface(GetType(IEnumerable(Of )).FullName) _

                    .GetGenericArguments()(0).GetMembers _

                    Where mbr.MemberType = Reflection.MemberTypes.Property

 

                Dim datatype = CType(mem, Reflection.PropertyInfo)

                Dim coltype = datatype.PropertyType.Name

                Select Case coltype

                    Case "Int32", "String", "Int64"

                        Dim gvc As New GridViewColumn

                        gvc.Header = mem.Name

                        gv.Columns.Add(gvc)

                        Dim XAMLdt = _

                        <DataTemplate

                            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

                            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

                            >

                            <StackPanel Orientation="Horizontal">

                                <TextBlock Name=<%= mem.Name %>

                                    Text=<%= If(coltype = "String", _

                                             "{Binding Path=" + mem.Name + "}", _

                                             "{Binding Path=" + mem.Name + "}") %>

                                    >

                                </TextBlock>

                            </StackPanel>

                        </DataTemplate>

                        gvc.CellTemplate = CType(System.Windows.Markup.XamlReader.Load(XAMLdt.CreateReader), DataTemplate)

                        If coltype <> "String" Then

                            '                           gvc.Width = 80

                            gvc.Width = Double.NaN ' auto

                        Else

                            '                                gvc.DisplayMemberBinding = New Binding(mem.Name)

                            '                            gvc.Width = 180

                            gvc.Width = Double.NaN ' auto

                        End If

                End Select

            Next

        End If

        Dim XAMLlbStyle = _

        <Style

            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

            TargetType="ListBoxItem">

            <Setter Property="Foreground" Value="Blue"/>

            <Style.Triggers>

                <Trigger Property="IsSelected" Value="True">

                    <Setter Property="Foreground" Value="White"/>

                    <Setter Property="Background" Value="Aquamarine"/>

                </Trigger>

                <Trigger Property="IsMouseOver" Value="True">

                    <Setter Property="Foreground" Value="Red"/>

                </Trigger>

            </Style.Triggers>

        </Style>

        _Browse.ItemContainerStyle = CType(Windows.Markup.XamlReader.Load(XAMLlbStyle.CreateReader), Windows.Style)

    End Sub

 

    Sub OnRowDblClickEvent(ByVal sender As Object, ByVal e As RoutedEventArgs)

        If _delDblClick IsNot Nothing Then

            _delDblClick.Invoke(sender, e)

        End If

    End Sub

 

    Sub OnMouseMoveEvent(ByVal sender As Object, ByVal e As RoutedEventArgs)

        If _delMouseMove IsNot Nothing Then

            _delMouseMove.Invoke(sender, e)

        End If

    End Sub

 

    Dim _Lastdir As System.ComponentModel.ListSortDirection = ComponentModel.ListSortDirection.Ascending

    Dim _LastHeaderClicked As GridViewColumnHeader = Nothing

    Sub HandleHeaderClick(ByVal sender As Object, ByVal e As RoutedEventArgs)

        If e.OriginalSource.GetType Is GetType(GridViewColumnHeader) Then

            Dim gvh = CType(e.OriginalSource, GridViewColumnHeader)

            Dim dir As System.ComponentModel.ListSortDirection = ComponentModel.ListSortDirection.Ascending

            If Not gvh Is Nothing AndAlso Not gvh.Column Is Nothing Then

                If _LastHeaderClicked IsNot Nothing Then

                    _LastHeaderClicked.Column.HeaderTemplate = Nothing

                End If

                Dim hdr = gvh.Column.Header

                If gvh Is _LastHeaderClicked Then

                    If _Lastdir = ComponentModel.ListSortDirection.Ascending Then

                        dir = ComponentModel.ListSortDirection.Descending

                    End If

                End If

                gvh.Column.HeaderTemplate = CType(Windows.Markup.XamlReader.Load( _

                    <DataTemplate

                        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

                        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

                        >

                        <DockPanel>

                            <TextBlock HorizontalAlignment="Center"

                                Text="{Binding}"/>

                            <Path

                                Fill="DarkGray"

                                Data=<%= If(dir = ComponentModel.ListSortDirection.Ascending, _

                                         "M 5,10 L 15,10 L 10,5", _

                                         "M 5,5 L 10,10 L 15,5") %>/>

                        </DockPanel>

                    </DataTemplate>.CreateReader), DataTemplate)

 

                Sort(CStr(hdr), dir)

                _LastHeaderClicked = gvh

                _Lastdir = dir

            End If

        End If

    End Sub

 

    Sub Sort(ByVal sortby As String, ByVal dir As System.ComponentModel.ListSortDirection)

        _Browse.Items.SortDescriptions.Clear()

        Dim sd = New System.ComponentModel.SortDescription(sortby, dir)

        _Browse.Items.SortDescriptions.Add(sd)

        _Browse.Items.Refresh()

    End Sub

End Class

 

 

 

</code sample>

Posted by Calvin_Hsia | 1 Comments
Filed under: , , , ,

Its easy to create a graph of memory use in Excel

In this post Examine .Net Memory Leaks I showed how to find a .Net managed memory leak.

 

Now let’s create a graph of memory and resource use over time.

 

 

Start Visual Studio 2008, File->New->Visual Basic (or C#) Windows, WPF application. Dbl click the WPF form to get to the Xaml.cs or Xaml.vb file

Paste in the appropriate version from below.

Hit F5 to run the code for about 2 minutes. Then Excel starts (if you have Excel installed)

 

 

When Excel starts, type these keystrokes exactly (we can automate Excel with a macro or use Automation, but that’s another story)

                Right Arrow (to skip the iteration column)

                Shift-Ctrl-End (to select the entire table)

                Alt (to activate the menu shortcuts

                N (to choose Insert)

                N (to choose Line Graph)

                Enter (to choose the first kind of line graph

 

See how easy it is to create a picture?

 

Try experimenting with the garbage collection and how it behaves. Try commenting out the UnSubscribe call.

 

Open the log.csv file from within VS. As the file changes, VS will automatically detect and reload the text, so you can watch as it runs.

 

See also

Create your own Test Host using XAML to run your unit tests

Excel's new gradient Data Bar feature is cool: you can do it too!

Create an ActiveX control using ATL that you can use from Fox, Excel, VB6, VB.Net

 

<VB Code>

Imports System.IO

 

Class Window1

    Public Shared logFile = "log.csv"    ' Excel can read/create graph easily

 

    Private Sub Window1_Loaded(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles MyBase.Loaded

        logFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), logFile)

 

        Dim nLoops = 100

        Dim nItersPerLoop = 100

        Dim dummy(nItersPerLoop) As MyWatcher

        For i = 1 To nLoops

            For j = 1 To nItersPerLoop

                Dim oWatcher = New MyWatcher

                dummy(i) = oWatcher

                dummy(i).UnSubscribe()

            Next

            GarbageCollect(0)

        Next

 

        Process.Start(logFile) ' start Excel 2007

        End ' end the program

    End Sub

 

    Private Shared _nIter As Integer

    Sub GarbageCollect(ByVal nGarbageCollect As Integer)

        For i = 1 To nGarbageCollect

            GC.Collect()

            GC.WaitForPendingFinalizers()

            Dim start = DateTime.Now

            ' give time to other threads to finish (WPF managed objects on other threads)

            Do Until (DateTime.Now - start).Duration > TimeSpan.FromSeconds(1)

                Dispatcher.Invoke(Windows.Threading.DispatcherPriority.Background, Function() Nothing)

            Loop

        Next

        If _nIter = 0 AndAlso File.Exists(logFile) Then

            File.Delete(logFile)

        End If

        Dim devenv = Process.GetCurrentProcess

        Dim sFmt = "{0,5}, [{1,22}], {2,10}, {3,5}, {4,5}, {5,5}"

        Using writer = If(File.Exists(logFile), File.AppendText(logFile), File.CreateText(logFile))

            _nIter += 1

            Dim sOutput As String

            If _nIter = 1 Then

                sOutput = String.Format(sFmt, "Iter", "When", "Priv Mb", "Hndle", "GDI", "User")

                writer.WriteLine(sOutput)

                Debug.WriteLine(sOutput)

            End If

 

            sOutput = String.Format(sFmt, _nIter, _

                    DateTime.Now, (devenv.PrivateMemorySize64 / 1000000.0).ToString("f6"), devenv.HandleCount, GetGuiResources(devenv.Handle, 0), GetGuiResources(devenv.Handle, 1))

            writer.WriteLine(sOutput)

            Debug.WriteLine(sOutput)

            writer.Close()

        End Using

 

    End Sub

    Declare Function GetGuiResources Lib "user32" (ByVal hHandle As IntPtr, ByVal uiFlags As Integer) As Integer

 

 

    Class MyWatcher

        Dim MyLargeMemoryEater(1000000) As String ' make the instance bigger to magnify issue: 4 bytes per array item on x86

        Dim fsw As IO.FileSystemWatcher

        Sub New()

            fsw = New IO.FileSystemWatcher

            fsw.Path = Path.GetDirectoryName(logFile)

            fsw.Filter = "*.*"

            AddHandler fsw.Created, AddressOf OnWatcherFileCreated

 

            fsw.EnableRaisingEvents = True

 

        End Sub

        Sub UnSubscribe()

            fsw.EnableRaisingEvents = False

            RemoveHandler fsw.Created, AddressOf OnWatcherFileCreated

        End Sub

        Sub OnWatcherFileCreated(ByVal sender As Object, ByVal args As System.IO.FileSystemEventArgs)

            Debug.WriteLine((New StackTrace).GetFrames(0).GetMethod.Name + " " + args.FullPath)

        End Sub

        Protected Overrides Sub Finalize()  ' called when garbage collector collects on the GC Finalizer thread.

            MyBase.Finalize()

            '            Debug.WriteLine((New StackTrace).GetFrames(0).GetMethod.Name + _nIter.ToString + " Thread= " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString)

        End Sub

 

    End Class

 

End Class

</VB Code>

 

<C# Code>

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Data;

using System.Windows.Documents;

using System.Windows.Input;

using System.Windows.Media;

using System.Windows.Media.Imaging;

using System.Windows.Navigation;

using System.Windows.Shapes;

using System.Diagnostics;

using System.IO;

 

namespace WpfApplication1

{

    /// <summary>

    /// Interaction logic for Window1.xaml

    /// </summary>

    public partial class Window1 : Window

    {

        public static string logFile = "log.csv"; // Excel can read/create graph easily

        private static int _nIter;

 

        public Window1()

        {

            InitializeComponent();

        }

 

        private void Window_Loaded(object sender, RoutedEventArgs e)

        {

            logFile = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), logFile);

            int nLoops = 100;

            int nItersPerLoop = 100;

            MyWatcher[] dummy = new MyWatcher[nItersPerLoop];

            for (int i = 0; i < nLoops; i++)

            {

                for (int j = 0; j < nItersPerLoop; j++)

                {

                    var oWatcher = new MyWatcher();

                    dummy[i] = oWatcher;

                    dummy[i].UnSubscribe();

                }

                GarbageCollect(01);

            }

 

            Process.Start(logFile);// start Excel 2007

            Process.GetCurrentProcess().CloseMainWindow();

 

        }

        private void GarbageCollect(int nGarbageCollect)

        {

            for (int i = 0; i < nGarbageCollect; i++)

            {

                GC.Collect();

                GC.WaitForPendingFinalizers();

                var start = DateTime.Now;

                // give time to other threads to finish (WPF managed objects on other threads)

                var dd = TimeSpan.FromSeconds(1);

                var yy = dd > TimeSpan.FromSeconds(2);

 

 

                while (((DateTime.Now - start)) < TimeSpan.FromSeconds(1))

                {

                    Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Background,

                        new System.Windows.Threading.DispatcherOperationCallback(delegate

                            {

                                return null;

                            }

                            ),null);

 

                }

 

            }

            if (_nIter == 0 && System.IO.File.Exists(logFile))

            {

                System.IO.File.Delete(logFile);

            }

            var devenv = Process.GetCurrentProcess();

            var sFmt = "{0,5}, [{1,22}], {2,10}, {3,5}, {4,5}, {5,5}";

            using (TextWriter writer = File.Exists(logFile) ? File.AppendText(logFile) :  File.CreateText(logFile))

            {

                _nIter += 1;

                var sOutput = "";

                if (_nIter == 1)

                {

                    sOutput = String.Format(sFmt, "Iter", "When", "Priv Mb", "Hndle", "GDI", "User");

                    writer.WriteLine(sOutput);

                    Debug.WriteLine(sOutput);

                }

                    sOutput = String.Format(sFmt, _nIter,

                            DateTime.Now, (devenv.PrivateMemorySize64 / 1000000.0).ToString("f6"), devenv.HandleCount, GetGuiResources(devenv.Handle, 0), GetGuiResources(devenv.Handle, 1));

                    writer.WriteLine(sOutput);

                    Debug.WriteLine(sOutput);

                    writer.Close();

            }

        }

 

        [System.Runtime.InteropServices.DllImport("User32")]

        extern public static int GetGuiResources(IntPtr hProcess, int uiFlags);

        public class MyWatcher

        {

            string[] MyLargeMemoryEater = new string[1000000]; //' make the instance bigger to magnify issue: 4 bytes per array item on x86

            System.IO.FileSystemWatcher fsw;

            public MyWatcher()

            {

                fsw = new System.IO.FileSystemWatcher();

                fsw.Path = System.IO.Path.GetDirectoryName(logFile);

                fsw.Filter = "*.*";

 

                fsw.EnableRaisingEvents = true;

                fsw.Created += OnWatcherFileCreated;

 

            }

            public void UnSubscribe()

            {

                fsw.EnableRaisingEvents = false;

                fsw.Created -= OnWatcherFileCreated;

            }

            void OnWatcherFileCreated(Object sender, System.IO.FileSystemEventArgs args)

            {

                Debug.WriteLine(((new StackTrace()).GetFrames()).FirstOrDefault().GetMethod().Name + " " + args.FullPath);

            }

        }

    }

}

 

</C# Code>

 

 

 

 

 

Posted by Calvin_Hsia | 0 Comments
Filed under: , ,

Make your Task Switcher Window bigger

One of the best features of Windows is the ability to Cut and Paste data within an application or from one application to another.  

 

You can alt-tab or click on the task bar to switch between actively running applications, like Word and Visual Studio. When you hit Alt-Tab, a window comes up.

 

This Task switcher window, by default has 3 rows of 7 icons. If you have more than 21 top level windows open, that dialog gets confusing.

 

Without tabbed browsing a few years ago (the ability of Internet Explorer to open multiple Tabs to view multiple web pages in a single top level IE Window), I had tons of these windows.  (Try Window Key Tab to switch applications on any machine with Aero enabled!)

 

(see also Create your own Flip Task Bar with live thumbnails using Vista Desktop Window Manager DWM and The VB version of the Flip Task Bar for Vista Desktop Window Manager)

 

In fact, sometimes I run so many I get confused when I Alt-Tab and see the behavior of the default 21 (3 row by 7 column) icon display. So, I just modify this to be 5 rows by 9 columns.

 

Open RegEdit and change these keys:

 

HKEY_CURRENT_USER\Control Panel\Desktop\CoolSwitchRows

HKEY_CURRENT_USER\Control Panel\Desktop\CoolSwitchColumns

 

See also CoolSwitchRows:  http://technet.microsoft.com/en-us/library/cc978607.aspx

 

 

In the old days (the late 70's to early 80's, when a 5 Meg hard disk was cool!), you could only run one application on your computer at a time. To transfer data from one program to another, you would open one application, save the information temporarily to a file on (floppy) disk, then open the other application, import the temporary file.

 

Then along came Borland's Sidekick and AutoDesq DesqView.  I think Sidekick had a way of storing text data: it was a TSR: a Terminate and Stay Resident program. That means it was always available even if you were running another application. Once you start it, it would “terminate” but still be running. It would always be in memory, reducing the memory available to other programs.

 

I remember writing various TSRs. One was a simple program that could flash on the screen any message at any time interval and duration:

 

                                Flashmsg  /m:"You're getting hungry" /t:200 /d:1

 

Another would monitor key strokes. Every 100th keystroke it could double a keystroke, substitute another, record, or suppress one.  I didn't circulate these evil programs. (do you believe me? J)

 

A more useful one came about because a fellow ice hockey teammate and client heard I knew something about computers and wanted his business's Contact List to be instantly available at any time. So I wrote a TSR that would display a simple database of names and phone numbers around 1982. Over the years, I migrated the database maintenance to FoxBase and eventually FoxPro (1990?). So while the client was using WordPerfect, he could hit a key combination to bring up his Contact List instantly.

 

A TSR can monitor various interrupts, such as the keyboard,  by just replacing the interrupt vector with its own. The interrupt vector was just a pointer to the code to run if the keyboard is hit. My TSR would just forward most requests to the original ISR (Interrupt Service Routine). Back in those DOS days, it was easy to replace system memory. “Protected mode” was unheard of.

 

DesqView was another TSR, but it allowed you to run multiple applications "simultaneously". If I remember right, it "paged" out one app for another, writing to disk the 256K that one was using for the other. This was an immense boost in productivity.

 

In those days, I was using a really great text editor called PC-Write (one of the first "shareware" programs)  from Bob Wallace (Microsoft Employee #9). (It was around this time that dBase, FoxBase were born.)

 

 

When Windows 3.1 came out around the early 90's, you could use OLE (Object Linking and Embedding, which sort of got renamed to ActiveX later) and DDE (Dynamic Data Exchange) to have applications communicate with each other. The big demo was a Word Document or Fox database embedding an Excel spreadsheet or a photograph.

 

You can still embed a photo or spreadsheet in Foxpro using a General field (on XP, Vista and Windows Server08 R2(which is Win7 under the hood)):

 

CREATE CURSOR temp (name c(10), gen g)

INSERT INTO temp (name) VALUES ("Photo")

APPEND GENERAL gen FROM d:\kids.jpg

MODIFY GENERAL gen

INSERT INTO temp (name) VALUES ("Excel")

APPEND GENERAL gen FROM d:\2008.07.xlsx

BROWSE

 

You can double click the image or Excel spreadsheet to see or edit it within Fox. You can also use the Edit->Insert Special menu to insert other kinds of objects.

 

For fun, try this:  

      x=DDEINITIATE("visualfoxpro","")

or

      _cliptext="This text goes on the clipboard"

 

Or notice the intellisense when you try this from the Fox Command window

x=CREATEOBJECT("Excel.Application")

x.Visible=1

x.Workbooks.Add

x.Cells(1,1).Value="Hi"

 

 

Try starting Visual Studio and choose File->Open->File-> navigate to a photo or spreadsheet!

 

Create or open an existing VB project which has no build errors and then

       Dim x=CREATEOBJECT("Excel.Application")

       Stop

 

F5 to hit the breakpoint, Debug->Window->Immediate to open the immediate window.

 

Then resize VS so you can see Excel,  type

x.Visible=1

x.Workbooks.Add

x.Cells(1,1).Value="Hi"

 

 

 

Try something similar in C# (add a reference to Microsoft.VisualBasic.Dll)

 

            Object x = Microsoft.VisualBasic.Interaction.CreateObject("excel.application","");

            var z = "Put a bpt here";

 

 

Process Explorer and Process Monitor can help you understand a program

Process Explorer and Process Monitor are must-have free utilities that you can download from http://technet.microsoft.com/en-us/sysinternals/bb795533.aspx

 

For example you can use ProcExp to examine a particular behavior of Visual Studio.

 

Start Visual Studio (any version). I was using VS 2008. 

Start Process Explorer (as Administrator)

Click on the Devenv.exe process (the main VS process) to select it.

Choose View->Show Lower Pane and View->Lower Pane View->Handles

In VS, try creating a new VB or C# console application.

 

Now ProcExp shows a list of handles opened by this particular process. A handle can be a file, an Event, a process, a thread, a registry key, etc.

 

Did you ever get a permission denied error message because the file is “opened by another process” but you didn’t know which process?

 

If you click on the Name column, the list of handles will be sorted by name.

 

I chose File->Save As to save the whole shebang to a file. Here’s a portion, showing the handle type (File), the file name, and the handle value:

 

File         C:\Windows\Microsoft.NET\Framework\v2.0.50727\CONFIG\enterprisesec.config.cch 0x264

File         C:\Windows\Microsoft.NET\Framework\v2.0.50727\CONFIG\security.config.cch             0x260

File         C:\Windows\Microsoft.NET\Framework\v2.0.50727\en\Microsoft.VisualBasic.xml          0x14D0

File         C:\Windows\Microsoft.NET\Framework\v2.0.50727\en\mscorlib.xml    0x8C0

File         C:\Windows\Microsoft.NET\Framework\v2.0.50727\en\System.Data.xml            0x89C

File         C:\Windows\Microsoft.NET\Framework\v2.0.50727\en\System.xml      0x13F4

File         C:\Windows\Microsoft.NET\Framework\v2.0.50727\Microsoft.CSharp.targets  0x7F4

File         C:\Windows\Microsoft.NET\Framework\v2.0.50727\Microsoft.VisualBasic.dll    0x88C

File         C:\Windows\Microsoft.NET\Framework\v2.0.50727\Microsoft.VisualBasic.targets           0x7F8

File         C:\Windows\Microsoft.NET\Framework\v2.0.50727\Microsoft.WinFX.targets    0x7E8

File         C:\Windows\Microsoft.NET\Framework\v2.0.50727\mscorlib.dll              0x86C

File         C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.Data.dll      0x13D4

File         C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.Deployment.dll      0x924

File         C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.dll                0x174C

File         C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.XML.dll      0x15B8

File         C:\Windows\Microsoft.NET\Framework\v3.5\Microsoft.CompactFramework.CSharp.targets     0x804

File         C:\Windows\Microsoft.NET\Framework\v3.5\Microsoft.CompactFramework.VisualBasic.targets                0x808

File         C:\Windows\Microsoft.NET\Framework\v3.5\Microsoft.CSharp.targets               0x7EC

File         C:\Windows\Microsoft.NET\Framework\v3.5\Microsoft.VisualBasic.targets        0x7F0

File         C:\Windows\Microsoft.NET\Framework\v3.5\Microsoft.WinFx.targets 0x80C

File         C:\Windows\Microsoft.NET\Framework\v3.5\SqlServer.targets               0x7FC

 

You can see this portion by scrolling the view to some framework assemblies.

 

Now play around with Visual Studio to see what files in this folder get opened and closed. The newly opened ones show in green for a couple seconds, while ones that close flash red, then disappear.

 

In particular, try creating a new VB Console application to see which assemblies get loaded. Close the solution, reopen it a few times. You’ll see these files get opened/closed:

File         C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.Data.dll      0x13D4

File         C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.Deployment.dll      0x924

File         C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.dll                0x174C

File         C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.XML.dll      0x15B8

 

Now type the letter “C” within Sub Main and you’ll see these XML files get opened:

File         C:\Windows\Microsoft.NET\Framework\v2.0.50727\en\Microsoft.VisualBasic.xml          0x14D0

File         C:\Windows\Microsoft.NET\Framework\v2.0.50727\en\mscorlib.xml    0x8C0

File         C:\Windows\Microsoft.NET\Framework\v2.0.50727\en\System.Data.xml            0x89C

File         C:\Windows\Microsoft.NET\Framework\v2.0.50727\en\System.xml      0x13F4

They will remain opened until you close the solution.

 

Now try typing the letter “C” before the Sub Main, but after the Module Module1 line.

No files get opened! Why?

 

Why does where you type make a difference? What are these files used for?

 

Here’s an excerpt. Does it provide any clues?

 

    <member name="T:System.Uri">

      <summary>Provides an object representation of a uniform resource identifier (URI) and easy access to the parts of the URI.</summary>

      <filterpriority>1</filterpriority>

    </member>

    <member name="M:System.Uri.#ctor(System.String)">

      <summary>Initializes a new instance of the <see cref="T:System.Uri" /> class with the specified URI.</summary>

      <param name="uriString">A URI. </param>

      <exception cref="T:System.ArgumentNullException">

        <paramref name="uriString" /> is null. </exception>

      <exception cref="T:System.UriFormatException">

        <paramref name="uriString" /> is empty.-or- The scheme specified in <paramref name="uriString" /> is not correctly formed. See <see cref="M:System.Uri.CheckSchemeName(System.String)" />.-or- <paramref name="uriString" /> contains too many slashes.-or- The password specified in <paramref name="uriString" /> is not valid.-or- The host name specified in <paramref name="uriString" /> is not valid.-or- The file name specified in <paramref name="uriString" /> is not valid. -or- The user name specified in <paramref name="uriString" /> is not valid.-or- The host or authority name specified in <paramref name="uriString" /> cannot be terminated by backslashes.-or- The port number specified in <paramref name="uriString" /> is not valid or cannot be parsed.-or- The length of <paramref name="uriString" /> exceeds 65534 characters.-or- The length of the scheme specified in <paramref name="uriString" /> exceeds 1023 characters.-or- There is an invalid character sequence in <paramref name="uriString" />.-or- The MS-DOS path specified in <paramref name="uriString" /> must start with c:\\.</exception>

    </member>

 

 

Now try typing this very slowly and watch the Proc Exp Handles list:

                Dim x = New Uri(

 

(In C#, try “Uri” )

 

You’ll get Intellisense and the parameter help for the constructor of the Uri. The strings for that help come from this XML file.

 

The “filterPriority” element indicates whether the item shows up on the Common or the All tab of the Intellisense list (VB only).

(BTW, did you know you can switch between the common and the All tabs Alt-Comma and Alt-Period?)

 

If you like, you can customize the XML of the framework assemblies. Edit the text and save it.

 

You can even author your own:

Try this: before Sub Main type 3 apostrophes. (for C#, try 3 slashes) This will appear:

    ''' <summary>

    '''

    ''' </summary>

    ''' <remarks></remarks>

 

Try hitting Enter then “<” after the </summary> tag.  Intellisense will prompt you for other XML Doc fields, such as Example, Remarks, See Also, Param.

 

You can fill out the “XmlDoc” information. On Project->Properties->Compile there’s a checkbox “Generate XML Documentation file” (see How to: Generate XML Documentation for a Project )

 

Now when your assemblies get referenced, you can provide the strings for intellisense, parameter help, info tips to show!

 

The “en” in the file path name indicates “English” version of the docs. Other languages will work too,  if you have them installed on your machine.

 

The letter “C” in the VB sample above triggers intellisense. If you’re not within a method, then only a few keywords are allowed that start with “C”, like Class, Const, or Custom. Thus the XML doc files aren’t necessary.

 

If you’re within the context of a method, you can potentially type the name of a shared method, for example: Console.WriteLine, so the XML files are loaded.

 

Try experimenting with other project types, such as C#, a Web app, or even start Visual FoxPro to see what files get opened under varying conditions. Try adding references to other assemblies.

 

We’ll explore more of ProcExp and ProcMon in future posts.

Posted by Calvin_Hsia | 0 Comments

You can use Visual Studio to debug itself!

How do you find out why your computer or a running program is so slow? Here’s one way.

 

Let’s attach the VS debugger to VS itself. The main executable for VS is devenv.exe.

 

Start Visual Studio 2008. This will be the “debugger”

 

Choose File->Open Project    C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE\devenv.exe

 

(You can also choose Debug->Attach to process to debug another instance of devenv.exe, or any other EXE, like Foxpro.exe or Excel.exe)

 

 

Hit F5, and a dialog pops up:

“Debugging information for ‘devenv.exe’ cannot be found or does not match. Symbols not loaded. Do you want to continue debugging?”. 

Answer yes, and Visual Studio starts. This will be the “debuggee”

 

Do anything you like in the debuggee, such as create or load a project. Hit F12 to cause an asynchronous breakpoint (or go to the debugger and choose Debug->Break All).

 

That will freeze all the debuggee threads and put you in the debugger.

 

You can then examine the Threads window (Debug->Windows->Threads) and see what threads are running.  There are several. You can dbl-click various threads and look at the Call stack for it (Debug->Windows->Call stack).

 

You’ll probably see that most threads are just waiting for something to happen.

 

Choose the main thread. When VS is idling, the stack will look like this:

 

            ntdll.dll!7c90eb94()       

            [Frames below may be incorrect and/or missing, no symbols loaded for ntdll.dll]

            ntdll.dll!7c90e9ab()       

            kernel32.dll!7c8094e2()

>          msvcr90.dll!_onexit_nolock(int (void)* func=0x0072006f)  Line 157 + 0x6 bytes    C

            00660072()       

 

Each stack entry shows the module and address that called the next stack entry. This isn’t very useful, so you need to load symbols. You can use the public Microsoft Symbol Server:

 

Tools->Options->Debug->Symbols

 

http://msdl.microsoft.com/download/symbols

 

Cache the symbols to a local dir, like C:\Symbols

 

Right click on the various modules (like ntdll.dll, kernel32.dll, msenv.dll etc.) in the call stack to load symbols. Now it’s a little more intelligible:

 

>          ntdll.dll!_KiFastSystemCallRet@0()       

            user32.dll!_NtUserKillTimer@8()  + 0xc bytes     

            msenv.dll!CMsoCMHandler::FPushMessageLoop()  + 0x36 bytes           

            msenv.dll!SCM::FPushMessageLoop()  + 0x4f bytes     

            msenv.dll!SCM_MsoCompMgr::FPushMessageLoop()  + 0x28 bytes      

            msenv.dll!CMsoComponent::PushMsgLoop()  + 0x28 bytes       

            msenv.dll!VStudioMainLogged()  + 0x19b bytes

            msenv.dll!_VStudioMain()  + 0x7d bytes

            devenv.exe!util_CallVsMain()  + 0xd8 bytes       

            devenv.exe!CDevEnvAppId::Run()  + 0x5cb bytes         

            devenv.exe!_WinMain@16()  + 0x60 bytes         

            devenv.exe!License::GetPID()  - 0x4cf9 bytes    

            kernel32.dll!_BaseProcessStart@4()  + 0x23 bytes         

 

You can see the WinMain calls a MessageLoop.

 

 

Let’s make the foreground thread busy. Create a VB console application. Add an XML literal:

 

Module Module1

 

    Sub Main()

        Dim bigxml = <xml>

 

                  </xml>

    End Sub

 

End Module

 

Make the XML literal big: open the file C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.5\System.Data.Linq.xml and copy everything except the <?xml version="1.0" encoding="utf-8"?> into the literal (between the <xml> and the </xml>), so you have about 2000 lines. 

 

Start Task Manager (Ctrl-Shift->Escape, or right click on the task bar and choose Task Manager). Observe the little tray icon in the System tray. It will indicate how busy your computer is.

 

For example, if you have 2 processors and 1 thread is very busy, it’ll show 50% busy.

 

Now, if you hover your mouse over the “bigxml”, you’ll trigger VB to create a Quick Info tooltip, but it takes quite a lot of calculation to figure out the tip.

 

Do your asynchronous breakpoint trick and you’ll see something like this:

 

>          msvb7.dll!BCSYM::IsNamedRoot()          

            msvb7.dll!BCSYM::AreTypesEqual()     

            msvb7.dll!EquivalentTypes()  + 0x17 bytes        

            msvb7.dll!ClassifyPredefinedCLRConversion()  + 0x22 bytes     

            msvb7.dll!Semantics::ClassifyPredefinedCLRConversion()  + 0x28 bytes

            msvb7.dll!Semantics::ClassifyPredefinedConversion()  + 0xbf bytes      

            msvb7.dll!Semantics::ResolveConversion()  + 0x234 bytes        

            msvb7.dll!Semantics::ClassifyUserDefinedConversion()  + 0x40685 bytes          

            msvb7.dll!Semantics::ClassifyConversion()  + 0x21e31 bytes     

            msvb7.dll!Semantics::CompareParameterTypeSpecificity()  + 0x57 bytes           

            msvb7.dll!Semantics::CompareParameterSpecificity()  + 0xa3 bytes       

            msvb7.dll!Semantics::InsertIfMethodAvailable()  + 0x461 bytes  

            msvb7.dll!Semantics::CollectOverloadCandidates()  + 0x1e6 bytes         

            msvb7.dll!Semantics::ResolveOverloading()  + 0xd9 bytes         

            msvb7.dll!Semantics::ResolveOverloadedCall()  + 0x5e bytes    

            msvb7.dll!Semantics::InterpretCallExpression()  + 0x2656f bytes

            msvb7.dll!Semantics::CreateConstructedInstance()  + 0xfa bytes

            msvb7.dll!Semantics::CreateConstructedInstance()  + 0x5b bytes           

            msvb7.dll!Semantics::InterpretXmlElement()  + 0x241 bytes       

            msvb7.dll!Semantics::InterpretXmlContent()  + 0x56 bytes          

            msvb7.dll!Semantics::InterpretXmlElement()  + 0x27c bytes       

            msvb7.dll!Semantics::InterpretXmlContent()  + 0x56 bytes          

            msvb7.dll!Semantics::InterpretXmlElement()  + 0x27c bytes       

            msvb7.dll!Semantics::InterpretXmlContent()  + 0x56 bytes          

            msvb7.dll!Semantics::InterpretXmlElement()  + 0x27c bytes       

            msvb7.dll!Semantics::InterpretXmlContent()  + 0x56 bytes          

            msvb7.dll!Semantics::InterpretXmlElement()  + 0x27c bytes       

            msvb7.dll!Semantics::InterpretXmlExpression()  - 0x13d bytes   

            msvb7.dll!Semantics::InterpretXmlExpression()  + 0xb0 bytes    

            msvb7.dll!Semantics::InterpretXmlExpression()  + 0xc3 bytes    

            msvb7.dll!Semantics::InterpretExpression()  - 0x1fc bytes          

            msvb7.dll!Semantics::InterpretExpressionWithTargetType()  + 0x43 bytes           

            msvb7.dll!Semantics::InterpretInitializer()  + 0x36 bytes  

            msvb7.dll!Semantics::InterpretInitializer()  + 0x1a bytes  

            msvb7.dll!Semantics::InterpretInitializer()  + 0x127 bytes 

            msvb7.dll!Semantics::InterpretVariableDeclarationStatement()  + 0x158c bytes    

            msvb7.dll!Semantics::InterpretStatement()  + 0x7b2f bytes         

            msvb7.dll!Semantics::InterpretStatementSequence()  + 0x2f bytes          

            msvb7.dll!Semantics::InterpretBlock()  + 0x24 bytes      

            msvb7.dll!Semantics::InterpretMethodBody()  + 0x1fa bytes      

            msvb7.dll!SourceFile::GetBoundMethodBodyTrees()  + 0x126 bytes       

            msvb7.dll!CBaseSymbolLocator::GetBoundMethodBody()  + 0xb6 bytes           

            msvb7.dll!CSymbolLocator::LocateSymbolInMethodImpl()  + 0x29 bytes

            msvb7.dll!CSymbolLocator::LocateSymbol()  + 0x81f bytes       

            msvb7.dll!CIntelliSense::GenQuickInfo()  + 0x52399 bytes          

            msvb7.dll!CIntelliSense::HandleQuickInfo()  + 0x29 bytes           

            msvb7.dll!CIntelliSense::ProcessCompletionInfo()  + 0x66cda bytes       

            msvb7.dll!CIntelliSense::GenIntelliSenseInfo()  + 0x402 bytes     

            msvb7.dll!SourceFileView::GenIntelliSenseInfo()  + 0x94 bytes   

            msvb7.dll!SourceFileView::GetDataTip()  + 0x743 bytes 

            msvb7.dll!CVBLangService::GetDataTip()  + 0x11d bytes           

            msenv.dll!CEditView::GetFilterDataTipText()  + 0x37 bytes         

            msenv.dll!CEditView::HandleHoverWaitTimer()  + 0x213 bytes    

            msenv.dll!CEditView::TimerTick()  + 0x7d bytes 

            msenv.dll!CEditView::WndProc()  + 0x1685b9 bytes      

            msenv.dll!CEditView::StaticWndProc()  + 0x39 bytes     

            user32.dll!_InternalCallWinProc@20()  + 0x28 bytes        

            user32.dll!_UserCallWinProcCheckWow@32()  + 0xb7 bytes       

            user32.dll!_DispatchMessageWorker@8()  + 0xdc bytes

            user32.dll!_DispatchMessageW@4()  + 0xf bytes          

            msenv.dll!EnvironmentMsgLoop()  + 0xb6 bytes

            msenv.dll!CMsoCMHandler::FPushMessageLoop()  + 0x36 bytes           

            msenv.dll!SCM::FPushMessageLoop()  + 0x4f bytes     

            msenv.dll!SCM_MsoCompMgr::FPushMessageLoop()  + 0x28 bytes      

            msenv.dll!CMsoComponent::PushMsgLoop()  + 0x28 bytes       

            msenv.dll!VStudioMainLogged()  + 0x19b bytes

            msenv.dll!_VStudioMain()  + 0x7d bytes

            devenv.exe!util_CallVsMain()  + 0xd8 bytes       

            devenv.exe!CDevEnvAppId::Run()  + 0x5cb bytes         

            devenv.exe!_WinMain@16()  + 0x60 bytes         

            devenv.exe!License::GetPID()  - 0x4cf9 bytes    

            kernel32.dll!_BaseProcessStart@4()  + 0x23 bytes         

 

Note how it’s easy to read the stack. The bottom of the stack (the first thing put on it) is _BaseProcessStart, which starts the process. You can see that a timer tick caused GetDataTip to call GenIntelliSenseInfo, which calls GenQuickInfo, -> LocateSymbol, etc.

 

Each stack entry indicates the name of the routine being executed. The “+ 0x23 bytes” means the # of bytes into the routine that the call occurred. A low number means near the beginning of that method.

 

Because XML is a tree, and is thus a recursive data structure, you see that XMLContent can contain an XMLExpression and vice versa. The depth of the recursion reflects the actual XML being processed.

 

 

BTW, the VB background compiler thread is:

 

0          >          4620     Worker Thread   ThreadSyncManager::ThreadProc           _KiFastSystemCallRet@0            Normal  0

 

And it’s stack at idle:

 

>          ntdll.dll!_KiFastSystemCallRet@0()       

            ntdll.dll!_ZwWaitForMultipleObjects@20()  + 0xc bytes   

            kernel32.dll!_WaitForMultipleObjectsEx@20()  - 0x48 bytes        

            user32.dll!_RealMsgWaitForMultipleObjectsEx@20()  + 0xd9 bytes        

            ole32.dll!CCliModalLoop::BlockFn()  + 0x76 bytes         

            ole32.dll!_CoWaitForMultipleHandles@20()  + 0xe6 bytes           

            msvb7.dll!ThreadSyncManager::ThreadProc()  + 0x98 bytes       

            kernel32.dll!_BaseThreadStart@8()  + 0x37 bytes           

 

 

 

See also:

Dynamically attaching a debugger

Is a process hijacking your machine?

Posted by Calvin_Hsia | 2 Comments
Filed under: , ,

Stack overflow, expand your stack? Change your algorithm!

In the last post, Area fill algorithm: crayons and coloring book, I showed a program that emulates a kid drawing in a coloring book.

 

However, the algorithm wasn’t very efficient, and would explode even if you had a simple drawing: it was using the stack to store where to go.

 

The heart of the routine:

                void AreaFill(Point ptcell)

                {

                    if (ptcell.X >= 0 && ptcell.X < m_numCells.Width)

                    {

                        if (ptcell.Y >= 0 && ptcell.Y < m_numCells.Height)

                        {

        //                    System.Threading.Thread.Sleep(100);

                            if (DrawACell(ptcell, m_brushFill))

                            {

                                m_oColor = Color.FromArgb((int)(((((uint)m_oColor.ToArgb() & 0xffffff) + 140) & 0xffffff) | 0xff000000));

                                m_brushFill = new SolidBrush(m_oColor);

                                AreaFill(new Point(ptcell.X - 1, ptcell.Y));

                                AreaFill(new Point(ptcell.X + 1, ptcell.Y));

                                AreaFill(new Point(ptcell.X, ptcell.Y + 1));

                                AreaFill(new Point(ptcell.X, ptcell.Y - 1));

                            }

                        }

                    }

                }

 

 

It just checks boundaries, Draws the current point, then calls itself to draw the points North, South, East and West. Doing this for thousands of pixels eats up the current thread’s stack very fast.

 

One way to fix this is to expand the stack size.

 

You can examine the default stack size of an executable (EXE or DLL) by opening a Visual Studio command prompt (Start->Programs->VS2008->VSToos->VSCommand prompt), then type

link /dump /headers d:\dev\cs\Fill\bin\Debug\Fill.exe

 

Microsoft (R) COFF/PE Dumper Version 9.00.21022.08

Copyright (C) Microsoft Corporation.  All rights reserved.

 

 

Dump of file Fill.exe

 

PE signature found

 

File Type: EXECUTABLE IMAGE

 

FILE HEADER VALUES

             14C machine (x86)

               3 number of sections

        4A1ECBC2 time date stamp Thu May 28 10:37:06 2009

               0 file pointer to symbol table

               0 number of symbols

              E0 size of optional header

             10E characteristics

                   Executable

                   Line numbers stripped

                   Symbols stripped

                   32 bit word machine

 

OPTIONAL HEADER VALUES

             10B magic # (PE32)

            8.00 linker version

            2000 size of code

             800 size of initialized data

               0 size of uninitialized data

            3E7E entry point (00403E7E)

            2000 base of code

            4000 base of data

          400000 image base (00400000 to 00407FFF)

            2000 section alignment

             200 file alignment

            4.00 operating system version

            0.00 image version

            4.00 subsystem version

               0 Win32 version

            8000 size of image

             200 size of headers

               0 checksum

               2 subsystem (Windows GUI)

             540 DLL characteristics

                   Dynamic base

                   NX compatible

                   No structured exception handler

          100000 size of stack reserve

            1000 size of stack commit

          100000 size of heap reserve

            1000 size of heap commit

 

This is a little misleading, because the default stack size is 100000 Hex, which is 1,048,576, or about 1 Megabyte.

 

You can change the stack size using Editbin.exe

 

D:\dev\cs\Fill\bin\Debug>link /dump /headers Fill.exe | find "stack"

          100000 size of stack reserve

            1000 size of stack commit

 

D:\dev\cs\Fill\bin\Debug>editbin /stack:10000,1000 Fill.exe

Microsoft (R) COFF/PE Editor Version 9.00.21022.08

Copyright (C) Microsoft Corporation.  All rights reserved.

 

D:\dev\cs\Fill\bin\Debug>link /dump /headers Fill.exe | find "stack"

            2710 size of stack reserve

             3E8 size of stack commit

 

 

 

A better way to fix this is to use heap memory , rather than the stack:

 

 

        //* More efficient algorithm: don't use the stack to store state

        void AreaFill(Point ptcell)

        {

            Queue<Point> queueCells = new Queue<Point>();

            queueCells.Enqueue(ptcell);

            while (queueCells.Count > 0)

            {

                ptcell = queueCells.Dequeue();

                if (ptcell.X >= 0 && ptcell.X < m_numCells.Width)

                {

                    if (ptcell.Y >= 0 && ptcell.Y < m_numCells.Height)

                    {

                        //                    System.Threading.Thread.Sleep(100);

                        if (DrawACell(ptcell, m_brushFill))

                        {

                            m_oColor = Color.FromArgb((int)(((((uint)m_oColor.ToArgb() & 0xffffff) + 140) & 0xffffff) | 0xff000000));

                            m_brushFill = new SolidBrush(m_oColor);

                            queueCells.Enqueue(new Point(ptcell.X - 1, ptcell.Y));

                            queueCells.Enqueue(new Point(ptcell.X + 1, ptcell.Y));

                            queueCells.Enqueue(new Point(ptcell.X, ptcell.Y + 1));

                            queueCells.Enqueue(new Point(ptcell.X, ptcell.Y - 1));

                        }

                    }

                }

            }

        }

 

This solution is almost the same as the first: however, instead of recurring to go North, etc, the routine just puts the points to work on into a queue.

 

The VB solution is analogous:

    Sub AreaFill(ByVal ptcell As Point)

        Dim queueCells = New Queue(Of Point)

        queueCells.Enqueue(ptcell)

        While queueCells.Count > 0

            ptcell = queueCells.Dequeue()

            If (ptcell.X >= 0 And ptcell.X < m_numCells.Width) Then

 

                If (ptcell.Y >= 0 And ptcell.Y < m_numCells.Height) Then

                    '                    System.Threading.Thread.Sleep(100);

                    If (DrawACell(ptcell, m_brushFill)) Then

                        Me.m_oColor = Color.FromArgb((((Me.m_oColor.ToArgb And &HFFFFFF) + 140) And &HFFFFFF) Or &HFF000000)

 

                        '    m_oColor = Color.FromArgb((int)(((((uint)m_oColor.ToArgb() & 0xffffff) + 140) & 0xffffff) | 0xff000000));

                        m_brushFill = New SolidBrush(m_oColor)

                        queueCells.Enqueue(New Point(ptcell.X - 1, ptcell.Y))

                        queueCells.Enqueue(New Point(ptcell.X + 1, ptcell.Y))

                        queueCells.Enqueue(New Point(ptcell.X, ptcell.Y + 1))

                        queueCells.Enqueue(New Point(ptcell.X, ptcell.Y - 1))

                    End If

                End If

            End If

        End While

    End Sub

 

 

 

 

See also:

Tail Recursion

Cartoon animation program

Comment/Uncomment code to switch versions quickly without using macros

 

http://msdn.microsoft.com/en-us/library/8cxs58a6.aspx

http://bytes.com/groups/net-c/229335-stack-size

 

Posted by Calvin_Hsia | 1 Comments
Filed under: , ,

Area fill algorithm: crayons and coloring book

Kids know how to use crayons and a  coloring book. How do you write such a program?

 

In my last post (Which pixels do you turn on when you draw a line?) I showed how to draw a line. Now suppose you have some lines or shapes already drawn. How would you write code to fill in an area bounded by the drawn pixels?

 

IOW, imagine you’ve drawn a circle. You want to fill the circle with a color. Right click inside. What code should run? What if the figure were more complex, like a large block “W” or a curled up snake.

 

More formally: Given an array of pixels that represents the outline of a shape, and a point’s  x,y coordinate within that shape, how would you area fill that shape?

 

Perhaps you could write some code that will check all pixels to the East, North, West, South until it reached a pixel that was already painted. Then what?

 

What sort of algorithm and data structure would you use?

 

 

Below are C# and VB versions of a simple implementation that isn’t very efficient, but is quite simple.

 

The code is identical to the last post (the drawing parts) except for the areas delimited by “AREA”. This makes it easier for you to cut/paste the code.

 

Start Visual Studio 2008

Choose  File->New->Project->C# or VB->Windows Forms Application.

Choose View->Code

 

Paste in the VB or C# version of the code below, hit F5 to run it. Draw a shape, right click to fill.

 

How would you improve it? Why isn’t it efficient?

 

Try making the form bigger and paint more pixels. What happens?

 

Clue:  look at the source code for MineSweeper that I wrote to take advantage of the (then) new feature Collections. It’s part of the Task Pane for Visual Foxpro 9.0

 

See also:

Remove double spaces from pasted code samples in blog

 

 

<C# Sample>

#define AREA 

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Linq;

using System.Text;

using System.Windows.Forms;

 

namespace WindowsFormsApplication1

{

    public partial class Form1 : System.Windows.Forms.Form

    {

        Size m_numCells = new Size(350, 200);// we'll use an array of Cells

        Boolean[,] m_cells; // the array of cells: whether they've been drawn or not

        Size m_cellSize = new Size(8, 8);  // cell height & width

        Size m_Offset = new Size(0, 0);

        bool m_MouseDown = false;

        Button btnErase;

        Point? m_PtOld;

        SolidBrush m_brushMouse = new SolidBrush(Color.Red);

        SolidBrush m_brushGenerated = new SolidBrush(Color.Black);

        delegate bool DrawCellDelegate(Point ptcell, Brush br);

        Graphics m_oGraphics;

        public Form1()

        {

            this.Load += new EventHandler(this.Loaded);

        }

        void Loaded(Object o, EventArgs e)

        {

            this.Width = 600;

            this.Height = 400;

            this.btnErase = new Button();

            this.btnErase.Text = "&Erase";

            this.btnErase.Click += new EventHandler(this.btnErase_Click);

            this.Controls.Add(this.btnErase);

            this.BackColor = Color.White;

            btnErase_Click(null, null);

        }

        void btnErase_Click(object sender, System.EventArgs e)

        {

            m_oGraphics = Graphics.FromHwnd(this.Handle);

            m_numCells.Width = this.Width / m_cellSize.Width;

            m_numCells.Height = this.Height / m_cellSize.Height;

            m_cells = new Boolean[m_numCells.Width, m_numCells.Height];

            m_oGraphics.FillRectangle(Brushes.White, new Rectangle(0, 0, this.Width, this.Height));

        }

        Point PointToCell(Point p1)

        {

            Point ptcell = new Point(

            (p1.X - m_Offset.Width) / m_cellSize.Width,

            (p1.Y - m_Offset.Height) / m_cellSize.Height);

            return ptcell;

        }

        protected override void OnMouseDown(MouseEventArgs e)

        {

            if (e.Button == MouseButtons.Left)

            {

                m_MouseDown = true;

                m_PtOld = new Point(e.X, e.Y);

                CheckMouseDown(e);

            }

#if AREA

            else

            {

                AreaFill(PointToCell(new Point(e.X, e.Y)));

            }

#endif

        }

        protected override void OnMouseMove(MouseEventArgs e)

        {

            if (m_MouseDown)

            {

                CheckMouseDown(e);

            }

        }

        protected override void OnMouseUp(MouseEventArgs e)

        {

            m_MouseDown = false;

        }

        void CheckMouseDown(MouseEventArgs e)

        {

            Point ptMouse = new Point(e.X, e.Y);

            Point ptcell = PointToCell(ptMouse);

            if (ptcell.X >= 0 && ptcell.X < m_numCells.Width &&

                ptcell.Y >= 0 && ptcell.Y < m_numCells.Height)

            {

                DrawLineOfCells(PointToCell(m_PtOld.Value), ptcell, new DrawCellDelegate(DrawACell));

                m_PtOld = ptMouse;

            }

        }

 

        bool DrawACell(Point ptcell, Brush br)

        {

            bool fDidDraw = false;

            if (!m_cells[ptcell.X, ptcell.Y]) // if not drawn already

            {

                m_cells[ptcell.X, ptcell.Y] = true;

                //*

                m_oGraphics.FillRectangle(br,

                    m_Offset.Width + ptcell.X * m_cellSize.Width,

                    m_Offset.Height + ptcell.Y * m_cellSize.Height,

                    m_cellSize.Width,

                    m_cellSize.Height);

                /*/

                 g.DrawRectangle(new Pen(Color.Blue,1),

                    m_Offset.Width + ptcell.X * m_cellSize.Width,

                    m_Offset.Height + ptcell.Y * m_cellSize.Height,

                    m_cellSize.Width,

                    m_cellSize.Height);

                

                  //*/

                fDidDraw = true;

            }

            return fDidDraw;

        }

        void DrawLineOfCells(Point p1, Point p2, DrawCellDelegate drawit)

        {

            // http://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm

            Brush br =  m_brushMouse;

            int x0 = p1.X;

            int y0 = p1.Y;

            int x1 = p2.X;

            int y1 = p2.Y;

            int x, cx, deltax, xstep,

                y, cy, deltay, ystep,

                 error;

            bool st;

 

            // find largest delta for pixel steps

            st = (Math.Abs(y1 - y0) > Math.Abs(x1 - x0));

 

            // if deltay > deltax then swap x,y

            if (st)

            {

                x0 ^= y0; y0 ^= x0; x0 ^= y0; // swap(x0, y0);

                x1 ^= y1; y1 ^= x1; x1 ^= y1; // swap(x1, y1);

            }

 

            deltax = Math.Abs(x1 - x0);

            deltay = Math.Abs(y1 - y0);

            error = (deltax / 2);

            y = y0;

 

            if (x0 > x1) { xstep = -1; }

            else { xstep = 1; }

 

            if (y0 > y1) { ystep = -1; }

            else { ystep = 1; }

 

            for (x = x0; (x != (x1 + xstep)); x += xstep)

            {

                cx = x; cy = y; // copy of x, copy of y

 

                // if x,y swapped above, swap them back now

                if (st)

                {

                    cx ^= cy; cy ^= cx; cx ^= cy;

                }

                if (drawit(new Point(cx, cy), br))

                {

                    br = m_brushGenerated;

                }

 

                error -= deltay; // converge toward end of line

                if (error < 0)

                { // not done yet

                    y += ystep;

                    error += deltax;

                }

            }

        }

 

#if AREA

 

        SolidBrush m_brushFill = new SolidBrush(Color.Blue);

        Color m_oColor = Color.Black;

        /*

        /*/

                void AreaFill(Point ptcell)

                {

                    if (ptcell.X >= 0 && ptcell.X < m_numCells.Width)

                    {

                        if (ptcell.Y >= 0 && ptcell.Y < m_numCells.Height)

                        {

        //                    System.Threading.Thread.Sleep(100);

                            if (DrawACell(ptcell, m_brushFill))

                            {

                                m_oColor = Color.FromArgb((int)(((((uint)m_oColor.ToArgb() & 0xffffff) + 140) & 0xffffff) | 0xff000000));

                                m_brushFill = new SolidBrush(m_oColor);

                                AreaFill(new Point(ptcell.X - 1, ptcell.Y));

                                AreaFill(new Point(ptcell.X + 1, ptcell.Y));

                                AreaFill(new Point(ptcell.X, ptcell.Y + 1));

                                AreaFill(new Point(ptcell.X, ptcell.Y - 1));

                            }

                        }

                    }

                }

        //*/

#endif

    }

}

 

</C# Sample>

 

<VB Sample>

#Const AREA = True

Public Class Form1

    Dim m_numCells = New Size(350, 300) ' we'll use an array of cells

    Dim m_cells(,) As Boolean   ' the array of cells: whether they've been drawn or not

    Dim m_cellSize = New Size(8, 8) ' cell size & width

    Dim m_Offset = New Size(0, 0)

    Dim m_MouseDown = False

    Dim WithEvents btnErase As Button

    Dim m_PtOld As Point?

    Dim m_brushGenerated = New SolidBrush(Color.Black)

    Dim m_brushMouse = New SolidBrush(Color.Red)

    Dim m_oGraphics As Graphics

    Delegate Function DrawCellDelegate(ByVal ptCell As Point, ByVal br As Brush) As Boolean

 

    Sub Form_Load() Handles Me.Load

        Me.Width = 600

        Me.Height = 400

        Me.btnErase = New Button()

        Me.btnErase.Text = "&Erase"

        Me.Controls.Add(Me.btnErase)

        Me.BackColor = Color.White

        btnErase_Click()

 

    End Sub

    Sub btnErase_Click() Handles btnErase.Click

        m_oGraphics = Graphics.FromHwnd(Me.Handle)

        m_numCells.Width = Me.Width / m_cellSize.Width

        m_numCells.Height = Me.Height / m_cellSize.Height

        ReDim m_cells(m_numCells.Width, m_numCells.Height)

        m_oGraphics.FillRectangle(Brushes.White, New Rectangle(0, 0, Me.Width, Me.Height))

 

    End Sub

    Function PointToCell(ByVal p1 As Point) As Point

        Dim ptcell = New Point( _

            (p1.X - m_Offset.Width) / m_cellSize.Width, _

            (p1.Y - m_Offset.Height) / m_cellSize.Height)

        Return ptcell

 

    End Function

    Protected Overrides Sub OnMouseDown(ByVal e As System.Windows.Forms.MouseEventArgs)

        If e.Button = Windows.Forms.MouseButtons.Left Then

            m_MouseDown = True

            m_PtOld = New Point(e.X, e.Y)

            CheckMouseDown(e)

#If AREA Then

        Else

            AreaFill(PointToCell(New Point(e.X, e.Y)))

#End If

        End If

    End Sub

    Protected Overrides Sub OnMouseMove(ByVal e As System.Windows.Forms.MouseEventArgs)

        If m_MouseDown Then

            CheckMouseDown(e)

        End If

    End Sub

    Protected Overrides Sub OnMouseUp(ByVal e As System.Windows.Forms.MouseEventArgs)

        m_MouseDown = False

    End Sub

    Sub CheckMouseDown(ByVal e As MouseEventArgs)

        Dim ptMouse = New Point(e.X, e.Y)

        Dim ptcell = PointToCell(ptMouse)

        If (ptcell.X >= 0 And ptcell.X < m_numCells.Width And _

            ptcell.Y >= 0 And ptcell.Y < m_numCells.Height) Then

 

            DrawLineOfCells(PointToCell(m_PtOld.Value), ptcell, New DrawCellDelegate(AddressOf DrawACell))

            m_PtOld = ptMouse

        End If

    End Sub

    Function DrawACell(ByVal ptCell As Point, ByVal br As Brush) As Boolean

        Dim fDidDraw = False

        If Not m_cells(ptCell.X, ptCell.Y) Then

            m_cells(ptCell.X, ptCell.Y) = True

            m_oGraphics.FillRectangle(br, _

                    m_Offset.Width + ptCell.X * m_cellSize.Width, _

                    m_Offset.Height + ptCell.Y * m_cellSize.Height, _

                    m_cellSize.Width, _

                    m_cellSize.Height)

            fDidDraw = True

        End If

        Return fDidDraw

    End Function

    Sub DrawLineOfCells(ByVal p0 As Point, ByVal p1 As Point, ByVal drawit As DrawCellDelegate)

        Dim br = m_brushMouse

        Dim x0 = p0.X

        Dim y0 = p0.Y

        Dim x1 = p1.X

        Dim y1 = p1.Y

        Dim fSwapped = False

        Dim dx = Math.Abs(x1 - x0)

        Dim dy = Math.Abs(y1 - y0)

        If dy > dx Then

            fSwapped = True ' swap x0<=>y0, x1<->y1

            x0 = p0.Y

            y0 = p0.X

            x1 = p1.Y

            y1 = p1.X

            dx = Math.Abs(x1 - x0)

            dy = Math.Abs(y1 - y0)

        End If

        Dim err = CInt(dx / 2)

        Dim y = y0

        Dim xstep = 1

        If x0 > x1 Then

            xstep = -1

        End If

        Dim ystep = 1

        If y0 > y1 Then

            ystep = -1

        End If

        Dim x = x0

        While x <> x1 + xstep

            Dim cx = x, cy = y ' copy of x,y

            If fSwapped Then

                cx = y

                cy = x

            End If

            If drawit(New Point(cx, cy), br) Then ' if it wasn't already drawn

                br = m_brushGenerated

            End If

            err -= dy

            If err < 0 Then

                y += ystep

                err += dx

            End If

            x += xstep

        End While

    End Sub

#If AREA Then

    Dim m_brushFill = New SolidBrush(Color.Blue)

    Dim m_oColor = Color.Black

    Sub AreaFill(ByVal ptcell As Point)

        If (ptcell.X >= 0 And ptcell.X < m_numCells.Width) Then

 

            If (ptcell.Y >= 0 And ptcell.Y < m_numCells.Height) Then

                '                    System.Threading.Thread.Sleep(100);

                If (DrawACell(ptcell, m_brushFill)) Then

                    Me.m_oColor = Color.FromArgb((((Me.m_oColor.ToArgb And &HFFFFFF) + 140) And &HFFFFFF) Or &HFF000000)

 

                    '    m_oColor = Color.FromArgb((int)(((((uint)m_oColor.ToArgb() & 0xffffff) + 140) & 0xffffff) | 0xff000000));

                    m_brushFill = New SolidBrush(m_oColor)

                    AreaFill(New Point(ptcell.X - 1, ptcell.Y)) ' West

                    AreaFill(New Point(ptcell.X + 1, ptcell.Y)) ' East

                    AreaFill(New Point(ptcell.X, ptcell.Y + 1)) ' South

                    AreaFill(New Point(ptcell.X, ptcell.Y - 1)) ' North

                End If

            End If

        End If

    End Sub

#End If

 

End Class

 

</VB Sample>

 

Posted by Calvin_Hsia | 4 Comments
Filed under: , ,

Which pixels do you turn on when you draw a line?

When I wrote my cartoon animation program almost 30 years ago (see Cartoon animation program) I needed to know how to draw a line.

 

Of course, nowadays, we just call a library function that will draw a line given two points.

 

If you think about it, the problem is quite complex. Imagine a rectangular array of pixels. Which ones do you paint in order to “see” a straight line?

 

If the desired line is horizontal, or vertical, the problem is simple. However, choosing which pixels to draw for an oblique line is an interesting mathematical problem: see Bresenham's line algorithm - Wikipedia, the free encyclopedia

 

Even back then, I found Bresenham’s algorithm in a book and implemented it for my cartoon program.

 

Suppose you want to create a simple drawing program. When you move the mouse, you can handle the MouseMove event, and light up the pixel at the position of the mouse.

 

However, this will only create a dotted line, with fewer dots if the mouse moved quickly.

 

Let’s see if we can use Bresenham’s algorithm to light up the pixels between successive mouse events.

 

This sample uses larger “pixels”, so you can actually see them as squares. In the code I called them cells (mainly because I stole a lot of the code from my game of Life (see Cellular Automata: The Game of Life )

 

The actual mouse down causes a red pixel to be drawn, and the generated ones are black.

 

Below are C# and VB versions.

 

Start Visual Studio 2008

Choose  File->New->Project->C# or VB->Windows Forms Application.

Choose View->Code

 

Paste in the VB or C# version of the code below, hit F5 to run it.

 

Move the mouse slowly and you’ll see more red pixels. Quickly, and you’ll see more black generated pixels.

Note how the code needs to distinguish between 2 sets of x-y coordinates: actual pixels, and cell coordinates.

Try adjusting the Cell size.

 

Think about what a right click would do.

 

See also:

Comment/Uncomment code to switch versions quickly without using macros

Remove double spaces from pasted code samples in blog

 

 

<C#Sample>

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Linq;

using System.Text;

using System.Windows.Forms;

 

namespace WindowsFormsApplication1

{

    public partial class Form1 : System.Windows.Forms.Form

    {

        Size m_numCells = new Size(350, 200);// we'll use an array of Cells

        Boolean[,] m_cells; // the array of cells: whether they've been drawn or not

        Size m_cellSize = new Size(8, 8);  // cell height & width

        Size m_Offset = new Size(0, 0);

        bool m_MouseDown = false;

        Button btnErase;

        Point? m_PtOld;

        SolidBrush m_brushMouse = new SolidBrush(Color.Red);

        SolidBrush m_brushGenerated = new SolidBrush(Color.Black);

        delegate bool DrawCellDelegate(Point ptcell, Brush br);

        Graphics m_oGraphics;

        public Form1()

        {

            this.Load += new EventHandler(this.Loaded);

        }

        void Loaded(Object o, EventArgs e)

        {

            this.Width = 600;

            this.Height = 400;

            this.btnErase = new Button();

            this.btnErase.Text = "&Erase";

            this.btnErase.Click += new EventHandler(this.btnErase_Click);

            this.Controls.Add(this.btnErase);

            this.BackColor = Color.White;

            btnErase_Click(null, null);

        }

        void btnErase_Click(object sender, System.EventArgs e)

        {

            m_oGraphics = Graphics.FromHwnd(this.Handle);

            m_numCells.Width = this.Width / m_cellSize.Width;

            m_numCells.Height = this.Height / m_cellSize.Height;

            m_cells = new Boolean[m_numCells.Width, m_numCells.Height];

            m_oGraphics.FillRectangle(Brushes.White, new Rectangle(0, 0, this.Width, this.Height));

        }

        Point PointToCell(Point p1)

        {

            Point ptcell = new Point(

            (p1.X - m_Offset.Width) / m_cellSize.Width,

            (p1.Y - m_Offset.Height) / m_cellSize.Height);

            return ptcell;

        }

        protected override void OnMouseDown(MouseEventArgs e)

        {

            if (e.Button == MouseButtons.Left)

            {

                m_MouseDown = true;

                m_PtOld = new Point(e.X, e.Y);

                CheckMouseDown(e);

            }

#if AREA

            else

            {

                AreaFill(PointToCell(new Point(e.X, e.Y)));

            }

#endif

        }

        protected override void OnMouseMove(MouseEventArgs e)

        {

            if (m_MouseDown)

            {

                CheckMouseDown(e);

            }

        }

        protected override void OnMouseUp(MouseEventArgs e)

        {

            m_MouseDown = false;

        }

        void CheckMouseDown(MouseEventArgs e)

        {

            Point ptMouse = new Point(e.X, e.Y);

            Point ptcell = PointToCell(ptMouse);

            if (ptcell.X >= 0 && ptcell.X < m_numCells.Width &&

                ptcell.Y >= 0 && ptcell.Y < m_numCells.Height)

            {

                DrawLineOfCells(PointToCell(m_PtOld.Value), ptcell, new DrawCellDelegate(DrawACell));

                m_PtOld = ptMouse;

            }

        }

 

        bool DrawACell(Point ptcell, Brush br)

        {

            bool fDidDraw = false;

            if (!m_cells[ptcell.X, ptcell.Y]) // if not drawn already

            {

                m_cells[ptcell.X, ptcell.Y] = true;

                //*

                m_oGraphics.FillRectangle(br,

                    m_Offset.Width + ptcell.X * m_cellSize.Width,

                    m_Offset.Height + ptcell.Y * m_cellSize.Height,

                    m_cellSize.Width,

                    m_cellSize.Height);

                /*/

                 g.DrawRectangle(new Pen(Color.Blue,1),

                    m_Offset.Width + ptcell.X * m_cellSize.Width,

                    m_Offset.Height + ptcell.Y * m_cellSize.Height,

                    m_cellSize.Width,

                    m_cellSize.Height);

                

                  //*/

                fDidDraw = true;

            }

            return fDidDraw;

        }

        void DrawLineOfCells(Point p1, Point p2, DrawCellDelegate drawit)

        {

            // http://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm

            Brush br =  m_brushMouse;

            int x0 = p1.X;

            int y0 = p1.Y;

            int x1 = p2.X;

            int y1 = p2.Y;

            int x, cx, deltax, xstep,

                y, cy, deltay, ystep,

                 error;

            bool st;

 

            // find largest delta for pixel steps

            st = (Math.Abs(y1 - y0) > Math.Abs(x1 - x0));

 

            // if deltay > deltax then swap x,y

            if (st)

            {

                x0 ^= y0; y0 ^= x0; x0 ^= y0; // swap(x0, y0);

                x1 ^= y1; y1 ^= x1; x1 ^= y1; // swap(x1, y1);

            }

 

            deltax = Math.Abs(x1 - x0);

            deltay = Math.Abs(y1 - y0);

            error = (deltax / 2);

            y = y0;

 

            if (x0 > x1) { xstep = -1; }

            else { xstep = 1; }

 

            if (y0 > y1) { ystep = -1; }

            else { ystep = 1; }

 

            for (x = x0; (x != (x1 + xstep)); x += xstep)

            {

                cx = x; cy = y; // copy of x, copy of y

 

                // if x,y swapped above, swap them back now

                if (st)

                {

                    cx ^= cy; cy ^= cx; cx ^= cy;

                }

                if (drawit(new Point(cx, cy), br))

                {

                    br = m_brushGenerated;

                }

 

                error -= deltay; // converge toward end of line

                if (error < 0)

                { // not done yet

                    y += ystep;

                    error += deltax;

                }

            }

        }

    }

}

</C#Sample>

 

 

 

<VBSample>

Public Class Form1

    Dim m_numCells = New Size(350, 300) ' we'll use an array of cells

    Dim m_cells(,) As Boolean   ' the array of cells: whether they've been drawn or not

    Dim m_cellSize = New Size(8, 8) ' cell size & width

    Dim m_Offset = New Size(0, 0)

    Dim m_MouseDown = False

    Dim WithEvents btnErase As Button

    Dim m_PtOld As Point?

    Dim m_brushGenerated = New SolidBrush(Color.Black)

    Dim m_brushMouse = New SolidBrush(Color.Red)

    Dim m_oGraphics As Graphics

    Delegate Function DrawCellDelegate(ByVal ptCell As Point, ByVal br As Brush) As Boolean

 

    Sub Form_Load() Handles Me.Load

        Me.Width = 600

        Me.Height = 400

        Me.btnErase = New Button()

        Me.btnErase.Text = "&Erase"

        Me.Controls.Add(Me.btnErase)

        Me.BackColor = Color.White

        btnErase_Click()

 

    End Sub

    Sub btnErase_Click() Handles btnErase.Click

        m_oGraphics = Graphics.FromHwnd(Me.Handle)

        m_numCells.Width = Me.Width / m_cellSize.Width

        m_numCells.Height = Me.Height / m_cellSize.Height

        ReDim m_cells(m_numCells.Width, m_numCells.Height)

        m_oGraphics.FillRectangle(Brushes.White, New Rectangle(0, 0, Me.Width, Me.Height))

 

    End Sub

    Function PointToCell(ByVal p1 As Point) As Point

        Dim ptcell = New Point( _

            (p1.X - m_Offset.Width) / m_cellSize.Width, _

            (p1.Y - m_Offset.Height) / m_cellSize.Height)

        Return ptcell

 

    End Function

    Protected Overrides Sub OnMouseDown(ByVal e As System.Windows.Forms.MouseEventArgs)

        If e.Button = Windows.Forms.MouseButtons.Left Then

            m_MouseDown = True

            m_PtOld = New Point(e.X, e.Y)

            CheckMouseDown(e)

#If AREA Then

        Else

            AreaFill(PointToCell(New Point(e.X, e.Y)))

#End If

        End If

    End Sub

    Protected Overrides Sub OnMouseMove(ByVal e As System.Windows.Forms.MouseEventArgs)

        If m_MouseDown Then

            CheckMouseDown(e)

        End If

    End Sub

    Protected Overrides Sub OnMouseUp(ByVal e As System.Windows.Forms.MouseEventArgs)

        m_MouseDown = False

    End Sub

    Sub CheckMouseDown(ByVal e As MouseEventArgs)

        Dim ptMouse = New Point(e.X, e.Y)

        Dim ptcell = PointToCell(ptMouse)

        If (ptcell.X >= 0 And ptcell.X < m_numCells.Width And _

            ptcell.Y >= 0 And ptcell.Y < m_numCells.Height) Then

 

            DrawLineOfCells(PointToCell(m_PtOld.Value), ptcell, New DrawCellDelegate(AddressOf DrawACell))

            m_PtOld = ptMouse

        End If

    End Sub

    Function DrawACell(ByVal ptCell As Point, ByVal br As Brush) As Boolean

        Dim fDidDraw = False

        If Not m_cells(ptCell.X, ptCell.Y) Then

            m_cells(ptCell.X, ptCell.Y) = True

            m_oGraphics.FillRectangle(br, _

                    m_Offset.Width + ptCell.X * m_cellSize.Width, _

                    m_Offset.Height + ptCell.Y * m_cellSize.Height, _

                    m_cellSize.Width, _

                    m_cellSize.Height)

            fDidDraw = True

        End If

        Return fDidDraw

    End Function

    Sub DrawLineOfCells(ByVal p0 As Point, ByVal p1 As Point, ByVal drawit As DrawCellDelegate)

        Dim br = m_brushMouse

        Dim x0 = p0.X

        Dim y0 = p0.Y

        Dim x1 = p1.X

        Dim y1 = p1.Y

        Dim fSwapped = False

        Dim dx = Math.Abs(x1 - x0)

        Dim dy = Math.Abs(y1 - y0)

        If dy > dx Then

            fSwapped = True ' swap x0<=>y0, x1<->y1

            x0 = p0.Y

            y0 = p0.X

            x1 = p1.Y

            y1 = p1.X

            dx = Math.Abs(x1 - x0)

            dy = Math.Abs(y1 - y0)

        End If

        Dim err = CInt(dx / 2)

        Dim y = y0

        Dim xstep = 1

        If x0 > x1 Then

            xstep = -1

        End If

        Dim ystep = 1

        If y0 > y1 Then

            ystep = -1

        End If

        Dim x = x0

        While x <> x1 + xstep

            Dim cx = x, cy = y ' copy of x,y

            If fSwapped Then

                cx = y

                cy = x

            End If

            If drawit(New Point(cx, cy), br) Then ' if it wasn't already drawn

                br = m_brushGenerated

            End If

            err -= dy

            If err < 0 Then

                y += ystep

                err += dx

            End If

            x += xstep

        End While

    End Sub

 

 

End Class

 

</VBSample>

Posted by Calvin_Hsia | 3 Comments
Filed under: , ,

Comment/Uncomment code to switch versions quickly without using macros

In a typical day, I write or debug programs in several languages: typically Foxpro, C#, VB, C++ and 32 bit assembly, with an occasional MSIL, IDL and 64 bit ASM thrown in.

 

Sometimes, I like to switch between one version of code and another. This is useful if I want to do side by side comparisons of behavior.

 

One way to do this is with preprocessor macros, like this:

 

#If  SomeValue

                <one version of code>

#else

                <another version>

#endif

 

However, that’s a fair amount of typing.

 

There’s a shortcut that works with C# and C++ style comments.

 

In these languages, a line that starts with “//” is a comment.

 

Also, a block comment (which can span multiple lines) starts with “/*” and ends with “*/”

 

 

 

//*

      int sub foo1() {

            int x = 2;

                  Console.WriteLine((new System.Diagnostics.StackTrace().GetFrames()[0].GetMethod().Name)); // shows Foo1

            return x;

      }

 

/*/

      int sub foo2() {

            int x = 3;

                  Console.WriteLine((new System.Diagnostics.StackTrace().GetFrames()[0].GetMethod().Name)); // shows Foo2

            return x;

      }

// */

 

With a single character change I can switch between foo1 and foo2: just delete the very first “/”.  That changes the single line comment into a block comment. The “*/” of the “/*/” now acts like the end of the comment block.

 

Using an editor that colors the code (like Visual Studio) shows the switch properly

 

 

 

/*

      int sub foo1() {

            int x = 2;

                  Console.WriteLine((new System.Diagnostics.StackTrace().GetFrames()[0].GetMethod().Name)); // shows Foo1

            return x;

      }

 

/*/

      int sub foo2() {

            int x = 3;

                  Console.WriteLine((new System.Diagnostics.StackTrace().GetFrames()[0].GetMethod().Name)); // shows Foo2

            return x;

      }

// */

 

This technique is useful when creating sample code for others to play with, such as in my next blog post.

 

 

 

Unorthodox chopsticks

Several years ago, my wife and I were walking through a local shopping mall. At the time, there was some sort of Asian festival. At a display booth there was a table upon which were two trays, side by side. One was empty, and the other had many beans. The sign challenged visitors to see how many beans could be moved to the empty tray with chopsticks in one minute.  A piece of paper indicated the top score so far: something like 10. I imagined how somebody could have spent 6 seconds per bean…

 

My wife, being quite adept with the tool, was able to get a respectable number of beans across, despite the difficulty of picking up a single slippery bean.

 

Then she challenged me, having used chopsticks all my life, to see if I could beat her score.

 

To her chagrin, I deftly wielded the sticks, one in each hand, horizontally in parallel to scoop up dozens of beans at a time, crushing the day’s high score. Of course this wasn’t the normal way most people use chopsticks, but then, it easily was the best solution to the problem at hand.

 

(I’m sure some of you are thinking that a better solution be to pick up the full tray and pour the beans onto the empty tray, but how hard would it be to lift the tray with chopsticks?)

 

 

 

At a recent social gathering, we were divided into groups to solve some word puzzles: given a fairly long word, how many words can be formed from the letters of the original word before the timer rang.

 

Many people would write out the given word as soon as the timer started like so:

 

            E S T A B L I S H M E N T

 

so that others in the group could think up words.

 

When it was my turn to write the word, I wrote it like this:

 

                        E S T A

                        B L I S H

                        M E N T

 

This gave our group 2 dimensions along which to see letter combinations to form words.

 

 

 

My kids had a toy recently, and it had a box into which we had to insert batteries. The box had very tiny Philips screws on it, and my kids tried various small screwdrivers, even my set of jeweler’s drivers. I could get the screws to turn, but they never came out. It turns out that the screws were decorative: just pull the top off the box<sigh>. 

 

(In the old days, no battery operated toys protected their owners from the hazardous battery voltages. Nowadays, probably due to some lawsuit, every compartment seems to need to protect kids from 1.5 volts)

 

 

I spend much of my time solving problems. Sometimes I get so involved in a problem I get stuck. Perhaps then, it is a good time to take a break, rethink the approach, use the tools a little differently or even just kick the tires. Often talking about it with someone else can give useful insights. Doing the unorthodox or unthinkable might just solve the problem!

 

 

See also:

A phone number challenge

A Discounter Introduces Reductions: Multiple Anagrams

Carburetor is a car part, but prosecutable is not

Write your own hangman game

Create your own Word Search puzzles

The Nametag Game

Create your own typing tutor!

Posted by Calvin_Hsia | 7 Comments
Filed under: ,

Cartoon animation program

A cartoon can be thought of as a series of drawings. To simulate movement, the drawings can be slightly different from each other.

 

Remember drawing simple cartoons using a pad of paper? Simply flipping through the pages made the drawings come to life.

 

This was tedious work: a computer can help.

 

Just after first IBM PC came out in Aug 81, I wrote a cartoon animation program in C++ and assembly code. The concept was very simple: just use the mouse (I had to write my own mouse driver for a RS-232 serial mouse and had to hijack the COM1 port for my DOS program) to let the user draw some lines on the screen. Then the user could save those lines as a cartoon frame, and then draw another frame. The program could then calculate multiple frames in between the user created frames, creating smooth animation.

 

That early 80's version of cartoon still runs on XP (although it writes directly to the video memory so it requires full screen mode.

It doesn't run on Vista (I think you can download a DOS compatible window to make it work.)

 

A zip file of the 80's version is available here. Unzip the contents to a folder (check out the date stamps!), then run cartoon.exe and hit Shift-A.

It includes some stored samples, like flying birds, alphabet, basketball. Because this was written before Windows, it won't work with your mouse. You can see the main menu bar at the top. The only thing you can do with this program is run the stored samples by hitting Shift-A. Q will Quit. Try typing other chars to invoke various commands.

If you get it to run on Vista, please let me know the details.

 

About 10 years ago, I wrote another version of Cartoon in Foxpro and it was modified and published as a Solution Sample.

 

Start Visual Foxpro, Task Pane, Solution Samples. In the "Search for sample" text, box, type in Animation. "Display line animation in a form"

You can run the form, or click on the button on the right of the task pane that opens  the form in the Form Designer, so you can see the source code.

 

You can also open the form in the Class Browser, then export the code into a single file using the "View Class Code" button.

 

Foxpro excerpt of the inbetween algorithm (notice how cursors (in-memory data tables) are used)

 

      SELECT (lcTable)

      DO WHILE !EOF("shadow")

            mr = recno()

            mr2 = recno("shadow")

            FOR nb = 0 TO nBetween

                  THISFORMSET.frmAnimation.cls

                  GO mr

                  IF mr2 < RECCOUNT("shadow")

                        GO mr2 IN shadow

                  ENDIF

                  nFrames1 = &lcTable..frameno

                  nFrames2 = shadow.frameno

                  SCAN WHILE &lcTable..frameno = nFrames1

                        nx1 = &lcTable..x1 + nb * (shadow.x1 - &lcTable..x1) / nBetween

                        ny1 = &lcTable..y1 + nb * (shadow.y1 - &lcTable..y1) / nBetween

                        nx2 = &lcTable..x2 + nb * (shadow.x2 - &lcTable..x2) / nBetween

                        ny2 = &lcTable..y2 + nb * (shadow.y2 - &lcTable..y2) / nBetween

                        THISFORMSET.frmAnimation.line(nx1,ny1,nx2,ny2)

                        IF !EOF("shadow")

                              SKIP IN shadow

                              IF shadow.frameno # nFrames2

                                    SKIP -1 IN shadow

                              ENDIF

                        ENDIF

                  ENDSCAN

                  SELECT shadow

                  IF !EOF()

                        SKIP

                        LOCATE REST FOR shadow.Frameno # nFrames2

                  ENDIF

                  SELECT (lcTable)

                  wait wind "" time .05

            ENDFOR

      ENDDO

      USE IN shadow

      SCAN REST

            THISFORMSET.frmAnimation.line(x1,y1,x2,y2)

      ENDSCAN

      THISFORMSET.frmAnimation.frameno = nFrames1 + 1

 

 

 

Below is a more up to date example using WPF.  The equivalent animation code is in tmr_tick().

Try running it, hitting the Demo button.

 

Start Visual Studio 2008.

Choose File->New Project->Visual Basic->WPF Application.

(This also works with Temporary projects)

Open Window1.xaml.vb. Paste in the code below, then hit F5 to run.

 

I had my wife and kids playing with it for quite a while!

 

Try adding a frame or MouseWheel while a cartoon is playing.

 

Try right click and then draw: it changes the way you draw.

 

Experiment with using a touchpad and a mouse for drawing.

 

Try using variable frame rates. Add a feature to save/restore the current cartoon.

 

Try drawing your name, or the letters of the alphabet.

 

My original had features like copy/paste from frames, color fill

 

See also:

My toys over the years

Why was the original IBM PC 4.77 Megahertz?

 

<Cartoon Code>

Class Window1

    Private WithEvents btnNewFrame As Button

    Private WithEvents btnErase As Button

    Private WithEvents btnPlay As Button

    Private WithEvents btnDemo As Button

    Private WithEvents btnReset As Button

    Private txtStatus As TextBlock

    Private _AnimControl As AnimControl

    Sub Load() Handles MyBase.Loaded

        Me.Width = 800

        Me.Height = 600

        Dim xaml = _

        <DockPanel

            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" LastChildFill="True">

            <StackPanel Background="Transparent" Orientation="Vertical" DockPanel.Dock="Top">

                <TextBlock>Draw to create lines for a cartoon frame. Add a new frame, hit play</TextBlock>

                <TextBlock>Rebirth of Calvin's cartoon program circa 1982

                    <Hyperlink>http://blogs.msdn.com/calvin_hsia</Hyperlink>

                </TextBlock>

            </StackPanel>

            <Border DockPanel.Dock="Bottom" Height="25">

                <StackPanel Orientation="Horizontal">

                    <Button Name="btnNewFrame" ToolTip="Add current drawing to cartoon, so you can create a new one">_New Frame</Button>

                    <Button Name="btnErase">_Erase</Button>

                    <Button Name="btnPlay" ToolTip="Animate the current frames or stop animation">_Play</Button>

                    <Button Name="btnDemo">_Demo</Button>

                    <Button Name="btnReset" ToolTip="Erase all frames">_Reset</Button>

                    <TextBox Name="txtBetween" Text="{Binding Path=txtBetween.text}"></TextBox>

                    <TextBlock Name="txtStatus"></TextBlock>

                </StackPanel>

            </Border>

            <UserControl Name="MyCtrl"/>

        </DockPanel>

 

        Dim dPanel = CType(System.Windows.Markup.XamlReader.Load(xaml.CreateReader), DockPanel)

        Dim MyCtrl = CType(dPanel.FindName("MyCtrl"), UserControl)

        _AnimControl = New AnimControl(Me)

        MyCtrl.Content = _AnimControl

        btnPlay = CType(dPanel.FindName("btnPlay"), Button)

        btnNewFrame = CType(dPanel.FindName("btnNewFrame"), Button)

        btnPlay = CType(dPanel.FindName("btnPlay"), Button)

        btnDemo = CType(dPanel.FindName("btnDemo"), Button)

        btnErase = CType(dPanel.FindName("btnErase"), Button)

        btnReset = CType(dPanel.FindName("btnReset"), Button)

        txtStatus = CType(dPanel.FindName("txtStatus"), TextBlock)

        Me.Content = dPanel

    End Sub

    Sub btnNewFrame_Click() Handles btnNewFrame.Click

        _AnimControl.NewFrame()

        RefreshStatus()

    End Sub

    Sub btnPlay_Click() Handles btnPlay.Click

        btnNewFrame_Click()   ' save any currently drawn changes first

        _AnimControl.Play()

    End Sub

    Sub btnDemo_Click() Handles btnDemo.Click

        _AnimControl.Demo()

    End Sub

    Sub btnErase_Click() Handles btnErase.Click

        _AnimControl.EraseBtn()

    End Sub

    Sub btnReset_Click() Handles btnReset.Click

        _AnimControl.Reset()

    End Sub

    Friend Sub RefreshStatus()

        Me.txtStatus.Text = String.Format("Frame count = {0} CurLineCnt = {1} CurFrame= {2} Between = {3}", _

                                          _AnimControl._UserFrameList.Count, _AnimControl._CurLineList.Count, _

                                          _AnimControl._ndxUserFrame, _AnimControl._nBetween)

    End Sub

End Class

 

Public Class AnimControl

    Inherits FrameworkElement

    Private WithEvents _timer As New System.Windows.Threading.DispatcherTimer

    Private _Window1 As Window1

    Friend _nBetween As Integer = 10 ' # of frames being calc'd between user frames

    Friend _ndxUserFrame As Integer   ' index into user created frames.

    Private _ndxBetween As Integer  ' from 0 to nBetween

    Friend _nBetweenDyn As Integer = 0 ' # to add to _nBetween for next animation: adjustable by mousewheel

    Private _ptCurrent As Point?

    Private _ptOld As Point?

    Private _fPenDown As Boolean

    Private _oPen = New Pen(Brushes.Black, 2)

    Private _PenModeDrag As Boolean = True ' Click to create line segs, or continuous drag to create multiple segs

    ' lines to draw for current image: could be while composing, or playing. Could be real frame or calc'd frame

    Friend _CurLineList As New List(Of cFrameLine)

 

    'Frames stored by user

    Friend _UserFrameList As New List(Of cCartoonFrame)   

    Sub New(ByVal w As Window1)

        _Window1 = w

    End Sub

    Sub Reset()

        Me._timer.IsEnabled = False 'stop playback, if any

        Me._UserFrameList.Clear() ' erase all user data

        Me._nBetweenDyn = 0

        EraseBtn()

    End Sub

    Sub EraseBtn() ' erase current frame

        _CurLineList.Clear()

        Me._ptOld = Nothing

        Me._fPenDown = False

        Me.InvalidateVisual()

    End Sub

    Sub Demo()

        Reset()

        Me._CurLineList.Add(New cFrameLine(New Point(10, 10), New Point(10, 300)))

        Me._CurLineList.Add(New cFrameLine(New Point(10, 300), New Point(300, 300)))

        Me._CurLineList.Add(New cFrameLine(New Point(300, 300), New Point(300, 10)))

        Me._CurLineList.Add(New cFrameLine(New Point(300, 10), New Point(10, 10)))

        Me._UserFrameList.Add(New cCartoonFrame(Me._CurLineList, 30))

        Me._CurLineList.Clear() ' reset for next frame

        Me._CurLineList.Add(New cFrameLine(New Point(10, 10), New Point(300, 10)))

        Me._CurLineList.Add(New cFrameLine(New Point(300, 10), New Point(300, 300)))

        Me._CurLineList.Add(New cFrameLine(New Point(300, 300), New Point(10, 300)))

        Me._CurLineList.Add(New cFrameLine(New Point(10, 300), New Point(10, 10)))

        Me._UserFrameList.Add(New cCartoonFrame(Me._CurLineList, 50))

        Me._CurLineList.Clear() ' reset for next frame

        Me._CurLineList.Add(New cFrameLine(New Point(10, 10), New Point(300, 10)))

        Me._UserFrameList.Add(New cCartoonFrame(Me._CurLineList, 10))

        Play()

    End Sub

    Sub NewFrame()

        If _CurLineList.Count > 0 Then

            Dim curFrame = New cCartoonFrame(_CurLineList, 10)

            _UserFrameList.Add(curFrame)

            EraseBtn()

        End If

    End Sub

 

    Friend Sub Play()

        If _UserFrameList.Count < 2 Then

            MsgBox("Need at least 2 frames to animate")

            Return

        End If

        If _timer.IsEnabled Then ' if we're already playing, stop

            _timer.IsEnabled = False

        Else

            _timer.Interval = New TimeSpan(0, 0, 0, 0, 50) ' days,hrs,mins,secs,msecs

            _timer.IsEnabled = True

        End If

        Me._fPenDown = False

        Me._ndxUserFrame = 0

        Me._ndxBetween = 1 ' 1st is drawn now, next by timer tick

        Me._CurLineList.Clear()

        Me._CurLineList.AddRange(Me._UserFrameList(0)._Lines) 'get the 1st frame

        Me.InvalidateVisual() ' show it

    End Sub

    Sub tmr_tick() Handles _timer.Tick ' let's do the animating

        If _ndxUserFrame = Me._UserFrameList.Count - 1 Then     ' we've reached the end: let's restart

            Me._ndxUserFrame = 0

            Me._ndxBetween = 0

        End If

        Me._CurLineList.Clear()

        Dim frmLeft = Me._UserFrameList(Me._ndxUserFrame) ' the frame on the left

        Dim frmRight = Me._UserFrameList(Me._ndxUserFrame + 1) ' the frame on the right

        _nBetween = Math.Max(0, frmLeft._nBetween + Me._nBetweenDyn) ' recorded value plus mousewheel adjustment

        Dim nLinesToDraw = Math.Max(frmLeft._Lines.Count, frmRight._Lines.Count) - 1

        For ndx = 0 To nLinesToDraw ' calc the lines to draw

            Dim lineLeft = frmLeft._Lines(Math.Min(ndx, frmLeft._Lines.Count - 1))

            Dim lineRight = frmRight._Lines(Math.Min(ndx, frmRight._Lines.Count - 1))

            Dim pt0 As New Point With _

                {.X = lineLeft.pt0.X + Me._ndxBetween * (lineRight.pt0.X - lineLeft.pt0.X) / (_nBetween + 1), _

                 .Y = lineLeft.pt0.Y + Me._ndxBetween * (lineRight.pt0.Y - lineLeft.pt0.Y) / (_nBetween + 1)}

            Dim pt1 As New Point With _

                {.X = lineLeft.pt1.X + Me._ndxBetween * (lineRight.pt1.X - lineLeft.pt1.X) / (_nBetween + 1), _

                 .Y = lineLeft.pt1.Y + Me._ndxBetween * (lineRight.pt1.Y - lineLeft.pt1.Y) / (_nBetween + 1)}

            Dim newLine = New cFrameLine(pt0, pt1)

            Me._CurLineList.Add(newLine)

        Next

        If Me._ndxBetween > Me._nBetween Then ' we've reached the right

            Me._ndxUserFrame += 1 ' advance to next user frame

            Me._ndxBetween = 0 ' we don't want to redraw frmRight when it becomes frmLeft

        End If

        Me._ndxBetween += 1 ' advance to next frame

        Me.InvalidateVisual()

    End Sub

    Protected Overrides Sub OnRender(ByVal drawingContext As System.Windows.Media.DrawingContext)

        drawingContext.DrawRectangle(Brushes.AliceBlue, New Pen(Brushes.Purple, 1), New Rect(0, 0, Me.RenderSize.Width, Me.RenderSize.Height))

        For Each fr In Me._CurLineList ' draw the lines in the current frame

            drawingContext.DrawLine(_oPen, fr.pt0, fr.pt1)

        Next

        If Me._fPenDown Then

            If Me._ptOld.HasValue Then

                drawingContext.DrawLine(_oPen, Me._ptOld, Me._ptCurrent)

            Else

                drawingContext.DrawLine(_oPen, Me._ptOld, Me._ptCurrent)

            End If

        End If

        _Window1.RefreshStatus()

    End Sub

    Protected Overrides Sub OnMouseDown(ByVal e As System.Windows.Input.MouseButtonEventArgs)

        If e.RightButton = MouseButtonState.Pressed Then

            Me._PenModeDrag = Not Me._PenModeDrag ' toggle modes on right click

        Else

            If Me._PenModeDrag Then

                Me._ptOld = e.GetPosition(Me)

            Else

                If e.RightButton = MouseButtonState.Pressed Then

                    Me._fPenDown = False

                    Me._ptOld = Nothing

                Else

                    Me._fPenDown = True

                    Me._ptCurrent = e.GetPosition(Me) ' get cur pos rel to self

                    If Not Me._ptOld.HasValue Then

                        Me._ptOld = Me._ptCurrent ' same

                    End If

                    Me.InvalidateVisual()

                End If

            End If

        End If

    End Sub

 

    Protected Overrides Sub OnMouseMove(ByVal e As System.Windows.Input.MouseEventArgs)

        If Me._PenModeDrag Then

            If e.LeftButton = MouseButtonState.Pressed Then

                If Me._ptOld.HasValue Then

                    Me._ptCurrent = e.GetPosition(Me)

                    Dim newFrameLine = New cFrameLine(Me._ptOld, Me._ptCurrent)

                    Me._CurLineList.Add(newFrameLine)

                    Me._ptOld = Me._ptCurrent

                    Me.InvalidateVisual()

                End If

            End If

        Else

            If Me._fPenDown Then

                If Me._ptOld.HasValue Then

                    Me._ptCurrent = e.GetPosition(Me)

                End If

                Me.InvalidateVisual()

            End If

        End If

    End Sub

    Protected Overrides Sub OnMouseUp(ByVal e As System.Windows.Input.MouseButtonEventArgs)

        If Me._fPenDown Then

            Me._ptCurrent = e.GetPosition(Me) ' get cur pos rel to self

            Dim newFrameLine = New cFrameLine(Me._ptOld, Me._ptCurrent)

            Me._CurLineList.Add(newFrameLine)

            Me._ptOld = Me._ptCurrent

            Me._fPenDown = False

            Me.InvalidateVisual()

        End If

    End Sub

    Protected Overrides Sub OnMouseWheel(ByVal e As System.Windows.Input.MouseWheelEventArgs)

        If Me._timer.IsEnabled Then ' only when playing

            Me._nBetweenDyn += If(e.Delta > 0, 2, -2)

        End If

    End Sub

    <DebuggerDisplay("{ToString()}")> _

    Public Class cCartoonFrame ' User created cartoon frame

        Public ReadOnly _Lines As New List(Of cFrameLine) ' a frame is a list of lines

        ' # of frames to gen between real user frames

        Public ReadOnly _nBetween As Integer

        Sub New(ByVal lst As List(Of cFrameLine), ByVal nBetween As Integer)

            _Lines.AddRange(lst)

            _nBetween = nBetween

        End Sub

        Public Overrides Function ToString() As String

            Return String.Format("LineCount = {0}", _Lines.Count)

        End Function

    End Class

    'A line to be animated. Could belong to a real or gen'd user frame, or while user is actively drawing

    <DebuggerDisplay("{ToString()}")> _

    Public Class cFrameLine ' User created cartoon line. It's just 2 points.

        Friend ReadOnly pt0 As Point

        Friend ReadOnly pt1 As Point

        Sub New(ByVal pt0 As Point, ByVal pt1 As Point)

            Me.pt0 = pt0

            Me.pt1 = pt1

        End Sub

        Public Overrides Function ToString() As String

            Return String.Format("Line = ({0}) - ({1})", pt0.ToString, pt1.ToString)

        End Function

    End Class

End Class

 

</Cartoon Code>

 

Posted by Calvin_Hsia | 15 Comments

Overload Operator new to detect memory leaks

There are various leak detection methods for memory allocators. A popular one is to tag each allocation with some information about the caller. When there’s a memory leak, you just need to look at that tag info to find the line of code that allocated the memory.

 

However, this requires that the caller pass in the parameters, such as file and line number, for each allocation. (Perhaps we want to do this only on debug builds.) Also, there could be thousands of callers: we want to avoid changing all the call sites.

 

The __FILE__ and __LINE__  predefined macros and default parameters can help eliminate the need to modify thousands of lines of source code. They  are the file and line number of the currently compiled line. Then we can store the file/line with every allocation.

 

We can create an allocation method with 2 default parameters, and use these predefined macros as the defaults:

 

void * MyAlloc(size_t cbSize, char *szFile = __FILE__, UINT nLineNo = __LINE__)

 

Now a caller to MyAlloc will automatically pass in the file and line number parameters with no change to the original source.

    void *ptemp = MyAlloc(100);// let's allocate 100 bytes (Default params are __FILE__ and __LINE__

 

Although this works with direct calls to MyAlloc, many allocations are via the “new” operator. We can override the default module implementation with our own:

// the single override of the module's new operator:

void * _cdecl operator new (size_t cbSize)

    void *p = MyAlloc(cbSize, __FILE__, __LINE__);  // this line will show for all New operator calls<sigh>

    return p;

}

Using this technique, every “new” allocation will point to this same file/line of code. Not very useful: we want to know which line of code called “new”, not where the “new” operator override is.

 

But how do we pass in default parameters (so we don’t have to modify thousands of lines of source)?

 

If we try this:

void * _cdecl operator new (size_t cbSize, char *szFile = __FILE__, UINT nLineNo = __LINE__)

 

 

we get a linker error:

 

1>d:\dev\vc\overnew\overnew.cpp(99) : error C2668: 'operator new' : ambiguous call to overloaded function

1>        d:\dev\vc\overnew\overnew.cpp(45): could be 'void *operator new(size_t,char *,UINT)'

1>        d:\dev\vc\overnew\predefined c++ types (compiler internal)(23): or       'void *operator new(size_t)'

1>        while trying to match the argument list '(unsigned int)'

 

That makes sense: the linker has no idea which operator new to call because there’s only 1 parameter: the size,  and both implementations match operator new with 1 integer parameter.

 

We also have problems when trying to link in COM, ATL or STD Lib code which also call “new”

 

One way to avoid this is to use an overload (not an override). What’s the difference?

 

An override replaces existing functionality, whereas the overload adds functionality, and the called code is determined by it’s signature.

 

If we add a parameter to operator New (like a simple integer), then we have overloaded it. If we then modify every caller to pass in the parameter, then we can use the predefined macros and default parameter trick:

 

void * _cdecl operator new (size_t cbSize, int nAnyIntParam, char *szFile = __FILE__, UINT nLineNo = __LINE__)

 

The big drawback is we have to modify all the call sites.

 

If the call sites already have a parameter and are thus calling the overload, then no big deal: this is what the FoxPro code base does: each memory handle is marked with the kind of memory  (Data Engine, Property Sheet, Form Designer, UserCode, etc). It was simple to add the File/LineNo default parameters to debug builds.

 

However, this still doesn’t work with COM, ATL, STD code. IMalloc,  IMallocSpy can be used for mapping COM calls through our code.

 

A solution is to recall that the main task at hand is to find which line of code allocated a leaking block: instead of recording __LINE__, which could be the same for all new calls, let’s use the same recording mechanism to record the caller’s address (they’re both just 32 bit DWORDS.)

 

// the single override of the module's new operator:

void * _cdecl operator new (size_t cbSize)

{

    UINT *EbpRegister ; // the Base Pointer: the stack frame base

    _asm { mov EbpRegister, ebp};

    UINT CallerAddr = *(((size_t *)EbpRegister)+1) ; // the return addr

 

    void *p = MyAlloc(cbSize, __FILE__, CallerAddr); 

    return p;

}

 

 

Below is a sample that you can build and run using Visual Studio. (I suspect all versions will work.)

 

It demonstrates allocating and freeing memory from direct calls, from operator new, and from overloaded operator new.

 

 

Start VS, choose File->New->Project->C++->Win32->Console Application. Call it OverNew

 

In the wizard choose add common header files for ATL, click Finish.

 

Paste in the Sample1 code below:

 

 

Now hit F5 to run the code.

 

If you uncomment any of the Delete lines, the program will leak: the memory is never released.

There is no warning or error that the memory leaks. In this simple scenario, the leak doesn’t matter because the process just exits. However, in more complex scenarios, the leak can be crippling.

 

 

Now when you run the code, you get output like this:

 

 

 

 

 

MyAlloc 1056a8 Size = 100 d:\dev\vc\overnew\overnew.cpp(23)

MyAlloc 105850 Size = 4 d:\dev\vc\overnew\overnew.cpp(61)

Op new 105958

Constructor 105958

MyAlloc 105998 Size = 4 d:\dev\vc\overnew\overnew.cpp(61)

Op new 105aa0

Constructor 105aa0

MyAlloc 105ae0 Size = 4 d:\dev\vc\overnew\overnew.cpp(69)

Op new overload 105be8

Constructor 105be8

Destructor 105958

Op del 105958

MyDelete 105958 d:\dev\vc\overnew\overnew.cpp(61)

Destructor 105aa0

Op del 105aa0

MyDelete 105aa0 d:\dev\vc\overnew\overnew.cpp(61)

Op del 1057b0

MyDelete 1057b0 d:\dev\vc\overnew\overnew.cpp(23)

Destructor 105be8

Op del 105be8

MyDelete 105be8 d:\dev\vc\overnew\overnew.cpp(69)

 

 

 

 

Note: this sample doesn’t overload the operator new []() version which allocates arrays. That’s for you to do.

void* _cdecl operator new[](size_t cbSize)

 

See also:

Examine .Net Memory Leaks

Very Advanced Debugging tips

 

 

 

 

<Sample1>

// OverNew.cpp : Defines the entry point for the console application.

//

 

#include "stdafx.h"

 

// version of OutputDebugString that allows variable # args

void OutputDebugStringf(char *szFmt, ...)

{

    va_list marker;

    va_start(marker, szFmt);  // the varargs start at szFmt

    char szBuf[1024];

    _vsnprintf_s(szBuf, sizeof(szBuf),szFmt, marker);

    OutputDebugStringA("\n");

    OutputDebugStringA(szBuf);

}

 

struct AllocHeader  // we'll put a header at the beginning of each alloc

{

    char szFile[MAX_PATH];

    UINT nLineNo;

};

 

void * MyAlloc(size_t cbSize, char *szFile = __FILE__, UINT nLineNo = __LINE__)

{

    // We allocate a header followed by the desired allocation

    void *p = malloc(sizeof(AllocHeader) + cbSize );

    AllocHeader *pHeader = (AllocHeader *)p;

    strcpy_s(pHeader->szFile, szFile);

    pHeader->nLineNo = nLineNo;

    OutputDebugStringf("MyAlloc %x Size = %d %s(%d)", p, cbSize, szFile, nLineNo);

    // we return the address + sizeof(AllocHeader)

    return (void *)( (size_t)p+sizeof(AllocHeader));

}

void MyDelete(void *p)

{

    // we need to free our allocator too

    AllocHeader *pHeader = (AllocHeader *)((size_t)p - sizeof(AllocHeader));

    OutputDebugStringf("MyDelete %x %s(%d)", p, pHeader->szFile, pHeader->nLineNo);

    free((void *)((size_t)p - sizeof(AllocHeader)));

}

 

 

// the single override of the module's new operator:

void * _cdecl operator new (size_t cbSize)

//void * _cdecl operator new (size_t cbSize, char *szFile = __FILE__, UINT nLineNo = __LINE__)

{

//#define USEWORKAROUND 1 // uncomment this to get the caller addr

#if USEWORKAROUND

    UINT *EbpRegister ; // the Base Pointer: the stack frame base

    _asm { mov EbpRegister, ebp};

    UINT CallerAddr = *(((size_t *)EbpRegister)+1) ; // the return addr

// if you get a leak, you'll get something like:

//          d:\dev\vc\overnew\overnew.cpp(10189270)

// Break into the debugger. Take the # in parens, put it in Watch window: turn on hex display->it shows addr of caller

// Go to disassembly, put the address in the Address bar hit enter. Bingo: you're at the caller that didn't free!

 

    //    CallerAddr -=  (size_t)g_hinstDll;    // you can get a relative address if you like: look at the link map

    void *p = MyAlloc(cbSize, __FILE__, CallerAddr); 

    OutputDebugStringf("Op new %x CallerAddr = %x", p, CallerAddr);

#else

    void *p = MyAlloc(cbSize, __FILE__, __LINE__);  // this line will show for all New operator calls<sigh>

    OutputDebugStringf("Op new %x", p);

#endif

 

    return p;

}

 

// an overload of the new operator, with an int param

void * _cdecl operator new (size_t cbSize, int nAnyIntParam, char *szFile = __FILE__, UINT nLineNo = __LINE__)

{

    void *p = MyAlloc(cbSize, szFile, nLineNo);  // this line will show for all New operator calls<sigh>

    OutputDebugStringf("Op new overload %x", p);

    return p;

}

 

 

void  _cdecl operator delete(void *p)

{

    OutputDebugStringf("Op del %x", p);

    MyDelete(p);

}

 

class TestClass

{

public:

    int mem1;

    TestClass() { // Constructur

        OutputDebugStringf("Constructor %x", this);

    }

    ~TestClass() {      // destructor

        OutputDebugStringf("Destructor %x", this);

    }

};

 

int _tmain(int argc, _TCHAR* argv[])

{

 

    void *ptemp = MyAlloc(100);// let's allocate 100 bytes (Default params are __FILE__ and __LINE__

    TestClass *aTestClass =  new TestClass();   //allocate space for an instance

 

    TestClass *aTestClass2 =  new TestClass();  //allocate space for another instance

 

    TestClass *aTestClass3 =  new (1) TestClass();    //allocate space for another instance, using custom New

 

    delete aTestClass;  // comment out this line for mem leak

 

    delete aTestClass2; // comment out this line for mem leak

 

    delete ptemp; // comment out this line for mem leak

 

    delete aTestClass3; // comment out this line for mem leak

 

    return 0;

}

 

 

</Sample1>

 

 

 

Posted by Calvin_Hsia | 5 Comments
Filed under: , ,

Write simple Debug helpers to help you debug and maintain your code

Much of my time is spent using the Visual Studio debugger examining code to figure out how it works and how to fix it. When stepping through a function, the values the function uses are very useful for code understanding. The debugger shows these values in the Watch/Locals/Auto/Callstack windows.  For example, the Locals window shows the Local variables used by the current code while at a breakpoint. Each variable might be a structure or class with many members. You can click to expand to examine sub-members of a value.

 

I also like to modify code while the debug session is live, so I can see real values while writing. Edit and Continue is a very useful feature, but it doesn’t work in every case, such as creating new methods or types. Also, native code doesn’t support EnC. So I like using an external editor (perhaps another instance of VS) to modify the code. This means that your debug session is still live, and the source code file still matches the binary, until you save the file in the external editor.

 

The string shown for a variable can be customized to help understand what the code is doing. As you step through the code, if you can understand at a glance what the function is doing, rather than drilling into sub-members, you’re more efficient. Some types have dozens of variables, and some might be more important than others.

 

For native code (C++, C) you can customize the display string with this: Customize the VS debugger display of your data

 

For managed code (VB, C#) you can override the ToString method to customize the string. In VS 2008, the debugger will display the results of calling the ToString method.

 

There are 2 options:

Tools->Options->Debugging->

Enable Property Evaluation and other implicit function calls

Call string conversion function on object in variables windows (C# and javascript only)

 

The next version of Visual Studio will add VB to this feature!

 

As a simple workaround, to have the debugger call ToString in VB programs, you need only prefix your types with

<DebuggerDisplay("{tostring}")> _

 

The line continuation character won’t be needed in the next version either!

 

If you don’t have the source code or can’t modify it, you can still modify the displayed string: Customize the display of types in the Debugger using Extension Methods and DebuggerDisplay Attribute

 

Below are a VB and C# program demonstrating how the string can be customized.