Managed code using unmanaged memory: HeapCreate, Peek and Poke

In the old days of Basic (starting over 4 decades ago), there were functions called Peek and Poke that would allow you to read and write memory directly. These were incredibly powerful commands: you could, for example, read and write directly to the hardware, like the video display, the tape cassette recorder, or the speaker.

 

More modern versions of the language dropped Peek/Poke. However, you can still read and write memory, even from managed code, and this ability is still extremely powerful. You can’t write directly to the speaker, but you can do other fun things: see What is your computer doing with all that memory? Write your own memory browser and Use Named Pipes and Shared Memory for inter process communication with a child process or two

 

The word “Managed” when applied to languages means that the memory that the program uses is automatically managed for the program: you can just use memory without paying attention to freeing it (usually). there is an automatic unused memory (“garbage”) collector.

(see Examine .Net Memory Leaks).

 

In this sample we’ll create our own heap into which we’ll write and then read some memory. Keep in mind that the memory could come from anywhere, not just our own heap. We define a TestData structure that has only 2 integer data members. Strings are more complicated because of variable length, encoding, allocation issues.

(see HeapCreate and Marshal.PtrToStructure)

 

Start Visual Studio 2010. (you can use older versions, but you’ll have to remove some of the new features I’ve used in the code)

File->New Project->(VB or C#) Windows ->WPF Application.

 

Double click on the form designer to get the code behind file. Replace with the respective version from below.

 

Make sure you have Tools->Options->Debugger->Just My Code unchecked.

 

Put a breakpoint (F9) on the first line and single step through. Observe the values change as each line is executed.

 

If you choose Debug->Windows->Memory1, you can try to see the memory by putting the Address ptr ( like 0x09C307D0) in the address window to see the memory. (In C#, you can drag/drop the address from the Locals window to the Memory window.) Right click on the memory window and choose to display the memory as 4 byte integers.

 

Unfortunately, when the current stack frame is VB code, the debugger “thinks” that you don’t care about memory, so it won’t evaluate the memory window. You can still see the memory by double clicking a native or C# stack frame on the stack. It’ll ask you for the source code for that frame, but you can just hit escape. Presto, the allocated memory is now available for inspection or even changing via the debugger.

 

If you’re running on a 64 bit OS, try running the code in 32/64 bit: Project->properties->Compile->Advanced->Target CPU (x86 or x64).

<VB Code>

Imports System.Runtime.InteropServices

Class MainWindow

    Private _hpHandle As IntPtr

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

        Dim wordsize = IntPtr.Size

        If wordsize <> 4 Then

            'are we in 32 or 64 bit land? doesn't matter!

        End If

        Dim datSize = Marshal.SizeOf(GetType(TestData))

        _hpHandle = Heap.HeapCreate(0, 0, 0)

        Dim ptr = Heap.HeapAlloc(_hpHandle, 0, datSize)

        Dim dataOrig = New TestData With {.data1 = 1, .data2 = 2}

        Marshal.StructureToPtr(dataOrig, ptr, fDeleteOld:=True)

        Dim tryd1 = Marshal.ReadInt32(ptr)

      Dim tryd2 = Marshal.ReadInt32(ptr + 4)

        'change the memory window contents in the debugger: repeat this stmt by using Set Next Statement

        Dim dataCopy = Marshal.PtrToStructure(ptr, GetType(TestData))

        Heap.HeapFree(_hpHandle, 0, ptr)

    End Sub

    Sub on_close() Handles MyBase.Closed

        Heap.HeapDestroy(_hpHandle)

    End Sub

    <StructLayout(LayoutKind.Sequential)>

    Structure TestData

        Dim data1 As Integer

        Dim data2 As Integer

        Public Overrides Function ToString() As String

            Return data1.ToString + " " + data2.ToString

        End Function

    End Structure

End Class

Public Class Heap

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

    Public Shared Function HeapCreate(

               ByVal flOptions As UInteger,

               ByVal dwInitialSize As UIntPtr,

               ByVal dwMaximumSize As UIntPtr

         ) As IntPtr

    End Function

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

    Public Shared Function HeapAlloc(

               ByVal hHeap As IntPtr,

               ByVal dwFlags As UInteger,

               ByVal dwSize As UIntPtr

         ) As IntPtr

    End Function

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

    Public Shared Function HeapFree(

               ByVal hHeap As IntPtr,

   ByVal dwFlags As UInteger,

               ByVal lpMem As IntPtr

         ) As Boolean

    End Function

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

    Public Shared Function HeapDestroy(

               ByVal hHeap As IntPtr

         ) As Boolean

    End Function

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.Runtime.InteropServices;

namespace Heapcs

{

    /// <summary>

    /// Interaction logic for MainWindow.xaml

    /// </summary>

    public partial class MainWindow : Window

    {

        public MainWindow()

        {

            InitializeComponent();

        }

        private IntPtr _hpHandle;

        private void Window_Loaded(object sender, RoutedEventArgs e)

        {

            var wordsize = IntPtr.Size;

            if (wordsize != 4)

            {

                //are we in 32 or 64 bit land? doesn't matter!

            }

            var datSize = Marshal.SizeOf(typeof(TestData));

            _hpHandle = Heap.HeapCreate(0, UIntPtr.Zero, UIntPtr.Zero);

            var ptr = Heap.HeapAlloc(_hpHandle, 0, (UIntPtr)datSize);

            var dataOrig = new TestData { data1 = 1, data2 = 2 };

            Marshal.StructureToPtr(dataOrig, ptr, true);

            var tryd1 = Marshal.ReadInt32(ptr);

            var tryd2 = Marshal.ReadInt32(ptr + 4);

            //change the memory window contents in the debugger: repeat this stmt by using Set Next Statement

            var dataCopy = Marshal.PtrToStructure(ptr, typeof(TestData));

            Heap.HeapFree(_hpHandle, 0, ptr);

            this.Closed +=new EventHandler(on_close);

        }

        private void on_close(object sender, EventArgs e)

        {

            Heap.HeapDestroy(_hpHandle);

        }

    }

    struct TestData

    {

        public int data1;

        public int data2;

        public override string ToString()

        {

            return data1.ToString() + " " + data2.ToString();

        }

    }

    public class Heap

    {

        [DllImport("kernel32.dll", SetLastError = true)]

        public static extern IntPtr HeapCreate(uint flOptions, UIntPtr dwInitialsize, UIntPtr dwMaximumSize);

        [DllImport("kernel32.dll", SetLastError = true)]

        public static extern IntPtr HeapAlloc(IntPtr hHeap, uint dwFlags, UIntPtr dwSize);

        [DllImport("kernel32.dll", SetLastError = true)]

        public static extern bool HeapFree(IntPtr hHeap, uint dwFlags, IntPtr lpMem);

        [DllImport("kernel32.dll", SetLastError = true)]

        public static extern bool HeapDestroy(IntPtr hHeap);

    }

}

</C# Code>