LoginSignup
37
41

More than 3 years have passed since last update.

C# unsafeポインタ TIPS 17連発

Posted at

初めに

本記事は @gushwellさんの C#リフレクションTIPS 55連発 に触発されて書きました。

C#リフレクションTIPS 55連発 は、薄っすらとしか覚えていないリフレクションが網羅的にまとめられており、その便利さのおかげで 余計に リフレクションを覚えられなくなってしまった良記事です:thumbsup_tone1:

私は業務で画像を扱うことが多く、P/Invoke でメモリを受け渡したり、ポインタ越しにメモリを操作するのですが、その度に過去のコードを探ったり、ぐぐったりしています。

ポインタに関わらず大体のことは ぐぐれば出てくるのですが、手間なので、未来の自分のため unsafeポインタ についてまとめました。

前提

これから示すコード(のほとんど)は、unsafe コンパイラオプションの有効化 と 下記usingディレクティブ が前提となっています。

動作は .NET Core 3.1 で確認しています。

using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

型変換

System.IntPtr と void* は相互に変換できます。

1. Convert IntPtr -> Pointer

unsafe {
    // IntPtr intPtr
    void* pointer = intPtr.ToPointer();
}

2. Convert Pointer -> IntPtr

unsafe {
    // void* pointer
    IntPtr intPtr0 = new IntPtr(pointer);
    IntPtr intPtr1 = (IntPtr)pointer;   // どちらも同じ
}

3. Convert StackData -> Pointer

どちらでも結果は同じなので、お好みで使えば良いと思います。

byte b;
unsafe {
    byte* pointer = &b;
}
byte b = 0x00;
unsafe {
    void* pointer = Unsafe.AsPointer<byte>(ref b);
}

4. Convert Pointer -> ref T

unsafe {
    // void* pointer
    ref byte b = ref Unsafe.AsRef<byte>(pointer);
}

5. Convert Array -> Pointer

マネージドオブジェクトはヒープ領域で管理されているので、GCの再配置を防ぐため fixed を使う必要があります。

byte[] array = new byte[size];
unsafe {
    fixed (byte* ptr = array) {
    }
}

読み込み

6. Read from IntPtr

ポインタから値を読み込む。

// unsafe不要
byte b = Marshal.ReadByte(intPtr);
short s = Marshal.ReadInt16(intPtr);
int i = Marshal.ReadInt32(intPtr);
long l = Marshal.ReadInt64(intPtr);

MyStruct st = Marshal.PtrToStructure<MyStruct>(intPtr);

7. Read from Pointer

ポインタから値を読み込む。

unsafe {
    // void* pointer
    byte b = Unsafe.Read<byte>(pointer);

    // アライメントを考慮しない版
    MyStruct s = Unsafe.ReadUnaligned<MyStruct>(pointer);
}

書き込み

8. Write to IntPtr

ポインタに値を書き込む。

// unsafe不要
Marshal.WriteByte(intPtr, 0x01);
Marshal.WriteInt16(intPtr, 0x0123);
Marshal.WriteInt32(intPtr, 0x0123_4567);
Marshal.WriteInt64(intPtr, 0x0123_4567_89ab_cdef);

Marshal.StructureToPtr<MyStruct>(myStruct, intPtr, fDeleteOld: false);

9. Write to Pointer

ポインタに値を書き込む。

Write() と Copy() が提供されていますが、参照渡しできる Copy() の方がパフォーマンスが良さそうです。

unsafe {
    // void* pointer
    Unsafe.Write<byte>(pointer0, 0xff);

    // アライメントを考慮しない版
    Unsafe.WriteUnaligned<MyStruct>(pointer1, myStruct);

    byte b = 0x00;
    Unsafe.Copy<byte>(pointer2, ref b);
}

10. Fill Pointer

ポインタから指定サイズ分だけ、値(byte)を書き込む。

unsafe {
    // void* pointer
    Unsafe.InitBlock(pointer, 0x00, (uint)size);

    // アライメントを考慮しない版
    Unsafe.InitBlockUnaligned(pointer, 0x00, (uint)size);
}

コピー

11. Copy Pointer -> Pointer

Unsafe

unsafe {
    // void* srcPointer / void* destPointer
    Unsafe.CopyBlock(srcPointer, destPointer, (uint)size);

    // アライメントを考慮しない版
    Unsafe.CopyBlockUnaligned(srcPointer, destPointer, (uint)size);
}

Buffer

Unsafeクラスには存在しない書き込み先メモリの使用可能なバイト数を設定する引数が存在しますが、使う場面が分からないので、上の Unsafe.CopyBlock() を使っておけば良さそうです。

unsafe {
    // void* srcPointer / void* destPointer
    Buffer.MemoryCopy(srcPointer, destPointer, size, size);
}

12. Copy IntPtr -> IntPtr

動作は速いが、マルチプラットフォームで動作しません。

RtlMoveMemory() @kernel32.dll

// unsafe不要
[DllImport("kernel32.dll", EntryPoint = "RtlMoveMemory", SetLastError = false)]
private static extern void RtlMoveMemory(IntPtr dest, IntPtr src, [MarshalAs(UnmanagedType.U4)] int length);

RtlMoveMemory(destIntPtr, srcIntPtr, length);

memcpy @msvcrt.dll

// unsafe不要
[DllImport("msvcrt.dll", EntryPoint = "memcpy", SetLastError = false)]
private static extern IntPtr memcpy(IntPtr dest, IntPtr src, UIntPtr count);

_ = memcpy(destIntPtr, srcIntPtr, (UIntPtr)length);

13. Copy IntPtr -> Array

// unsafe不要
Marshal.Copy(srcIntPtr, destArray, startIndex: 0, destArray.Length);

14. Copy Array -> IntPtr

// unsafe不要
Marshal.Copy(srcArray, startIndex: 0, destIntPtr, srcArray.Length);

その他

15. アンマネージドメモリの確保

AllocCoTaskMem() を使う方が良さげです。

AllocHGlobalとAllocCoTaskMem どちらを使うべきか? - Qiita

Marshl.AllocCoTaskMem()

// unsafe不要
IntPtr intPtr = Marshal.AllocCoTaskMem(allocSize);
// do something
Marshal.FreeCoTaskMem(intPtr);

Marshal.AllocHGlobal()

// unsafe不要
IntPtr intPtr = Marshal.AllocHGlobal(allocSize);
// do something
Marshal.FreeHGlobal(intPtr);

16. スタックメモリのポインタ取得

unsafe {
    byte* pointer = stackalloc byte[size];
}

17. Span化

unsafe {
    // void* pointer
    var span = new Span<byte>(pointer, length);
    var roSpan = new ReadOnlySpan<byte>(pointer, length);
}

終わりに

30連発くらいにはなるかと思っていましたが、遠く及びませんでした…

37
41
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
37
41