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:
1 ///2 /// 设置当前VBO的数据。 3 /// 4 /// 5 /// 6 /// 7 public static void glBufferData(uint target, UnmanagedArrayBase data, uint usage) 8 { 9 GetDelegateFor ()((uint)target, 10 data.ByteLength, // 使用非托管数组 11 data.Header, // 使用非托管数组 12 (uint)usage); 13 } 14 // ... 15 // glBufferData的声明 16 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.
1 ///2 /// 金字塔的posotion array. 3 /// 4 static vec3[] positions = new vec3[] 5 { 6 new vec3(0.0f, 1.0f, 0.0f), 7 new vec3(-1.0f, -1.0f, 1.0f), 8 // ... 9 new vec3(-1.0f, -1.0f, 1.0f), 10 }; 11 // Create a vertex buffer for the vertex data. 12 { 13 uint[] ids = new uint[1]; 14 GL.GenBuffers(1, ids); 15 GL.BindBuffer(GL.GL_ARRAY_BUFFER, ids[0]); 16 // 使用vec3作为泛型的非托管数组的参数 17 UnmanagedArray positionArray = new UnmanagedArray (positions.Length); 18 for (int i = 0; i < positions.Length; i++) 19 { 20 // 使用this[i]这样的索引方式来读写非托管数组的元素 21 positionArray[i] = positions[i]; 22 } 23 GL.BufferData(BufferDataTarget.ArrayBuffer, positionArray, BufferDataUsage.StaticDraw); 24 GL.VertexAttribPointer(positionLocation, 3, GL.GL_FLOAT, false, 0, IntPtr.Zero); 25 GL.EnableVertexAttribArray(positionLocation); 26 }
UnmanagedArray
所以我设计了这样一个非托管的数组类型:无unsafe,可接收任何struct类型作为泛型参数,可随时释放内存。
So I designed this UnmangedArray
1 ///2 /// 元素类型为sbyte, byte, char, short, ushort, int, uint, long, ulong, float, double, decimal, bool或其它struct的非托管数组。 3 /// 不能使用enum类型作为T。 4 /// 5 /// sbyte, byte, char, short, ushort, int, uint, long, ulong, float, double, decimal, bool或其它struct, 不能使用enum类型作为T。 6 public class UnmanagedArray : UnmanagedArrayBase where T : struct 7 { 8 9 /// 10 ///元素类型为sbyte, byte, char, short, ushort, int, uint, long, ulong, float, double, decimal, bool或其它struct的非托管数组。 11 /// 12 /// 13 [MethodImpl(MethodImplOptions.Synchronized)] 14 public UnmanagedArray(int count) 15 : base(count, Marshal.SizeOf(typeof(T))) 16 { 17 } 18 19 /// 20 /// 获取或设置索引为 的元素。 21 /// 22 /// 23 /// 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 (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 (value, pItem, true);// works in .net 4.5.1 45 } 46 } 47 48 /// 49 /// 按索引顺序依次获取各个元素。 50 /// 51 /// 52 public IEnumerable 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 /// 65 /// 非托管数组的基类。 66 /// 67 public abstract class UnmanagedArrayBase : IDisposable 68 { 69 70 /// 71 /// 数组指针。 72 /// 73 public IntPtr Header { get; private set; } 74 75 /// 76 /// 元素数目。 77 /// 78 public int Count { get; private set; } 79 80 /// 81 /// 单个元素的字节数。 82 /// 83 protected int elementSize; 84 85 /// 86 /// 申请到的字节数。(元素数目 * 单个元素的字节数)。 87 /// 88 public int ByteLength 89 { 90 get { return this.Count * this.elementSize; } 91 } 92 93 94 /// 95 /// 非托管数组。 96 /// 97 /// 元素数目。 98 /// 单个元素的字节数。 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 allocatedArrays = new List (); 112 113 /// 114 /// 立即释放所有 。 115 /// 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 /// 134 /// Internal variable which checks if Dispose has already been called 135 /// 136 protected Boolean disposed; 137 138 /// 139 /// Releases unmanaged and - optionally - managed resources 140 /// 141 /// true to release both managed and unmanaged resources; false to release only unmanaged resources. 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 /// 167 /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. 168 /// 169 public void Dispose() 170 { 171 this.Dispose(true); 172 GC.SuppressFinalize(this); 173 } 174 175 #endregion 176 177 }
如何使用(How to use)
UnmanagedArray
Using UnamangedAray
1 internal static void TypicalScene() 2 { 3 const int count = 100; 4 5 // 测试float类型 6 var floatArray = new UnmanagedArray<float>(count); 7 for (int i = 0; i < count; i++) 8 { 9 floatArray[i] = i; 10 } 11 for (int i = 0; i < count; i++) 12 { 13 var item = floatArray[i]; 14 if (item != i) 15 { throw new Exception(); } 16 } 17 18 // 测试int类型 19 var intArray = new UnmanagedArray<int>(count); 20 for (int i = 0; i < count; i++) 21 { 22 intArray[i] = i; 23 } 24 for (int i = 0; i < count; i++) 25 { 26 var item = intArray[i]; 27 if (item != i) 28 { throw new Exception(); } 29 } 30 31 // 测试bool类型 32 var boolArray = new UnmanagedArray<bool>(count); 33 for (int i = 0; i < count; i++) 34 { 35 boolArray[i] = i % 2 == 0; 36 } 37 for (int i = 0; i < count; i++) 38 { 39 var item = boolArray[i]; 40 if (item != (i % 2 == 0)) 41 { throw new Exception(); } 42 } 43 44 // 测试vec3类型 45 var vec3Array = new UnmanagedArray(count); 46 for (int i = 0; i < count; i++) 47 { 48 vec3Array[i] = new vec3(i * 3 + 0, i * 3 + 1, i * 3 + 2); 49 } 50 for (int i = 0; i < count; i++) 51 { 52 var item = vec3Array[i]; 53 var old = new vec3(i * 3 + 0, i * 3 + 1, i * 3 + 2); 54 if (item.x != old.x || item.y != old.y || item.z != old.z) 55 { throw new Exception(); } 56 } 57 58 // 测试foreach 59 foreach (var item in vec3Array.GetElements()) 60 { 61 Console.WriteLine(item); 62 } 63 64 // 释放此数组占用的内存,这之后就不能再使用vec3Array了。 65 vec3Array.Dispose(); 66 67 // 立即释放所有非托管数组占用的内存,这之后就不能再使用上面申请的数组了。 68 UnmanagedArrayBase.FreeAll(); 69 }
快速读写UnmanagedArray
UnmanagedArrayHelper
由于很多时候需要申请和使用很大的UnmanagedArray
1 public static class UnmanagedArrayHelper 2 { 3 /////4 ///// 错误 1 无法获取托管类型(“T”)的地址和大小,或无法声明指向它的指针 5 ///// 6 ///// 7 ///// 8 ///// 9 //public static unsafe T* FirstElement (this UnmanagedArray 10 //{ 11 // var header = (void*)array.Header; 12 // return (T*)header; 13 //} 14 15 ///array) where T : struct 16 /// 获取非托管数组的第一个元素的地址。 17 /// 18 /// 19 /// 20 public static unsafe void* FirstElement(this UnmanagedArrayBase array) 21 { 22 var header = (void*)array.Header; 23 24 return header; 25 } 26 27 public static unsafe void* LastElement(this UnmanagedArrayBase array) 28 { 29 var last = (void*)(array.Header + (array.ByteLength - array.ByteLength / array.Length)); 30 31 return last; 32 } 33 34 /// 35 /// 获取非托管数组的最后一个元素的地址再向后一个单位的地址。 36 /// 37 /// 38 /// 39 public static unsafe void* TailAddress(this UnmanagedArrayBase array) 40 { 41 var tail = (void*)(array.Header + array.ByteLength); 42 43 return tail; 44 } 45 }
如何使用
这个类型实现了3个扩展方法,可以获取UnmanagedArray
下面是一个例子。用unsafe的方式读写UnmanagedArray
1 public static void TypicalScene() 2 { 3 int length = 1000000; 4 UnmanagedArray<int> array = new UnmanagedArray<int>(length); 5 UnmanagedArray<int> array2 = new UnmanagedArray<int>(length); 6 7 long tick = DateTime.Now.Ticks; 8 for (int i = 0; i < length; i++) 9 { 10 array[i] = i; 11 } 12 long totalTicks = DateTime.Now.Ticks - tick; 13 14 tick = DateTime.Now.Ticks; 15 unsafe 16 { 17 int* header = (int*)array2.FirstElement(); 18 int* last = (int*)array2.LastElement(); 19 int* tailAddress = (int*)array2.TailAddress(); 20 int value = 0; 21 for (int* ptr = header; ptr <= last/*or: ptr < tailAddress*/; ptr++) 22 { 23 *ptr = value++; 24 } 25 } 26 long totalTicks2 = DateTime.Now.Ticks - tick; 27 Console.WriteLine("ticks: {0}, {1}", totalTicks, totalTicks2);// unsafe method works faster. 28 29 for (int i = 0; i < length; i++) 30 { 31 if (array[i] != i) 32 { 33 Console.WriteLine("something wrong here"); 34 } 35 if (array2[i] != i) 36 { 37 Console.WriteLine("something wrong here"); 38 } 39 } 40 41 array.Dispose(); 42 array2.Dispose(); 43 }
1 unsafe 2 { 3 vec3* header = (vec3*)vec3Array.FirstElement(); 4 vec3* last = (vec3*)vec3Array.LastElement(); 5 vec3* tailAddress = (vec3*)vec3Array.TailAddress(); 6 int i = 0; 7 for (vec3* ptr = header; ptr <= last/*or: ptr < tailAddress*/; ptr++) 8 { 9 *ptr = new vec3(i * 3 + 0, i * 3 + 1, i * 3 + 2); 10 i++; 11 } 12 i = 0; 13 for (vec3* ptr = header; ptr <= last/*or: ptr < tailAddress*/; ptr++, i++) 14 { 15 var item = *ptr; 16 var old = new vec3(i * 3 + 0, i * 3 + 1, i * 3 + 2); 17 if (item.x != old.x || item.y != old.y || item.z != old.z) 18 { throw new Exception(); } 19 } 20 }
2015-08-25
用StructLayout和MarshalAs支持复杂的struct
在OpenGL中我需要用UnmanagedArray
1 ///2 /// Represents a 4x4 matrix. 3 /// 4 [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Size = 4 * 4 * 4)] 5 public struct mat4 6 { 7 /// 8 /// Gets or sets the column at the specified index. 9 /// 10 /// 11 /// The column. 12 /// 13 /// The column index. 14 /// The column at index . 15 public vec4 this[int column] 16 { 17 get { return cols[column]; } 18 set { cols[column] = value; } 19 } 20 21 /// 22 /// Gets or sets the element at and . 23 /// 24 /// 25 /// The element at and . 26 /// 27 /// The column index. 28 /// The row index. 29 /// 30 /// The element at and . 31 /// 32 public float this[int column, int row] 33 { 34 get { return cols[column][row]; } 35 set { cols[column][row] = value; } 36 } 37 38 /// 39 /// The columms of the matrix. 40 /// 41 [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] 42 private vec4[] cols; 43 } 44 45 /// 46 /// Represents a four dimensional vector. 47 /// 48 [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, 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 }
注意:UnmanagedArray
下面是测试用例。
1 mat4 matrix = glm.scale(mat4.identity(), new vec3(2, 3, 4)); 2 3 var size = Marshal.SizeOf(typeof(mat4)); 4 size = Marshal.SizeOf(matrix); 5 6 UnmanagedArrayarray = new UnmanagedArray (1); 7 array[0] = matrix; 8 9 mat4 newMatirx = array[0]; // newMatrix should be equal to matrix 10 11 array.Dispose();
如果matrix和newMatrix相等,就说明上述Attribute配置正确了。