C#+無(wú)unsafe的非托管大數(shù)組示例詳解(large unmanaged array in c# without ‘unsafe’ keyword)
C#申請(qǐng)一個(gè)大數(shù)組(Use a large array in C#)
在C#里,有時(shí)候我需要能夠申請(qǐng)一個(gè)很大的數(shù)組、使用之、然后立即釋放其占用的內(nèi)存。
Sometimes I need to allocate a large array, use it and then release its memory space immediately.
由于在C#里提供的 int[] array = new int[1000000]; 這樣的數(shù)組,其內(nèi)存釋放很難由程序員完全控制,在申請(qǐng)一個(gè)大數(shù)組后,程序可能會(huì)變得很慢。
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時(shí)十分需要設(shè)計(jì)一個(gè)非托管的數(shù)組,比如在glBufferData時(shí)我希望可以使用下面的glBufferData:
Specially in C#+OpenGL routines when I'm using VAO/VBO, I need an unmanaged array for glBufferData:
/// <summary>
/// 設(shè)置當(dāng)前VBO的數(shù)據(jù)。
/// </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, // 使用非托管數(shù)組
data.Header, // 使用非托管數(shù)組
(uint)usage);
}
// ...
// glBufferData的聲明
private delegate void glBufferData(uint target, int size, IntPtr data, uint usage);
而在指定VBO的數(shù)據(jù)時(shí),可能是float、vec3等等類(lèi)型:
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作為泛型的非托管數(shù)組的參數(shù)
UnmanagedArray<vec3> positionArray = new UnmanagedArray<vec3>(positions.Length);
for (int i = 0; i < positions.Length; i++)
{
// 使用this[i]這樣的索引方式來(lái)讀寫(xiě)非托管數(shù)組的元素
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>
所以我設(shè)計(jì)了這樣一個(gè)非托管的數(shù)組類(lèi)型:無(wú)unsafe,可接收任何struct類(lèi)型作為泛型參數(shù),可隨時(shí)釋放內(nèi)存。
So I designed this UnmangedArray<T> : no 'unsafe' keyword, takes any struct as generic parameter, can be released anytime you want.
1 /// <summary>
2 /// 元素類(lèi)型為sbyte, byte, char, short, ushort, int, uint, long, ulong, float, double, decimal, bool或其它struct的非托管數(shù)組。
3 /// <para>不能使用enum類(lèi)型作為T(mén)。</para>
4 /// </summary>
5 /// <typeparam name="T">sbyte, byte, char, short, ushort, int, uint, long, ulong, float, double, decimal, bool或其它struct, 不能使用enum類(lèi)型作為T(mén)。</typeparam>
6 public class UnmanagedArray<T> : UnmanagedArrayBase where T : struct
7 {
8
9 /// <summary>
10 ///元素類(lèi)型為sbyte, byte, char, short, ushort, int, uint, long, ulong, float, double, decimal, bool或其它struct的非托管數(shù)組。
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 /// 獲取或設(shè)置索引為<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 /// 按索引順序依次獲取各個(gè)元素。
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 /// 非托管數(shù)組的基類(lèi)。
66 /// </summary>
67 public abstract class UnmanagedArrayBase : IDisposable
68 {
69
70 /// <summary>
71 /// 數(shù)組指針。
72 /// </summary>
73 public IntPtr Header { get; private set; }
74
75 /// <summary>
76 /// 元素?cái)?shù)目。
77 /// </summary>
78 public int Count { get; private set; }
79
80 /// <summary>
81 /// 單個(gè)元素的字節(jié)數(shù)。
82 /// </summary>
83 protected int elementSize;
84
85 /// <summary>
86 /// 申請(qǐng)到的字節(jié)數(shù)。(元素?cái)?shù)目 * 單個(gè)元素的字節(jié)數(shù))。
87 /// </summary>
88 public int ByteLength
89 {
90 get { return this.Count * this.elementSize; }
91 }
92
93
94 /// <summary>
95 /// 非托管數(shù)組。
96 /// </summary>
97 /// <param name="elementCount">元素?cái)?shù)目。</param>
98 /// <param name="elementSize">單個(gè)元素的字節(jié)數(shù)。</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>使用方式十分簡(jiǎn)單,就像一個(gè)普通的數(shù)組一樣:
Using UnamangedAray<T> is just like a normal array(int[], vec3[], etc.):
internal static void TypicalScene()
{
const int count = 100;
// 測(cè)試float類(lèi)型
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(); }
}
// 測(cè)試int類(lèi)型
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(); }
}
// 測(cè)試bool類(lèi)型
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(); }
}
// 測(cè)試vec3類(lèi)型
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(); }
}
// 測(cè)試foreach
foreach (var item in vec3Array.GetElements())
{
Console.WriteLine(item);
}
// 釋放此數(shù)組占用的內(nèi)存,這之后就不能再使用vec3Array了。
vec3Array.Dispose();
// 立即釋放所有非托管數(shù)組占用的內(nèi)存,這之后就不能再使用上面申請(qǐng)的數(shù)組了。
UnmanagedArrayBase.FreeAll();
}
快速讀寫(xiě)UnmanagedArray<T>
UnmanagedArrayHelper
由于很多時(shí)候需要申請(qǐng)和使用很大的UnmanagedArray<T>,直接使用this[index]索引方式速度會(huì)偏慢,所以我添加了幾個(gè)輔助方法,專(zhuān)門(mén)解決快速讀寫(xiě)UnmanagedArray<T>的問(wèn)題。
public static class UnmanagedArrayHelper
{
///// <summary>
///// 錯(cuò)誤 1 無(wú)法獲取托管類(lèi)型(“T”)的地址和大小,或無(wú)法聲明指向它的指針
///// </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>
/// 獲取非托管數(shù)組的第一個(gè)元素的地址。
/// </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>
/// 獲取非托管數(shù)組的最后一個(gè)元素的地址再向后一個(gè)單位的地址。
/// </summary>
/// <param name="array"></param>
/// <returns></returns>
public static unsafe void* TailAddress(this UnmanagedArrayBase array)
{
var tail = (void*)(array.Header + array.ByteLength);
return tail;
}
}
如何使用
這個(gè)類(lèi)型實(shí)現(xiàn)了3個(gè)擴(kuò)展方法,可以獲取UnmanagedArray<T>的第一個(gè)元素的位置、最后一個(gè)元素的位置、最后一個(gè)元素+1的位置。用這種unsafe的方法可以實(shí)現(xiàn)C語(yǔ)言一樣的讀寫(xiě)速度。
下面是一個(gè)例子。用unsafe的方式讀寫(xiě)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 + 1, 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 + 1, i * 3 + 2);
if (item.x != old.x || item.y != old.y || item.z != old.z)
{ throw new Exception(); }
}
}
2015-08-25
用StructLayout和MarshalAs支持復(fù)雜的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, 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 }
mat4
注意:UnmanagedArray<T>支持的struct,T的大小必須是確定的。所以在mat4里我們用 [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Size = 4 * 4 * 4)] 指定mat4的大小為4個(gè) vec4 * 4個(gè) float * 4個(gè)字節(jié)(每個(gè)float) = 64字節(jié),并且在 private vec4[] cols; 上用 [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] 規(guī)定了cols的元素?cái)?shù)必須是4。之后在 vec4 上的 [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Size = 4 * 4)] 不寫(xiě)也可以,因?yàn)関ec4只有4個(gè)簡(jiǎn)單的float字段,不含復(fù)雜類(lèi)型。
下面是測(cè)試用例。
mat4 matrix = glm.scale(mat4.identity(), new vec3(2, 3, 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相等,就說(shuō)明上述Attribute配置正確了。
總結(jié)
到此這篇關(guān)于C#+無(wú)unsafe的非托管大數(shù)組(large unmanaged array in c# without 'unsafe' keyword)的文章就介紹到這了,更多相關(guān)C#+無(wú)unsafe的非托管大數(shù)組內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
一個(gè)C#開(kāi)發(fā)者重溫C++的心路歷程
作為一個(gè)C#開(kāi)發(fā)為什么要重新學(xué)習(xí)C++呢?因?yàn)樵贑#在很多業(yè)務(wù)場(chǎng)景需要調(diào)用一些C++編寫(xiě)的COM組件,如果不了解C++,那么,很容易。。。注定是要被C++同事忽悠的2019-05-05
C#實(shí)現(xiàn)路由器斷開(kāi)連接,更改公網(wǎng)ip的實(shí)例代碼
C#實(shí)現(xiàn)路由器斷開(kāi)連接,更改公網(wǎng)ip的實(shí)例代碼,需要的朋友可以參考一下2013-05-05
淺談C#9.0新特性之參數(shù)非空檢查簡(jiǎn)化
這篇文章主要介紹了淺談C#9.0新特性之參數(shù)非空檢查簡(jiǎn)化,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-06-06
C#控件Picturebox實(shí)現(xiàn)鼠標(biāo)拖拽功能
這篇文章主要為大家詳細(xì)介紹了C#控件Picturebox實(shí)現(xiàn)鼠標(biāo)拖拽功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-09-09
MessageBox的Buttons和三級(jí)聯(lián)動(dòng)效果
這篇文章主要介紹了MessageBox的Buttons和三級(jí)聯(lián)動(dòng)的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-11-11

