using System; using System.Runtime.InteropServices; using System.Windows.Forms; namespace PaintDotNet.SystemLayer { /// /// Contains methods for allocating, freeing, and performing operations on memory /// that is fixed (pinned) in memory. /// [CLSCompliant(false)] public unsafe static class Memory { private static IntPtr hHeap; static Memory() { hHeap = SafeNativeMethods.HeapCreate(0, IntPtr.Zero, IntPtr.Zero); uint info = 2; try { // Enable the low-fragmentation heap (LFH) SafeNativeMethods.HeapSetInformation(hHeap, NativeConstants.HeapCompatibilityInformation, (void*)&info, sizeof(uint)); } catch (Exception) { // If that method isn't available, like on Win2K, don't worry about it. } Application.ApplicationExit += new EventHandler(Application_ApplicationExit); } /// /// Gets the total amount of physical memory (RAM) in the system. /// public static ulong TotalPhysicalBytes { get { NativeStructs.MEMORYSTATUSEX mse = new NativeStructs.MEMORYSTATUSEX(); mse.dwLength = (uint)sizeof(NativeStructs.MEMORYSTATUSEX); bool result = NativeMethods.GlobalMemoryStatusEx(ref mse); if (!result) { NativeMethods.ThrowOnWin32Error("GlobalMemoryStatusEx"); } return mse.ullTotalPhys; } } private static void DestroyHeap() { IntPtr hHeap2 = hHeap; hHeap = IntPtr.Zero; SafeNativeMethods.HeapDestroy(hHeap2); } private static void Application_ApplicationExit(object sender, EventArgs e) { DestroyHeap(); } /// /// Allocates a block of memory at least as large as the amount requested. /// /// The number of bytes you want to allocate. /// A pointer to a block of memory at least as large as bytes. /// Thrown if the memory manager could not fulfill the request for a memory block at least as large as bytes. public static IntPtr Allocate(ulong bytes) { if (hHeap == IntPtr.Zero) { throw new InvalidOperationException("heap has already been destroyed"); } else { IntPtr block = SafeNativeMethods.HeapAlloc(hHeap, 0, new UIntPtr(bytes)); if (block == IntPtr.Zero) { throw new OutOfMemoryException("HeapAlloc returned a null pointer"); } if (bytes > 0) { GC.AddMemoryPressure((long)bytes); } return block; } } /// /// Allocates a block of memory at least as large as the amount requested. /// /// The number of bytes you want to allocate. /// A pointer to a block of memory at least as large as bytes /// /// This method uses an alternate method for allocating memory (VirtualAlloc in Windows). The allocation /// granularity is the page size of the system (usually 4K). Blocks allocated with this method may also /// be protected using the ProtectBlock method. /// public static IntPtr AllocateLarge(ulong bytes) { IntPtr block = SafeNativeMethods.VirtualAlloc(IntPtr.Zero, new UIntPtr(bytes), NativeConstants.MEM_COMMIT, NativeConstants.PAGE_READWRITE); if (block == IntPtr.Zero) { throw new OutOfMemoryException("VirtualAlloc returned a null pointer"); } if (bytes > 0) { GC.AddMemoryPressure((long)bytes); } return block; } /// /// Allocates a bitmap of the given height and width. Pixel data may be read/written directly, /// and it may be drawn to the screen using PdnGraphics.DrawBitmap(). /// /// The width of the bitmap to allocate. /// The height of the bitmap to allocate. /// Receives a handle to the bitmap. /// A pointer to the bitmap's pixel data. /// /// The following invariants may be useful for implementors: /// * The bitmap is always 32-bits per pixel, BGRA. /// * Stride for the bitmap is always width * 4. /// * The upper-left pixel of the bitmap (0,0) is located at the first memory location pointed to by the returned pointer. /// * The bitmap is top-down ("memory correct" ordering). /// * The 'handle' may be any type of data you want, but must be unique for the lifetime of the bitmap, and must not be IntPtr.Zero. /// * The handle's value must be understanded by PdnGraphics.DrawBitmap(). /// * The bitmap is always modified by directly reading and writing to the memory pointed to by the return value. /// * PdnGraphics.DrawBitmap() must always render from this memory location (i.e. it must treat the memory as 'volatile') /// public static IntPtr AllocateBitmap(int width, int height, out IntPtr handle) { NativeStructs.BITMAPINFO bmi = new NativeStructs.BITMAPINFO(); bmi.bmiHeader.biSize = (uint)sizeof(NativeStructs.BITMAPINFOHEADER); bmi.bmiHeader.biWidth = width; bmi.bmiHeader.biHeight = -height; bmi.bmiHeader.biPlanes = 1; bmi.bmiHeader.biBitCount = 32; bmi.bmiHeader.biCompression = NativeConstants.BI_RGB; bmi.bmiHeader.biSizeImage = 0; bmi.bmiHeader.biXPelsPerMeter = 96; bmi.bmiHeader.biYPelsPerMeter = 96; bmi.bmiHeader.biClrUsed = 0; bmi.bmiHeader.biClrImportant = 0; IntPtr pvBits; IntPtr hBitmap = SafeNativeMethods.CreateDIBSection( IntPtr.Zero, ref bmi, NativeConstants.DIB_RGB_COLORS, out pvBits, IntPtr.Zero, 0); if (hBitmap == IntPtr.Zero) { throw new OutOfMemoryException("CreateDIBSection returned NULL (" + Marshal.GetLastWin32Error().ToString() + ") while attempting to allocate " + width + "x" + height + " bitmap"); } handle = hBitmap; long bytes = (long)width * (long)height * 4; if (bytes > 0) { GC.AddMemoryPressure(bytes); } return pvBits; } /// /// Frees a bitmap previously allocated with AllocateBitmap. /// /// The handle that was returned from a previous call to AllocateBitmap. /// The width of the bitmap, as specified in the original call to AllocateBitmap. /// The height of the bitmap, as specified in the original call to AllocateBitmap. public static void FreeBitmap(IntPtr handle, long width, long height) { long bytes = (long)width * (long)height * 4; bool bResult = SafeNativeMethods.DeleteObject(handle); if (!bResult) { NativeMethods.ThrowOnWin32Error("DeleteObject returned false"); } if (bytes > 0) { GC.RemoveMemoryPressure(bytes); } } /// /// Frees a block of memory previously allocated with Allocate(). /// /// The block to free. /// There was an error freeing the block. public static void Free(IntPtr block) { if (Memory.hHeap != IntPtr.Zero) { long bytes = (long)SafeNativeMethods.HeapSize(hHeap, 0, block); bool result = SafeNativeMethods.HeapFree(hHeap, 0, block); if (!result) { int error = System.Runtime.InteropServices.Marshal.GetLastWin32Error(); throw new InvalidOperationException("HeapFree returned an error: " + error.ToString()); } if (bytes > 0) { GC.RemoveMemoryPressure(bytes); } } else { #if REPORTLEAKS throw new InvalidOperationException("memory leak! check the debug output for more info, and http://blogs.msdn.com/ricom/archive/2004/12/10/279612.aspx to track it down"); #endif } } /// /// Frees a block of memory previous allocated with AllocateLarge(). /// /// The block to free. /// The size of the block. public static void FreeLarge(IntPtr block, ulong bytes) { bool result = SafeNativeMethods.VirtualFree(block, UIntPtr.Zero, NativeConstants.MEM_RELEASE); if (!result) { //int error = System.Runtime.InteropServices.Marshal.GetLastWin32Error(); //throw new InvalidOperationException("VirtualFree returned an error: " + error.ToString()); } if (bytes > 0) { GC.RemoveMemoryPressure((long)bytes); } } /// /// Sets protection on a block previously allocated with AllocateLarge. /// /// The starting memory address to set protection for. /// The size of the block. /// Whether to allow read access. /// Whether to allow write access. /// /// You may not specify false for read access without also specifying false for write access. /// Note to implementors: This method is not guaranteed to actually set read/write-ability /// on a block of memory, and may instead be implemented as a no-op after parameter validation. /// public static void ProtectBlockLarge(IntPtr block, ulong size, bool readAccess, bool writeAccess) { uint flOldProtect; uint flNewProtect; if (readAccess && writeAccess) { flNewProtect = NativeConstants.PAGE_READWRITE; } else if (readAccess && !writeAccess) { flNewProtect = NativeConstants.PAGE_READONLY; } else if (!readAccess && !writeAccess) { flNewProtect = NativeConstants.PAGE_NOACCESS; } else { throw new InvalidOperationException("May not specify a page to be write-only"); } #if DEBUGSPEW Tracing.Ping("ProtectBlockLarge: block #" + block.ToString() + ", read: " + readAccess + ", write: " + writeAccess); #endif SafeNativeMethods.VirtualProtect(block, new UIntPtr(size), flNewProtect, out flOldProtect); } /// /// Copies bytes from one area of memory to another. Since this function only /// takes pointers, it can not do any bounds checking. /// /// The starting address of where to copy bytes to. /// The starting address of where to copy bytes from. /// The number of bytes to copy public static void Copy(IntPtr dst, IntPtr src, ulong length) { Copy(dst.ToPointer(), src.ToPointer(), length); } /// /// Copies bytes from one area of memory to another. Since this function only /// takes pointers, it can not do any bounds checking. /// /// The starting address of where to copy bytes to. /// The starting address of where to copy bytes from. /// The number of bytes to copy public static void Copy(void* dst, void* src, ulong length) { SafeNativeMethods.memcpy(dst, src, new UIntPtr(length)); } public static void SetToZero(IntPtr dst, ulong length) { SetToZero(dst.ToPointer(), length); } public static void SetToZero(void* dst, ulong length) { SafeNativeMethods.memset(dst, 0, new UIntPtr(length)); } } }