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 UnmanagedArraypositionArray = 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<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 ///2 /// 元素类型为sbyte, byte, char, short, ushort, int, uint, long, ulong, float, double, decimal, bool或其它struct的非托管数组。 3 /// 5 ///不能使用enum类型作为T。 4 ///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 /// 获取或设置索引为 22 /// 23 ///的元素。 21 /// 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 ListallocatedArrays = new List ();112 113 /// 114 /// 立即释放所有 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 Members132 133 ///。115 /// 134 /// Internal variable which checks if Dispose has already been called135 /// 136 protected Boolean disposed;137 138 ///139 /// Releases unmanaged and - optionally - managed resources140 /// 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 valid152 }153 //Unmanaged cleanup code here154 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 #endregion176 177 }
如何使用(How to use)
UnmanagedArray<T>使用方式十分简单,就像一个普通的数组一样:
Using UnamangedAray<T> is just like a normal array(int[], vec3[], etc.):
1 internal static void TypicalScene() 2 { 3 const int count = 100; 4 5 // 测试float类型 6 var floatArray = new UnmanagedArray(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 (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 (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 // 测试foreach59 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<T>
UnmanagedArrayHelper
由于很多时候需要申请和使用很大的UnmanagedArray<T>,直接使用this[index]索引方式速度会偏慢,所以我添加了几个辅助方法,专门解决快速读写UnmanagedArray<T>的问题。
1 public static class UnmanagedArrayHelper 2 { 3 /////4 ///// 错误 1 无法获取托管类型(“T”)的地址和大小,或无法声明指向它的指针 5 ///// 6 /////7 ///// 8 ///// 9 //public static unsafe T* FirstElement (this UnmanagedArray array) where T : struct10 //{11 // var header = (void*)array.Header;12 // return (T*)header;13 //}14 15 /// 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<T>的第一个元素的位置、最后一个元素的位置、最后一个元素+1的位置。用这种unsafe的方法可以实现C语言一样的读写速度。
下面是一个例子。用unsafe的方式读写UnmanagedArray<T>,速度比this[index]方式快10到70倍。
1 public static void TypicalScene() 2 { 3 int length = 1000000; 4 UnmanagedArray array = new UnmanagedArray (length); 5 UnmanagedArray array2 = new UnmanagedArray (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 unsafe16 {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<mat4>,其中mat4定义如下:
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 10 ///column at the specified index. 9 /// 11 /// The 13 /// The column index.14 ///column.12 /// 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 24 ///and .23 /// 25 /// The element at 27 /// The column index.28 /// The row index.29 ///and .26 /// 30 /// The element at 32 public float this[int column, int row]33 {34 get { return cols[column][row]; }35 set { cols[column][row] = value; }36 }37 38 ///and .31 /// 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 vec450 {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 get59 {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 set67 {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<T>支持的struct,T的大小必须是确定的。所以在mat4里我们用 [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, 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, CharSet = CharSet.Ansi, Size = 4 * 4)] 不写也可以,因为vec4只有4个简单的float字段,不含复杂类型。
下面是测试用例。
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 matrix10 11 array.Dispose();
如果matrix和newMatrix相等,就说明上述Attribute配置正确了。