unity常用代码Native

unity常用代码Native

参考 c#指针IntPtr
参考 c#调用c++

主要用到的类有

  • Allocator
    内存分配枚举值,说明要分配什么样的内存
    Invalid
    无效分配。
    None
    未分配,当使用NativeArrayUnsafeUtility用非托管内存指针创建 NativeArray 时可以传入 None ,让 NativeArray 直接使用传入的内存
    Temp
    最快的分配方法,适用于一帧内的生命时长,不能将该类型分配的数据传给 Job,用完调用Dispose
    比如在一个同步函数的开始分配内存,在函数结束前调用 Dispose

    TempJob
    	分配速度比 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个作用

    1. 调用 AtomicSafetyHandle.Release , 把 AtomicSafetyHandle 置成已释放状态
    2. 在析构函数中检查是否处于释放状态,否则提示内存泄漏
  • 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种处理方式

    1. 使用变量的地方在定义变量时添加 [NativeDisableUnsafePtrRestriction] 属性

    2. 自己设置 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);
      
    3. 使用临时的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 来管理内存,然后才能基于该数组创建切片
    本机切片。

    变量
    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();
          }
      
  • 使用 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);
          }
      
  • 分配非托管内存的几种方式

    	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;
		}
	}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值