c#指针IntPtr

c#指针IntPtr

主要类

  • IntPtr

    • 代表c++指针,可以指向任何对象,取决于你创建时赋的值
      任何c++指针,在C#这边都用 IntPtr 表示,比如
          void TrimImage(unsigned char *imageData, void **trimedImageData);
      
          void TrimImage(IntPtr imageData, ref IntPtr trimedImageData);
      
      c# 这边跟 IntPtr 相关的操作主要有 GCHandle(获得指针) , Marshal(操作指针)
  • GCHandle
    代表c#对象的引用,最常用的就是获得对象指针,或把对象转成指针

    • Alloc
      把c#对象转成 GCHandle, GCHandleType.Pinned 表示把对象固定住,不会被垃圾收集器移动

    • AddrOfPinnedObject
      获得c#对象的c++指针,这个是真的c++指针,可以传给c++函数直接操纵处理
      要求gchandle必须是通过 GCHandleType.Pinned 分配的,并且对象布局必须是 restricted type-layout

      • 用法:
        	T[] array = new T[10];
            //	托管对象地址是会被GC收集器改变的,因此需要先定住(可以理解成移动到非托管内存)
        	GCHandle gCHandle = GCHandle.Alloc(array, GCHandleType.Pinned);	//	获得 gcHandle,必须用 GCHandleType.Pinned
        	IntPtr intPtr = gCHandle.AddrOfPinnedObject();					//	获得 c++指针
        	CallCFunc(intPtr);		//	传给 c++ 使用,可以使用 UnsafeUtility.MemCpy 或 Marshal.Copy 之类的函数操作非托管内存
        	gcHandle.Free();		//	释放 gcHandle,重新被GC管理
        
    • ToIntPtr
      获得c#对象的c++指针,这个不是真的指针,只是一个唯一标识,按指针来保存而已,不能在c++中直接操纵该指针
      该指针的目的就是让c#对象可以做为c++参数进行传递,最后传回给c#代码通过 GCHandle.FromIntPtr ,
      重新获得 gcHandle 来操纵c#对象,GCHandle.Target 直接就是构造 GCHandle 时的 c# 对象

      	GCHandle gCHandle = GCHandle.Alloc(obj);				//	获得 gcHandle
      	IntPtr intPtr = GCHandle.ToIntPtr(gCHandle);			//	获得 c++指针
      	CallCFunc(intPtr)		//	传给 c++ 使用
      	{
      		CallC#Func(intPtr)	//	c++是不能直接操纵指针的,只能回传给 c#
      		{
      			T obj = GCHandle.FromIntPtr(intPtr).Target as T;	//	c#从指针获取c#对象
      		}
      	}
      	gCHandle.Free();		//	释放 gcHandle
      
    • FromIntPtr
      跟 ToIntPtr 配对

    • Free
      跟 Alloc 配对

  • System.Runtime.InteropServices.Marshal
    操作内存空间,比如在托管和非托管内存之间拷贝

    • 参考
      https://docs.microsoft.com/zh-cn/dotnet/api/system.runtime.interopservices.marshal?view=net-6.0
      https://docs.microsoft.com/zh-cn/dotnet/standard/native-interop/type-marshalling
      https://docs.microsoft.com/zh-cn/dotnet/framework/interop/marshalling-classes-structures-and-unions

    • AllocHGlobal(Int32)
      通过使用指定的字节数,从进程的非托管内存中分配内存。

    • AllocHGlobal(IntPtr)
      通过使用指向指定字节数的指针,从进程的非托管内存中分配内存。

    • FreeHGlobal(IntPtr)
      跟 AllocHGlobal 配对

    • Copy
      在托管和非托管内存中复制,你必须确保 IntPtr 指向的内存是正确的

    • Copy(Byte[], Int32, IntPtr, Int32)
      将数据从一维托管 8 位无符号整数数组复制到非托管内存指针。

    • PtrToStringAnsi(IntPtr, Int32)
      分配托管 String,然后从非托管 ANSI 或 UTF-8 字符串向其复制指定数目的字符,并将每个字符扩展为 UTF-16 字符。

    • StringToHGlobalAnsi(String)
      将托管 String 的内容复制到非托管内存,并在复制时转换为 ANSI 格式。

    • PtrToStringUni(IntPtr, Int32)
      分配托管 String,并从非托的 Unicode 字符串向其复制指定数目的字符。

    • StringToHGlobalUni(String)
      将托管 String 的内容复制到非托管内存。

    • PtrToStringUTF8(IntPtr, Int32)
      分配托管的 String,并从非托管的 UTF8 字符串向其复制指定数目的字符。

    • PtrToStructure
      将数据从非托管内存块封送到新分配的指定类型的托管对象。
      PtrToStructure(IntPtr, Type)
      PtrToStructure(IntPtr, T)

    • StructureToPtr(T, IntPtr, Boolean)
      将数据从指定类型的托管对象封送到非托管内存块。

    • ReadByte(IntPtr, Int32)
      从非托管内存按给定的偏移量(或索引)读取单个字节。

    • WriteByte(IntPtr, Byte)
      将单个字节值写入到非托管内存。

    • ReadInt16(IntPtr, Int32)
      从非托管内存按给定的偏移量读取一个 16 位带符号整数。

    • WriteInt16(IntPtr, Int32, Int16)
      按指定偏移量将 16 位带符号整数值写入非托管内存。

    • ReadInt32(IntPtr, Int32)
      从非托管内存按给定的偏移量读取一个 32 位带符号整数。

    • WriteInt32(IntPtr, Int32, Int32)
      按指定偏移量将 32 位带符号整数值写入非托管内存。

    • ReadIntPtr(IntPtr, Int32)
      从非托管内存按给定的偏移量读取处理器本机大小的整数。

    • WriteIntPtr(IntPtr, Int32, IntPtr)
      按指定的偏移量将一个处理器本机大小的整数值写入非托管内存。

    • SizeOf(Object)
      返回对象的非托管大小(以字节为单位)。

    • SizeOf(Type)
      返回非托管类型的大小(以字节为单位)。

    • IntPtr UnsafeAddrOfPinnedArrayElement(T[] arr, int index)
      获得数组某个元素的地址,必须先使用 GCHandle.Alloc(arr, GCHandleType.Pinned); 固定数组

    • System.Runtime.CompilerServices.Unsafe

指针跟其它类型转换

  • 跟 int 互转
    	int i=1; 
    	IntPtr p=new IntPtr(i);  
    	int ch_i=(int) p;
    

C#中获得指针

  • 分配内存

        //  分配空白内存
        IntPtr ptr = Marshal.AllocHGlobal(10000);       //  分配内存,返回指针
        Marshal.FreeHGlobal(ptr);                       //  释放内存
    
        //  分配字符串
    	string str="a";
    	IntPtr p=Marshal.StringToHGlobalAnsi(str);		//	存字符串,按多字节编码
    	string s=Marshal.PtrToStringAnsi(p);			//	取字符串,按多字节编码
    	Marshal.FreeHGlobal(p);							//	释放空间
    
  • 把托管对象转成指针
    参考上面 GCHandle.AddrOfPinnedObject 和 GCHandle.ToIntPtr

  • 使用fixed关键字
    参考 c#语法
    fixed 和 GCHandle.AddrOfPinnedObject 的比较:
    相同点:
    2者都能返回对象的地址,且结果是一样的
    不同点:
    AddrOfPinnedObject 重点是固定指针,会告诉GC不要移动,由于是手动释放,因此可以固定任意时间
    fixed 语句只在块内固定变量,编译器会生成代码告诉GC固定某个本地变量,GC本来就需要遍历本地变量,
    因为这些变量都是激活状态不能被释放,遍历的同时检查该变量是否固定,效率比 AddrOfPinnedObject 高得多
    但由于离开函数后,固定本地变量就失效,因此地址不能做为函数返回值
    总结:
    只在函数中完成,优先使用 fixed,效率高,如果指针不是立马用完,则需要 AddrOfPinnedObject

C#对象跟指针互相拷贝

  • 结构体拷贝

        //  分配结构体
        Student stu = new Student();
    	Marshal.StructureToPtr(stu, intPtr,true);		//	结构体拷贝到指针
    	stu = (Student)Marshal.PtrToStructure(intPtr, typeof(stuInfo));	//	指针拷贝到结构体
    
  • byte数组拷贝

        static void Copy(byte[] data, IntPtr ptr)
        {
            Marshal.Copy(data, 0, ptr, data.Length);    //  byte[]拷贝到指针
            Marshal.Copy(ptr, 0, data, data.Length);    //  指针拷贝到 byte[]
        } 
    

C#中操作指针

  • 使用unsafe代码操作指针
        public static void TestData(IntPtr data)
        {
            unsafe
            {
                int *p = (int *)data;
                int *temp = new int[100];
                for (int i = 0; i < 100; i++)
                    *(temp+i) = *(p+i);
                delete[] temp;
            }
        }
    

常用代码

  • 数组拷贝
    	public unsafe static void Copy(T[] src, int srcIndex, NativeArray<T> dst, int dstIndex, int length)
    	{
    		GCHandle gCHandle = GCHandle.Alloc(src, GCHandleType.Pinned);
    		IntPtr intPtr = gCHandle.AddrOfPinnedObject();
    		UnsafeUtility.MemCpy((byte*)dst.m_Buffer + dstIndex * UnsafeUtility.SizeOf<T>(), (byte*)(void*)intPtr + srcIndex * UnsafeUtility.SizeOf<T>(), length * UnsafeUtility.SizeOf<T>());
    		gCHandle.Free();
    	}
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值