C#2.0時代のゲームプログラミング(25)

今日は、byteなどを仮想streamとみなして、その一部をstructとしてアクセスする方法を説明する。しばらく悩んでいたのだが、id:akirameiさんの多大なる協力を得てこのたび完成した。


・必要となる背景


例えば、メモリ上にzip headerをbyte[ ]として読み込むとする。この一部の範囲をあるstructとみなして、ZipHeader->CompressSizeのようにしてアクセスしたい。しかし、可能ならば、byte からstructへのまるごとコピーなどは発生して欲しくない。


・解決法


1. GCHandle.Allocを使って、メモリをGCのcompactionの対象から外す。
2. Marshal.UnsafeAddrOfPinnedArrayElementで、pinningされたIntPtrを取得。
3. そのIntPtrをunsafe文脈で、castして使用する。
4. 使い終わったら、GCHandle.Free()で 1. を解除。


以下、C++的なPointerをC#で擬似的に実現するクラス。(Yanesdk.NETのYtl/Pointer.csより抜粋)



001 /// <summary>
002 /// C/C++的なポインタを実現する
003 /// </summary>
004 /// <typeparam name="T"></typeparam>
005 public class Pointer<T>
006 {
007 /// <summary>
008 /// 配列の先頭を指すポインタを生成する
009 /// </summary>
010 /// <param name="a"></param>
011 /// <param name="index"></param>
012 public Pointer(T[] array)
013 {
014 this.array = array;
015 this.index = 0;
016 }
017
018 /// <summary>
019 /// 配列と、その配列のどの要素を指すポインタを生成するかを指定する
020 /// </summary>
021 /// <param name="a"></param>
022 /// <param name="index"></param>
023 public Pointer(T[] array,int index)
024 {
025 this.array = array;
026 this.index = index;
027 }
028
029 /// <summary>
030 /// ポインタに整数加算
031 /// </summary>
032 /// <remarks>ポインタ同士の加算は出来ないし、出来てはならない。
033 /// </remarks>
034 /// <param name="pointer"></param>
035 /// <param name="n"></param>
036 /// <returns></returns>
037 public static Pointer<T> operator + (Pointer<T> pointer,int n)
038 {
039 return new Pointer<T>(pointer.array,pointer.index+n);
040 }
041
042 /// <summary>
043 /// ポインタに整数減算
044 /// </summary>
045 /// <remarks>ポインタ同士の加算は出来ないし、出来てはならない。
046 /// </remarks>
047 /// <param name="pointer"></param>
048 /// <param name="n"></param>
049 /// <returns></returns>
050 public static Pointer<T> operator -(Pointer<T> pointer , int n)
051 {
052 return new Pointer<T>(pointer.array , pointer.index - n);
053 }
054
055
056 /// <summary>
057 /// Cloneメソッド
058 /// </summary>
059 /// <returns></returns>
060 public Pointer<T> Clone()
061 {
062 return new Pointer<T>(this.array , this.index);
063 }
064
065 /// <summary>
066 /// 参照はがし
067 /// </summary>
068 public T Value
069 {
070 get { return array[index]; }
071 set { array[index] = value; }
072 }
073
074
075 /// <summary>
076 /// C#だと制約がきつすぎて、あまり無茶なことが出来ないが…
077 /// IntPtrがもらえればunsafe文脈で structにcastして用いることが出来るだろう。
078 /// </summary>
079 /// <returns></returns>
080 public IntPtrForStruct GetIntPtrForStruct()
081 {
082 GCHandle handle = GCHandle.Alloc(array , GCHandleType.Pinned);
083 IntPtr ptr = Marshal.UnsafeAddrOfPinnedArrayElement(array, index);
084
085 return new IntPtrForStruct(handle,ptr);
086 }
087
088 protected T[] array;
089 protected int index;
090 }
091
092 /// <summary>
093 /// PointerクラスのGetIntPtrForStructで用いるための構造体
094 /// </summary>
095 /// <remarks>
096 /// Pointerクラスの外に出すのは、OOPの観点からすればおかしいが、
097 /// 利便性の上からは許されるだろう…
098 /// </remarks>
099 public class IntPtrForStruct : IDisposable
100 {
101 private GCHandle handle;
102
103 /// <summary>
104 /// このIntPtrをunsafe文脈で structにcastして使うべし。
105 /// </summary>
106 public IntPtr Ptr
107 {
108 get { return ptr; }
109 }
110 private IntPtr ptr;
111
112 public IntPtrForStruct(GCHandle handle , IntPtr ptr)
113 {
114 this.handle = handle;
115 this.ptr = ptr;
116 }
117
118 public void Dispose()
119 {
120 this.handle.Free();
121 }
122 }
123


・使用例


001 // pointerのテストコード
002 struct XYZ
003 {
004 public int x;
005 public int y;
006 public int z;
007 }
008
009 {
010 {
011 byte[] d = new byte[256];
012 Pointer<byte> p = new Pointer<byte>(d , 5);
013 using ( IntPtrForStruct s = p.GetIntPtrForStruct() )
014 {
015 unsafe
016 {
017 XYZ* xyz = ( XYZ* ) s.Ptr;
018 {
019 xyz->x = 123;
020 xyz->y = 234;
021 xyz->z = 345;
022 }
023 }
024 }
025 }
026 }