unity常用代码Native
参考 c#指针IntPtr
参考 c#调用c++
主要用到的类有
-
Allocator
内存分配枚举值,说明要分配什么样的内存
Invalid
无效分配。
None
未分配,当使用NativeArrayUnsafeUtility用非托管内存指针创建 NativeArray 时可以传入 None ,让 NativeArray 直接使用传入的内存
Temp
最快的分配方法,适用于一帧内的生命时长,不能将该类型分配的数据传给 Job,用完调用Dispose
比如在一个同步函数的开始分配内存,在函数结束前调用 DisposeTempJob 分配速度比 Temp 慢比 Persistent 快,4帧的生命时长且线程安全。若四帧内没有调用Dispose,控制台会打印原生代码生成的警告。 大部分小任务都使用该类型分配NativeContainer 比如给一个job分配内存,等分配线程后执行完毕释放内存,必须保证在4帧内完成 Persistent 是对malloc的包装,能够维持尽可能地生命时长,在非常注重性能的情况下不应使用 Persistent
-
DisposeSentinel
这个类主要给 NativeArray 使用,用来监测内存是否正确释放,也就是检测内存泄漏
在 NativeArray 构造时调用 DisposeSentinel.Create 创建 AtomicSafetyHandle 和 DisposeSentinel
只有 allocator != Allocator.Temp 才会创建 DisposeSentinel
在 NativeArray.Dispose 时调用 DisposeSentinel.Dispose 释放 AtomicSafetyHandle 和 DisposeSentinel,用于监测 NativeArray 是否被释放,有2个作用- 调用 AtomicSafetyHandle.Release , 把 AtomicSafetyHandle 置成已释放状态
- 在析构函数中检查是否处于释放状态,否则提示内存泄漏
-
AtomicSafetyHandle
这个类主要给 NativeArray 使用,用来保证线程安全的
在 NativeArray 构造时调用 DisposeSentinel.Create 创建 AtomicSafetyHandle 和 DisposeSentinel
safety = ((allocator == Allocator.Temp) ? AtomicSafetyHandle.GetTempMemoryHandle() : AtomicSafetyHandle.Create());
在 NativeArray.Dispose 时调用 DisposeSentinel.Dispose 释放 AtomicSafetyHandle 和 DisposeSentinel
if (!AtomicSafetyHandle.IsTempMemoryHandle(safety)) {AtomicSafetyHandle.Release(safety);}
当我们使用 NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray 把一个指针转成 NativeArray 时,直接使用[]访问元素可能会触发AtomicSafetyHandle.CheckReadAndThrow 异常,这时候可以有2种处理方式-
使用变量的地方在定义变量时添加 [NativeDisableUnsafePtrRestriction] 属性
-
自己设置 AtomicSafetyHandle
NativeArray<byte> bytes = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray<byte>(ptr, data.Length, Unity.Collections.Allocator.None); // 仅在 ENABLE_UNITY_COLLECTIONS_CHECKS 开启时才设置,不然有些平台会报错 #if ENABLE_UNITY_COLLECTIONS_CHECKS // 设置 AtomicSafetyHandle AtomicSafetyHandle safetyHandle = AtomicSafetyHandle.Create(); NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref bytes, safetyHandle); #endif DoSomething(bytes); // 释放 AtomicSafetyHandle AtomicSafetyHandle.Release(safetyHandle);
-
使用临时的AtomicSafetyHandle
NativeArray<byte> bytes = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray<byte>(ptr, data.Length, Unity.Collections.Allocator.None); // 仅在 ENABLE_UNITY_COLLECTIONS_CHECKS 开启时才设置,不然有些平台会报错 #if ENABLE_UNITY_COLLECTIONS_CHECKS // 设置 AtomicSafetyHandle AtomicSafetyHandle safetyHandle = AtomicSafetyHandle.GetTempUnsafePtrSliceHandle(); #endif NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref bytes, safetyHandle);
-
-
Unity.Collections.LowLevel.Unsafe.UnsafeUtility
封装了很多数组拷贝,转换,托管内存和非托管内存操作等
AddressOf 此结构的内存地址。
AlignOf 结构的最小对齐单位,跟c++对齐类似,一个结构体只包含 char 和 int ,则对齐大小为 4+4=8
CopyObjectAddressToPtr 分配对结构或固定类的对象引用
CopyPtrToStructure 从 ptr 向 output 复制 sizeof(T) 个字节。
CopyStructureToPtr 从 input 向 ptr 复制 sizeof(T) 个字节。
EnumToInt 不需要拆箱,直接获得枚举值的整形表达式
Free 释放内存
GetFieldOffset 返回字段相对于结构或其所在类的偏移。
IsBlittable 返回此结构是否可直接复制到本机结构中,此结构应只包含blittable数值类型,在跟c通信时不应声明MarshalAs
blittable数值类型指托管代码和原生代码的二进制表达方式一致,比如 byte int float,不包括 bool string
IsUnmanaged 返回结构或类型是否非托管的. 非托管类型不包含任何托管字段,可自由复制
非托管类型可参考枚举值 UnmanagedType
IsValidNativeContainerElementType 判断某种类型是否可以做为 NativeContainer 的元素
Malloc 分配内存。
MemClear 清除内存。
MemCmp 通过将第一个给定内存缓冲区中的指定内存区域与第二个给定内存缓冲区中的相同区域进行比较,检查两个内存区域是否相同。
MemCpy 复制内存,如果源和目标重叠,不保证结果正确,此时应改用 MemMove
MemCpyReplicate 复制内存,顺序复制
MemCpyStride 与 MemCpy 类似,但可以通过 desinationStride 和 sourceStride 跳过字节
MemMove 移动内存。
MemSet 用某个值填充内存
PinGCArrayAndGetDataAddress 保持对该对象的强 GC 引用并将其固定。保证对象在移动 GC 中的内存位置不会移动。
返回数组第一个元素的地址。另请参阅:UnsafeUtility.ReleaseGCObject。
PinGCObjectAndGetAddress 保持对该对象的强 GC 引用并将其固定。保证对象在移动 GC 中的内存位置不会移动。
返回该对象的内存位置地址。另请参阅:UnsafeUtility.ReleaseGCObject。
ReleaseGCObject 释放之前由 UnsafeUtility.PinGCObjectAndGetAddress 获取的 GC 对象句柄。
SizeOf 结构的大小。
WriteArrayElement 写入数组元素。
WriteArrayElementWithStride 使用步幅写入数组元素。 -
NativeArray
代表非托管内存中的数组,内部调用 UnsafeUtility
本身不提供获取内存指针的函数,但可以通过 NativeArrayUnsafeUtility 直接操纵内存
IsCreated 指示 NativeArray 有一个已分配的内存缓冲区。
Length ativeArray 中元素的数量。
this[int] 按索引访问 NativeArray 元素。请注意,结构是按值而非引用返回的。NativeArray 构造函数,Allocator参考上面的说明 CopyFrom 从长度相同的另一个 NativeArray 或托管数组中复制所有元素。 CopyTo 将所有元素复制到长度相同的另一个 NativeArray 或托管数组。 Dispose GetEnumerator 获取枚举器。 GetSubArray 获得指定起始位置开始的指定长度的数组,引用原来的数组中的数据块,不能主动删除 Reinterpret 转成别的元素类型的数组,引用原数组的数据块,不能主动删除 ReinterpretLoad 获得把某个索引处的元素并转成别的类型 ReinterpretStore 用别的类型的值设置某个索引处的元素 ToArray 将 NativeArray 转换为托管数组。 静态函数 Copy 将一系列元素从源数组复制到目标数组,从源索引开始将它们复制到目标索引。 常用代码 // 分配内存空间 NativeArray<byte> array = new NativeArray<byte>(1000, Allocator.Temp); // 回收内存 array.Dispose();
-
NativeArrayUnsafeUtility
NativeArray 工具类,可以直接操纵内部指针
ConvertExistingDataToNativeArray 将现有缓冲区转换为 NativeArray,传入 Allocator.None 时表示NativeArray直接使用传入的内存,不再自行分配
GetUnsafeBufferPointerWithoutChecks 通过 NativeArray 获取指向数据所有者的指针,但不执行检查。
GetUnsafePtr 通过 NativeArray 获取指向内存缓冲区所有者的指针,执行是否可以写入原生数组的检查。
GetUnsafeReadOnlyPtr 通过 NativeArray 获取指向内存缓冲区所有者的指针,执行是否可以读取原生数组的检查。 -
NativeSlice
数组切片,就是代表 NativeArray 中的一段元素,本身并不管理内存,必须先有一个 NativeArray 来管理内存,然后才能基于该数组创建切片
本机切片。扫描二维码关注公众号,回复: 17428488 查看本文章变量 Length 切片中元素的数量。 Stride 返回为切片设置的步幅。 this[int] 按索引访问 NativeSlice 元素。请注意,结构是按值而非引用返回的。 公共函数 CopyFrom 从长度相同的一个 NativeSlice 或托管数组中复制所有元素。 CopyTo 将切片的所有元素都复制到长度相同的一个 NativeArray 或托管数组。 GetEnumerator GetEnumerator。 SliceConvert 转成别的类型的切片 SliceWithStride 转成别的类型的切片并指定步幅 ToArray 将 NativeSlice 转换为数组。 运算符 NativeSlice<T> 用于从 NativeArray 中创建 NativeSlice 的隐式运算符。
-
NativeSliceUnsafeUtility
NativeSlice 工具类,用来直接操纵非托管内存
ConvertExistingDataToNativeSlice ConvertExistingDataToNativeSlice。
GetUnsafePtr 获取 NativeSlice 内存缓冲区指针。检查是否可以向本机数组写入数据。
GetUnsafeReadOnlyPtr 获取 NativeSlice 内存缓冲区指针。检查是否可以从本机数组中读取数据。 -
NativeLeakDetection
静态变量
Mode 设置是应启用还是禁用本机内存泄漏检测。
常用代码
-
IntPtr 和 void * 的互转
void *p; IntPtr ip = new IntPtr(p); // void * 转成 IntPtr,构造函数支持,因此可以强转 IntPtr ip = (IntPtr)p; p = ip.ToPointer(); // IntPtr 转成 void *
-
IntPtr 和 NativeArray 互转
unsafe { // 由于该 array 的内存由外部控制,因此不需要调用 array.Dispose() NativeArray<byte> array = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray<byte>(intPtr.ToPointer(), len, Unity.Collections.Allocator.None); // 要获得只读可用 GetReadonlyUnsafePtr() IntPtr ip = (IntPtr)array.GetUnsafePtr(); }
-
byte[] 临时转成 NativeArray
- 如果是用完马上释放,可以用 fixed
unsafe { fixed ( byte * p = byteArray ) { // 由于该 array 的内存由外部控制,因此不需要调用 array.Dispose() NativeArray<byte> array = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray<byte>(p, byteArray.Length, Unity.Collections.Allocator.None); } }
- 如果要过会才释放,比如用于 Job,先用 GCHandle 获得固定指针
unsafe { GCHandle gCHandle = GCHandle.Alloc(byteArray, GCHandleType.Pinned); IntPtr intPtr = gCHandle.AddrOfPinnedObject(); // 由于该 array 的内存由外部控制,因此不需要调用 array.Dispose() NativeArray<byte> array = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray<byte>(intPtr.ToPointer(), byteArray.Length, Unity.Collections.Allocator.None); // 用完要释放 gCHandle.Free(); }
- 如果是用完马上释放,可以用 fixed
-
使用 NativeArray 分配内存的几种情况
Allocator 参考上面说明,可以看出 NativeArray 主要还是用来管理自己分配的内存,外部内存还是直接用 IntPtr 就可以了- 内部开辟一块内存,在不需要的时候需要手动调用 array.Dispose() 进行释放
// 指定内存大小 NativeArray<byte> array = new NativeArray<byte>(length, Allocator.Persistent); // 使用已有的 byteArray ,会进行复制 NativeArray<byte> array = new NativeArray<byte>(byteArray, Allocator.Persistent); // 使用已有的 nativeArray,会进行复制 NativeArray<byte> array = new NativeArray<byte>(nativeArray, Allocator.Persistent);
- 使用外部内存,不需要也不能调用 array.Dispose() 进行释放
unsafe { // 使用外部内存创建 NativeArray,最后一个参数只能是 None 或 Invalid NativeArray<byte> array = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray<byte>(intPtr.ToPointer(), len, Unity.Collections.Allocator.None); // 获得子数组,并没有复制内存,直接指向原来数组,也不能调用 Dispose(),当原数组释放后,子数组也变得无效 NativeArray<byte> array = nativeArray.GetSubArray(0,100); }
- 内部开辟一块内存,在不需要的时候需要手动调用 array.Dispose() 进行释放
-
分配非托管内存的几种方式
IntPtr p= Marshal.AllocHGlobal(lenght); // 使用 Marshal 分配 Marshal.FreeHGlobal(p); // 释放 void *p = UnsafeUtility.Malloc(UnsafeUtility.SizeOf<T>()*1000,UnsafeUtility.SizeOf<T>(),Allocator.Temp); // 使用 UnsafeUtility 分配 UnsafeUtility.Free(p); NativeArray<byte> array = new NativeArray<byte>(length, Allocator.Persistent); // 使用 NativeArray 分配,内部调用 UnsafeUtility.Malloc array.Dispose(); Texture2D texture = new Texture2D(outputWidth, outputHeight, TextureFormat.ARGB32, false); // 通过 Texture2D 间接分配内存 NativeArray<byte> data = texture.GetRawTextureData<byte>(); // 获得内存,内部通过 NativeArrayUnsafeUtility 实现 byte* ptr = stackalloc byte[16384]; // 直接在栈上分配内存,分配的内存块未定义,需要手动初始化,不需要手动释放,当函数返回时自动释放 Span<int> numbers = stackalloc[] { 1, 2, 3, 4, 5, 6 }; // 建议使用 Span 来操纵栈上的内存,可以调用 Clear 来初始化所有值
-
操作非托管内存的几种方式
Marshal.WriteInt32(p,10,11111); // Marshal 直接操纵 int data = UnsafeUtility.ReadArrayElement<int>(p,10); // 通过 UnsafeUtility 直接操纵 NativeArray<int> array = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray<int>(p, psize / UnsafeUtility.SizeOf<int>(), Allocator.None); // 转成 NativeArray 操作 array[10] = 10; // NativeArray 底层也是通过 UnsafeUtility 进行操作 NativeArray<byte> data = texture.GetRawTextureData<byte>(); // 纹理数据也可以转成 NativeArray 进行操作
-
Texture2D 常用操作
texture.LoadRawTextureData(intPtr,width*height*4); // 用非托管内存数据创建纹理(个人理解应该是在 c 层面拷贝了一份数据) NativeArray<byte> data = texture.GetRawTextureData<byte>(); // 获得非托管内存 IntPtr intPtr = (IntPtr)data.GetUnsafeReadOnlyPtr(); // 获得 NativeArray 的内存,进行只读校验(好像只读也可以往里拷贝数据) // 注意这跟 texture.GetNativeTexturePtr 是不一样的,后者是获得纹理指针,(D3D9 上为 IDirect3DBaseTexture9)
示例代码
// opencv 中快速把 Texture2D 转成 Mat
public static void fastTexture2DToMat(Texture2D texture2D, Mat mat)
{
Debug.Assert(mat.isContinuous()); // 必须是连续存放的
Debug.Assert(texture2D.mipmapCount==1); // 不能有层级图
#if OPENCV_USE_UNSAFE_CODE && UNITY_2018_2_OR_NEWER
unsafe
{
OpenCVForUnity_ByteArrayToMatData((IntPtr)NativeArrayUnsafeUtility.GetUnsafeReadOnlyPtr(texture2D.GetRawTextureData<byte>()), mat.nativeObj);
}
#else
GCHandle arrayHandle = GCHandle.Alloc(texture2D.GetRawTextureData(), GCHandleType.Pinned);
OpenCVForUnity_ByteArrayToMatData(arrayHandle.AddrOfPinnedObject(), mat.nativeObj);
arrayHandle.Free();
#endif
}
// opencv 中快速把 Mat 转成 Texture2D
public static void fastMatToTexture2D(Mat mat, Texture2D texture2D)
{
Debug.Assert(mat.isContinuous()); // 必须是连续存放的
Debug.Assert(texture2D.mipmapCount==1); // 不能有层级图
#if OPENCV_USE_UNSAFE_CODE && UNITY_2018_2_OR_NEWER
unsafe
{
OpenCVForUnity_MatDataToByteArray(mat.nativeObj, (IntPtr)NativeArrayUnsafeUtility.GetUnsafeReadOnlyPtr(texture2D.GetRawTextureData<byte>()));
}
texture2D.Apply(updateMipmaps, makeNoLongerReadable);
#else
texture2D.LoadRawTextureData((IntPtr)mat.dataAddr(), (int)mat.total() * (int)mat.elemSize());
texture2D.Apply(updateMipmaps, makeNoLongerReadable);
#endif
}
// 直接操作 NativeArray 内存,经过测试发现比直接遍历 NativeArray 速度快一倍
NativeArray<int> array = new NativeArray<int>(100000, Allocator.Temp, NativeArrayOptions.UninitializedMemory);
unsafe
{
int* p = (int *)NativeArrayUnsafeUtility.GetUnsafeReadOnlyPtr<int>(array);
for ( int i=0; i< array.Length; i++ )
{
p[i] = i;
}
}