はなちるのマイノート

Unityをメインとした技術ブログ。自分らしくまったりやっていきたいと思いますー!

【C#】Marshal.AllocHGlobal・NativeMemory.Alloc・NativeMemory.AllocZeroedを利用してアンマネージドメモリを確保する方法

はじめに

今回はアンマネージドメモリを確保・破棄する方法について以下の3つを紹介したいと思います。

  • Marshal.AllocHGlobal
  • NativeMemory.Alloc
  • NativeMemory.AllocZeroed

概要

NativeMemory.NET 6から登場したクラスです。Marshal.AllocHGlobalと内部的には同じ動作になっているそうです。

.NET 6からNativeMemoryというクラスが新たに追加されました。その名の通り、ネイティブメモリを扱いやすくするものです。今までもMarshal.AllocHGlobalといったメソッド経由でネイティブメモリを確保することは可能であったので、何が違うのか、というと、何も違いません。実際NativeMemoryArrayの .NET 6以前版はMarshalを使ってますし。そして .NET 6 では Marshal.AllocHGlobal は NativeMemory.Alloc を呼ぶので、完全に同一です。

neue cc - NativeMemoryArray - .NET 6 APIをフル活用した2GB超えの巨大データを扱うライブラリ

Marshal.AllocHGlobal

public static IntPtr AllocHGlobal (int cb);
public static IntPtr AllocHGlobal (IntPtr cb);

プロセスのアンマネージ メモリからメモリを割り当てます。

指定したバイト数(バイト数へのポインター)を使用して、プロセスのアンマネージ メモリからメモリを割り当てます。

learn.microsoft.com

返り値として新しく割り当てられたメモリへのポインターが返ってきます。

またGCの管理下にないので、後述のFreeHGlobal(IntPtr)メソッドを利用してメモリを解放してあげる必要があります。

Marshal.FreeHGlobal

public static void FreeHGlobal (IntPtr hglobal);

以前にプロセスのアンマネージ メモリから割り当てられたメモリを解放します。

learn.microsoft.com

NativeMemory.Alloc

public static void* Alloc (UIntPtr byteCount);
public static void* Alloc (UIntPtr elementCount, UIntPtr elementSize);

指定したサイズのメモリ ブロックをバイト単位(要素単位)で割り当てます。

learn.microsoft.com

返り値として割り当てられたメモリブロックへのポインターが返ってきます。

またこちらもGC管理下にないので、NativeMemory.Freeを用いて解放してあげる必要があります。

NativeMemory.AllocZeroed

public static void* AllocZeroed (nuint byteCount);
public static void* AllocZeroed (nuint elementCount, nuint elementSize);

指定したサイズのメモリ ブロックをバイト単位(要素単位)で割り当ててゼロにします。

learn.microsoft.com

NativeMemory.Allocとの違いは、確保したメモリを0で初期化するところです。

NativeMemory.Free

public static void Free (void* ptr);

メモリブロックを解放します。

learn.microsoft.com

やり方

public static void Main(string[] args)
{
    // UnManagedなHeap Memory確保(1)
    // Marshal.AllocHGlobal + Marshal.FreeHGlobalパターン
    // NONE: ゼロ初期化されている保証はない
    unsafe
    {
        // 確保
        IntPtr p = Marshal.AllocHGlobal(sizeof(int) * 10);
            
        // 利用しやすいようにSpanに変換
        Span<int> nativeArray = new Span<int>((int*)p, 10);

        nativeArray[0] = 100;
        // 100,0,0,0,0,0,0,0,0,0
        Console.WriteLine(string.Join(",", nativeArray.ToArray()));

        // 解放
        Marshal.FreeHGlobal(p);
    }
        
    // UnManagedなHeap Memory確保(2)
    // NativeMemory.Alloc + NativeMemory.Freeパターン
    // NONE: ゼロ初期化されている保証はない
    unsafe
    {
        // 確保
        void* p = NativeMemory.Alloc(sizeof(int) * 10);
            
        // 利用しやすいようにSpanに変換
        Span<int> nativeArray = new Span<int>((int*)p, 10);
            
        nativeArray[0] = 100;
        // 100,0,0,0,0,0,0,0,0,0
        Console.WriteLine(string.Join(",", nativeArray.ToArray()));

        // 解放
        NativeMemory.Free(p);
    }
        
    // UnManagedなHeap Memory確保(3)
    // NativeMemory.AllocZeroed + NativeMemory.Freeパターン
    // NOTE: セロ初期化されている
    unsafe
    {
        // 確保
        void* p = NativeMemory.AllocZeroed(sizeof(int) * 10);
            
        // 利用しやすいようにSpanに変換
        Span<int> nativeArray = new Span<int>((int*)p, 10);
            
        nativeArray[0] = 100;
        // 100,0,0,0,0,0,0,0,0,0
        Console.WriteLine(string.Join(",", nativeArray.ToArray()));
            
        // 解放
        NativeMemory.Free(p);
    }
}