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>