C#+無unsafe的非託管大陣列示例詳解(large unmanaged array in c# without ‘unsafe’ keyword)
C#申請一個大陣列(Use a large array in C#)
在C#裡,有時候我需要能夠申請一個很大的陣列、使用之、然後立即釋放其佔用的記憶體。
Sometimes I need to allocate a large array,use it and then release its memory space immediately.
由於在C#裡提供的 int[] array = new int[1000000]; 這樣的陣列,其記憶體釋放很難由程式設計師完全控制,在申請一個大陣列後,程式可能會變得很慢。
If I use something like int[] array = new int[1000000];,it will be difficult to release its memory space by programmer and the app probably runs slower and slower.
特別是在C#+OpenGL程式設計中,我在使用VAO/VBO時十分需要設計一個非託管的陣列,比如在glBufferData時我希望可以使用下面的glBufferData:
Specially in C#+OpenGL routines when I'm using VAO/VBO,I need an unmanaged array for glBufferData:
/// <summary> /// 設定當前VBO的資料。 /// </summary> /// <param name="target"></param> /// <param name="data"></param> /// <param name="usage"></param> public static void glBufferData(uint target,UnmanagedArrayBase data,uint usage) { GetDelegateFor<glBufferData>()((uint)target,data.ByteLength,// 使用非託管陣列 data.Header,// 使用非託管陣列 (uint)usage); } // ... // glBufferData的宣告 private delegate void glBufferData(uint target,int size,IntPtr data,uint usage);
而在指定VBO的資料時,可能是float、vec3等等型別:
And the content in VBO can be float,vec3 and any other structs.
/// <summary> /// 金字塔的posotion array. /// </summary> static vec3[] positions = new vec3[] { new vec3(0.0f,1.0f,0.0f),new vec3(-1.0f,-1.0f,1.0f),// ... new vec3(-1.0f,}; // Create a vertex buffer for the vertex data. { uint[] ids = new uint[1]; GL.GenBuffers(1,ids); GL.BindBuffer(GL.GL_ARRAY_BUFFER,ids[0]); // 使用vec3作為泛型的非託管陣列的引數 UnmanagedArray<vec3> positionArray = new UnmanagedArray<vec3>(positions.Length); for (int i = 0; i < positions.Length; i++) { // 使用this[i]這樣的索引方式來讀寫非託管陣列的元素 positionArray[i] = positions[i]; } GL.BufferData(BufferDataTarget.ArrayBuffer,positionArray,BufferDataUsage.StaticDraw); GL.VertexAttribPointer(positionLocation,3,GL.GL_FLOAT,false,IntPtr.Zero); GL.EnableVertexAttribArray(positionLocation); }
UnmanagedArray<T>
所以我設計了這樣一個非託管的陣列型別:無unsafe,可接收任何struct型別作為泛型引數,可隨時釋放記憶體。
So I designed this UnmangedArray<T> : no 'unsafe' keyword,takes any struct as generic parameter,can be released anytime you want.
1 /// <summary> 2 /// 元素型別為sbyte,byte,char,short,ushort,int,uint,long,ulong,float,double,decimal,bool或其它struct的非託管陣列。 3 /// <para>不能使用enum型別作為T。</para> 4 /// </summary> 5 /// <typeparam name="T">sbyte,bool或其它struct,不能使用enum型別作為T。</typeparam> 6 public class UnmanagedArray<T> : UnmanagedArrayBase where T : struct 7 { 8 9 /// <summary> 10 ///元素型別為sbyte,bool或其它struct的非託管陣列。 11 /// </summary> 12 /// <param name="count"></param> 13 [MethodImpl(MethodImplOptions.Synchronized)] 14 public UnmanagedArray(int count) 15 : base(count,Marshal.SizeOf(typeof(T))) 16 { 17 } 18 19 /// <summary> 20 /// 獲取或設定索引為<paramref name="index"/>的元素。 21 /// </summary> 22 /// <param name="index"></param> 23 /// <returns></returns> 24 public T this[int index] 25 { 26 get 27 { 28 if (index < 0 || index >= this.Count) 29 throw new IndexOutOfRangeException("index of UnmanagedArray is out of range"); 30 31 var pItem = this.Header + (index * elementSize); 32 //var obj = Marshal.PtrToStructure(pItem,typeof(T)); 33 //T result = (T)obj; 34 T result = Marshal.PtrToStructure<T>(pItem);// works in .net 4.5.1 35 return result; 36 } 37 set 38 { 39 if (index < 0 || index >= this.Count) 40 throw new IndexOutOfRangeException("index of UnmanagedArray is out of range"); 41 42 var pItem = this.Header + (index * elementSize); 43 //Marshal.StructureToPtr(value,pItem,true); 44 Marshal.StructureToPtr<T>(value,true);// works in .net 4.5.1 45 } 46 } 47 48 /// <summary> 49 /// 按索引順序依次獲取各個元素。 50 /// </summary> 51 /// <returns></returns> 52 public IEnumerable<T> GetElements() 53 { 54 if (!this.disposed) 55 { 56 for (int i = 0; i < this.Count; i++) 57 { 58 yield return this[i]; 59 } 60 } 61 } 62 } 63 64 /// <summary> 65 /// 非託管陣列的基類。 66 /// </summary> 67 public abstract class UnmanagedArrayBase : IDisposable 68 { 69 70 /// <summary> 71 /// 陣列指標。 72 /// </summary> 73 public IntPtr Header { get; private set; } 74 75 /// <summary> 76 /// 元素數目。 77 /// </summary> 78 public int Count { get; private set; } 79 80 /// <summary> 81 /// 單個元素的位元組數。 82 /// </summary> 83 protected int elementSize; 84 85 /// <summary> 86 /// 申請到的位元組數。(元素數目 * 單個元素的位元組數)。 87 /// </summary> 88 public int ByteLength 89 { 90 get { return this.Count * this.elementSize; } 91 } 92 93 94 /// <summary> 95 /// 非託管陣列。 96 /// </summary> 97 /// <param name="elementCount">元素數目。</param> 98 /// <param name="elementSize">單個元素的位元組數。</param> 99 [MethodImpl(MethodImplOptions.Synchronized)] 100 protected UnmanagedArrayBase(int elementCount,int elementSize) 101 { 102 this.Count = elementCount; 103 this.elementSize = elementSize; 104 105 int memSize = elementCount * elementSize; 106 this.Header = Marshal.AllocHGlobal(memSize); 107 108 allocatedArrays.Add(this); 109 } 110 111 private static readonly List<IDisposable> allocatedArrays = new List<IDisposable>(); 112 113 /// <summary> 114 /// 立即釋放所有<see cref="UnmanagedArray"/>。 115 /// </summary> 116 [MethodImpl(MethodImplOptions.Synchronized)] 117 public static void FreeAll() 118 { 119 foreach (var item in allocatedArrays) 120 { 121 item.Dispose(); 122 } 123 allocatedArrays.Clear(); 124 } 125 126 ~UnmanagedArrayBase() 127 { 128 Dispose(); 129 } 130 131 #region IDisposable Members 132 133 /// <summary> 134 /// Internal variable which checks if Dispose has already been called 135 /// </summary> 136 protected Boolean disposed; 137 138 /// <summary> 139 /// Releases unmanaged and - optionally - managed resources 140 /// </summary> 141 /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> 142 protected void Dispose(Boolean disposing) 143 { 144 if (disposed) 145 { 146 return; 147 } 148 149 if (disposing) 150 { 151 //Managed cleanup code here,while managed refs still valid 152 } 153 //Unmanaged cleanup code here 154 IntPtr ptr = this.Header; 155 156 if (ptr != IntPtr.Zero) 157 { 158 this.Count = 0; 159 this.Header = IntPtr.Zero; 160 Marshal.FreeHGlobal(ptr); 161 } 162 163 disposed = true; 164 } 165 166 /// <summary> 167 /// Performs application-defined tasks associated with freeing,releasing,or resetting unmanaged resources. 168 /// </summary> 169 public void Dispose() 170 { 171 this.Dispose(true); 172 GC.SuppressFinalize(this); 173 } 174 175 #endregion 176 177 } UnmanagedArray
如何使用(How to use)
UnmanagedArray<T>使用方式十分簡單,就像一個普通的陣列一樣:
Using UnamangedAray<T> is just like a normal array(int[],vec3[],etc.):
internal static void TypicalScene() { const int count = 100; // 測試float型別 var floatArray = new UnmanagedArray<float>(count); for (int i = 0; i < count; i++) { floatArray[i] = i; } for (int i = 0; i < count; i++) { var item = floatArray[i]; if (item != i) { throw new Exception(); } } // 測試int型別 var intArray = new UnmanagedArray<int>(count); for (int i = 0; i < count; i++) { intArray[i] = i; } for (int i = 0; i < count; i++) { var item = intArray[i]; if (item != i) { throw new Exception(); } } // 測試bool型別 var boolArray = new UnmanagedArray<bool>(count); for (int i = 0; i < count; i++) { boolArray[i] = i % 2 == 0; } for (int i = 0; i < count; i++) { var item = boolArray[i]; if (item != (i % 2 == 0)) { throw new Exception(); } } // 測試vec3型別 var vec3Array = new UnmanagedArray<vec3>(count); for (int i = 0; i < count; i++) { vec3Array[i] = new vec3(i * 3 + 0,i * 3 + 1,i * 3 + 2); } for (int i = 0; i < count; i++) { var item = vec3Array[i]; var old = new vec3(i * 3 + 0,i * 3 + 2); if (item.x != old.x || item.y != old.y || item.z != old.z) { throw new Exception(); } } // 測試foreach foreach (var item in vec3Array.GetElements()) { Console.WriteLine(item); } // 釋放此陣列佔用的記憶體,這之後就不能再使用vec3Array了。 vec3Array.Dispose(); // 立即釋放所有非託管陣列佔用的記憶體,這之後就不能再使用上面申請的陣列了。 UnmanagedArrayBase.FreeAll(); }
快速讀寫UnmanagedArray<T>
UnmanagedArrayHelper
由於很多時候需要申請和使用很大的UnmanagedArray<T>,直接使用this[index]索引方式速度會偏慢,所以我添加了幾個輔助方法,專門解決快速讀寫UnmanagedArray<T>的問題。
public static class UnmanagedArrayHelper { ///// <summary> ///// 錯誤 1 無法獲取託管型別(“T”)的地址和大小,或無法宣告指向它的指標 ///// </summary> ///// <typeparam name="T"></typeparam> ///// <param name="array"></param> ///// <returns></returns> //public static unsafe T* FirstElement<T>(this UnmanagedArray<T> array) where T : struct //{ // var header = (void*)array.Header; // return (T*)header; //} /// <summary> /// 獲取非託管陣列的第一個元素的地址。 /// </summary> /// <param name="array"></param> /// <returns></returns> public static unsafe void* FirstElement(this UnmanagedArrayBase array) { var header = (void*)array.Header; return header; } public static unsafe void* LastElement(this UnmanagedArrayBase array) { var last = (void*)(array.Header + (array.ByteLength - array.ByteLength / array.Length)); return last; } /// <summary> /// 獲取非託管陣列的最後一個元素的地址再向後一個單位的地址。 /// </summary> /// <param name="array"></param> /// <returns></returns> public static unsafe void* TailAddress(this UnmanagedArrayBase array) { var tail = (void*)(array.Header + array.ByteLength); return tail; } }
如何使用
這個型別實現了3個擴充套件方法,可以獲取UnmanagedArray<T>的第一個元素的位置、最後一個元素的位置、最後一個元素+1的位置。用這種unsafe的方法可以實現C語言一樣的讀寫速度。
下面是一個例子。用unsafe的方式讀寫UnmanagedArray<T>,速度比this[index]方式快10到70倍。
public static void TypicalScene() { int length = 1000000; UnmanagedArray<int> array = new UnmanagedArray<int>(length); UnmanagedArray<int> array2 = new UnmanagedArray<int>(length); long tick = DateTime.Now.Ticks; for (int i = 0; i < length; i++) { array[i] = i; } long totalTicks = DateTime.Now.Ticks - tick; tick = DateTime.Now.Ticks; unsafe { int* header = (int*)array2.FirstElement(); int* last = (int*)array2.LastElement(); int* tailAddress = (int*)array2.TailAddress(); int value = 0; for (int* ptr = header; ptr <= last/*or: ptr < tailAddress*/; ptr++) { *ptr = value++; } } long totalTicks2 = DateTime.Now.Ticks - tick; Console.WriteLine("ticks: {0},{1}",totalTicks,totalTicks2);// unsafe method works faster. for (int i = 0; i < length; i++) { if (array[i] != i) { Console.WriteLine("something wrong here"); } if (array2[i] != i) { Console.WriteLine("something wrong here"); } } array.Dispose(); array2.Dispose(); }
unsafe { vec3* header = (vec3*)vec3Array.FirstElement(); vec3* last = (vec3*)vec3Array.LastElement(); vec3* tailAddress = (vec3*)vec3Array.TailAddress(); int i = 0; for (vec3* ptr = header; ptr <= last/*or: ptr < tailAddress*/; ptr++) { *ptr = new vec3(i * 3 + 0,i * 3 + 2); i++; } i = 0; for (vec3* ptr = header; ptr <= last/*or: ptr < tailAddress*/; ptr++,i++) { var item = *ptr; var old = new vec3(i * 3 + 0,i * 3 + 2); if (item.x != old.x || item.y != old.y || item.z != old.z) { throw new Exception(); } } }
2015-08-25
用StructLayout和MarshalAs支援複雜的struct
在OpenGL中我需要用UnmanagedArray<mat4>,其中mat4定義如下:
1 /// <summary> 2 /// Represents a 4x4 matrix. 3 /// </summary> 4 [StructLayout(LayoutKind.Sequential,CharSet = CharSet.Ansi,Size = 4 * 4 * 4)] 5 public struct mat4 6 { 7 /// <summary> 8 /// Gets or sets the <see cref="vec4"/> column at the specified index. 9 /// </summary> 10 /// <value> 11 /// The <see cref="vec4"/> column. 12 /// </value> 13 /// <param name="column">The column index.</param> 14 /// <returns>The column at index <paramref name="column"/>.</returns> 15 public vec4 this[int column] 16 { 17 get { return cols[column]; } 18 set { cols[column] = value; } 19 } 20 21 /// <summary> 22 /// Gets or sets the element at <paramref name="column"/> and <paramref name="row"/>. 23 /// </summary> 24 /// <value> 25 /// The element at <paramref name="column"/> and <paramref name="row"/>. 26 /// </value> 27 /// <param name="column">The column index.</param> 28 /// <param name="row">The row index.</param> 29 /// <returns> 30 /// The element at <paramref name="column"/> and <paramref name="row"/>. 31 /// </returns> 32 public float this[int column,int row] 33 { 34 get { return cols[column][row]; } 35 set { cols[column][row] = value; } 36 } 37 38 /// <summary> 39 /// The columms of the matrix. 40 /// </summary> 41 [MarshalAs(UnmanagedType.ByValArray,SizeConst = 4)] 42 private vec4[] cols; 43 } 44 45 /// <summary> 46 /// Represents a four dimensional vector. 47 /// </summary> 48 [StructLayout(LayoutKind.Sequential,Size = 4 * 4)] 49 public struct vec4 50 { 51 public float x; 52 public float y; 53 public float z; 54 public float w; 55 56 public float this[int index] 57 { 58 get 59 { 60 if (index == 0) return x; 61 else if (index == 1) return y; 62 else if (index == 2) return z; 63 else if (index == 3) return w; 64 else throw new Exception("Out of range."); 65 } 66 set 67 { 68 if (index == 0) x = value; 69 else if (index == 1) y = value; 70 else if (index == 2) z = value; 71 else if (index == 3) w = value; 72 else throw new Exception("Out of range."); 73 } 74 } 75 } mat4
注意:UnmanagedArray<T>支援的struct,T的大小必須是確定的。所以在mat4裡我們用 [StructLayout(LayoutKind.Sequential,Size = 4 * 4 * 4)] 指定mat4的大小為4個 vec4 * 4個 float * 4個位元組(每個float) = 64位元組,並且在 private vec4[] cols; 上用 [MarshalAs(UnmanagedType.ByValArray,SizeConst = 4)] 規定了cols的元素數必須是4。之後在 vec4 上的 [StructLayout(LayoutKind.Sequential,Size = 4 * 4)] 不寫也可以,因為vec4只有4個簡單的float欄位,不含複雜型別。
下面是測試用例。
mat4 matrix = glm.scale(mat4.identity(),new vec3(2,4)); var size = Marshal.SizeOf(typeof(mat4)); size = Marshal.SizeOf(matrix); UnmanagedArray<mat4> array = new UnmanagedArray<mat4>(1); array[0] = matrix; mat4 newMatirx = array[0]; // newMatrix should be equal to matrix array.Dispose();
如果matrix和newMatrix相等,就說明上述Attribute配置正確了。
總結
到此這篇關於C#+無unsafe的非託管大陣列(large unmanaged array in c# without 'unsafe' keyword)的文章就介紹到這了,更多相關C#+無unsafe的非託管大陣列內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!