C#+无unsafe的非托管大数组示例详解(large unmanaged array in c# without ‘unsafe’ keyword)

这篇文章主要给大家介绍了关于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, -1.0f, 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, 0, 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, byte, char, short, ushort, int, uint, long, ulong, float, double, decimal, bool或其它struct, 不能使用enum类型作为T。</typeparam>
 6 public class UnmanagedArray<T> : UnmanagedArrayBase where T : struct
 7 {
 8 
 9 /// <summary>
 10 ///元素类型为sbyte, byte, char, short, ushort, int, uint, long, ulong, float, double, decimal, 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, pItem, 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 + 1, 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;
 /

本文标题为:C#+无unsafe的非托管大数组示例详解(large unmanaged array in c# without ‘unsafe’ keyword)

基础教程推荐