初めに
本記事は @gushwellさんの C#リフレクションTIPS 55連発に触発されて書きました。
C#リフレクションTIPS 55連発は、薄っすらとしか覚えていないリフレクションが網羅的にまとめられており、その便利さのおかげで 余計に リフレクションを覚えられなくなってしまった良記事です
私は業務で画像を扱うことが多く、P/Invoke でメモリを受け渡したり、ポインタ越しにメモリを操作するのですが、その度に過去のコードを探ったり、ぐぐったりしています。
ポインタに関わらず大体のことは ぐぐれば出てくるのですが、手間なので、未来の自分のため unsafeポインタ についてまとめました。
前提
これから示すコード(のほとんど)は、unsafe コンパイラオプションの有効化 と 下記usingディレクティブ が前提となっています。
動作は .NET Core 3.1 で確認しています。
usingSystem;usingSystem.Runtime.CompilerServices;usingSystem.Runtime.InteropServices;
型変換
System.IntPtr と void* は相互に変換できます。
1. Convert IntPtr -> Pointer
unsafe{// IntPtr intPtrvoid*pointer=intPtr.ToPointer();}
2. Convert Pointer -> IntPtr
unsafe{// void* pointerIntPtrintPtr0=newIntPtr(pointer);IntPtrintPtr1=(IntPtr)pointer;// どちらも同じ}
3. Convert StackData -> Pointer
どちらでも結果は同じなので、お好みで使えば良いと思います。
byteb;unsafe{byte*pointer=&b;}
byteb=0x00;unsafe{void*pointer=Unsafe.AsPointer<byte>(refb);}
4. Convert Pointer -> ref T
unsafe{// void* pointerrefbyteb=refUnsafe.AsRef<byte>(pointer);}
5. Convert Array -> Pointer
マネージドオブジェクトはヒープ領域で管理されているので、GCの再配置を防ぐため fixed を使う必要があります。
byte[]array=newbyte[size];unsafe{fixed(byte*ptr=array){}}
読み込み
6. Read from IntPtr
ポインタから値を読み込む。
// unsafe不要byteb=Marshal.ReadByte(intPtr);shorts=Marshal.ReadInt16(intPtr);inti=Marshal.ReadInt32(intPtr);longl=Marshal.ReadInt64(intPtr);MyStructst=Marshal.PtrToStructure<MyStruct>(intPtr);
7. Read from Pointer
ポインタから値を読み込む。
unsafe{// void* pointerbyteb=Unsafe.Read<byte>(pointer);// アライメントを考慮しない版MyStructs=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* pointerUnsafe.Write<byte>(pointer0,0xff);// アライメントを考慮しない版Unsafe.WriteUnaligned<MyStruct>(pointer1,myStruct);byteb=0x00;Unsafe.Copy<byte>(pointer2,refb);}
10. Fill Pointer
ポインタから指定サイズ分だけ、値(byte)を書き込む。
unsafe{// void* pointerUnsafe.InitBlock(pointer,0x00,(uint)size);// アライメントを考慮しない版Unsafe.InitBlockUnaligned(pointer,0x00,(uint)size);}
コピー
11. Copy Pointer -> Pointer
Unsafe
unsafe{// void* srcPointer / void* destPointerUnsafe.CopyBlock(srcPointer,destPointer,(uint)size);// アライメントを考慮しない版Unsafe.CopyBlockUnaligned(srcPointer,destPointer,(uint)size);}
Buffer
Unsafeクラスには存在しない書き込み先メモリの使用可能なバイト数を設定する引数が存在しますが、使う場面が分からないので、上の Unsafe.CopyBlock() を使っておけば良さそうです。
unsafe{// void* srcPointer / void* destPointerBuffer.MemoryCopy(srcPointer,destPointer,size,size);}
12. Copy IntPtr -> IntPtr
動作は速いが、マルチプラットフォームで動作しません。
RtlMoveMemory() @kernel32.dll
// unsafe不要[DllImport("kernel32.dll",EntryPoint="RtlMoveMemory",SetLastError=false)]privatestaticexternvoidRtlMoveMemory(IntPtrdest,IntPtrsrc,[MarshalAs(UnmanagedType.U4)]intlength);RtlMoveMemory(destIntPtr,srcIntPtr,length);
memcpy @msvcrt.dll
// unsafe不要[DllImport("msvcrt.dll",EntryPoint="memcpy",SetLastError=false)]privatestaticexternIntPtrmemcpy(IntPtrdest,IntPtrsrc,UIntPtrcount);_=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不要IntPtrintPtr=Marshal.AllocCoTaskMem(allocSize);// do somethingMarshal.FreeCoTaskMem(intPtr);
Marshal.AllocHGlobal()
// unsafe不要IntPtrintPtr=Marshal.AllocHGlobal(allocSize);// do somethingMarshal.FreeHGlobal(intPtr);
16. スタックメモリのポインタ取得
unsafe{byte*pointer=stackallocbyte[size];}
17. Span化
unsafe{// void* pointervarspan=newSpan<byte>(pointer,length);varroSpan=newReadOnlySpan<byte>(pointer,length);}
終わりに
30連発くらいにはなるかと思っていましたが、遠く及びませんでした…