c#指针IntPtr
主要类
-
IntPtr
- 代表c++指针,可以指向任何对象,取决于你创建时赋的值
任何c++指针,在C#这边都用 IntPtr 表示,比如void TrimImage(unsigned char *imageData, void **trimedImageData);
c# 这边跟 IntPtr 相关的操作主要有 GCHandle(获得指针) , Marshal(操作指针)void TrimImage(IntPtr imageData, ref IntPtr trimedImageData);
- 代表c++指针,可以指向任何对象,取决于你创建时赋的值
-
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(); }