1,Marshal
1. 内存管理
分配和释放非托管内存
AllocHGlobal 和 FreeHGlobal
IntPtr unmanagedMemory = Marshal.AllocHGlobal(1024); // 分配1024字节的非托管内存
try {
// 使用分配的非托管内存...
} finally {
Marshal.FreeHGlobal(unmanagedMemory); // 释放非托管内存
}
重新调整已分配的非托管内存大小
ReAllocHGlobal
IntPtr newUnmanagedMemory = Marshal.ReAllocHGlobal(unmanagedMemory, new IntPtr(2048)); // 调整为2048字节
2. 数据类型转换
结构体与非托管指针之间转换
StructureToPtr 和 PtrToStructure
// 定义结构体
[StructLayout(LayoutKind.Sequential)]
struct MyStruct {
public int x;
public int y;
}
MyStruct structInstance = new MyStruct { x = 1, y = 2 };
IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf<MyStruct>());
try {
Marshal.StructureToPtr(structInstance, ptr, false); // 序列化到非托管内存
MyStruct copy = (MyStruct)Marshal.PtrToStructure(ptr, typeof(MyStruct)); // 反序列化回托管对象
} finally {
Marshal.FreeHGlobal(ptr);
}
3. 字符串操作
托管字符串与非托管字符串之间的转换
StringToHGlobalAnsi、StringToHGlobalUni 和 PtrToStringAnsi、PtrToStringUni
string managedString = "Hello, World!";
IntPtr unmanagedString = Marshal.StringToHGlobalAnsi(managedString);
try {
string copiedString = Marshal.PtrToStringAnsi(unmanagedString);
} finally {
Marshal.FreeHGlobal(unmanagedString);
}
4. 获取和设置非托管对象的状态
释放COM对象
ReleaseComObject
object comObject = /* ... */;
int remainingRefs = Marshal.ReleaseComObject(comObject); // 减少引用计数
5. 回调函数和委托
非托管函数指针与托管委托之间的转换
GetDelegateForFunctionPointer 和 GetFunctionPointerForDelegate
// 假设有一个非托管函数指针类型的定义
delegate void UnmanagedFunctionDelegate(int a, int b);
// 将托管委托转换为非托管函数指针
UnmanagedFunctionDelegate del = (a, b) => Console.WriteLine(a + b);
IntPtr functionPtr = Marshal.GetFunctionPointerForDelegate(del);
// 将非托管函数指针转换为托管委托
UnmanagedFunctionDelegate newDel = (UnmanagedFunctionDelegate)Marshal.GetDelegateForFunctionPointer(functionPtr, typeof(UnmanagedFunctionDelegate));
6. 其他功能
获取类型的大小
SizeOf
int size = Marshal.SizeOf(typeof(MyStruct));
获取字段偏移量
OffsetOf
int offset = Marshal.OffsetOf(typeof(MyStruct), "x").ToInt32();
Marshal.Copy 的用法
Marshal.Copy 方法用于在托管数组和非托管内存块之间复制数据。它有几种重载形式,下面是两个常见的使用场景:
从非托管内存复制到托管数组
byte[] managedArray = new byte[1024];
IntPtr unmanagedMemory = Marshal.AllocHGlobal(1024);
try {
// 初始化非托管内存...
// 复制非托管内存到托管数组
Marshal.Copy(unmanagedMemory, managedArray, 0, 1024);
} finally {
Marshal.FreeHGlobal(unmanagedMemory);
}
从托管数组复制到非托管内存
byte[] managedArray = new byte[1024];
IntPtr unmanagedMemory = Marshal.AllocHGlobal(1024);
try {
// 初始化托管数组...
// 复制托管数组到非托管内存
Marshal.Copy(managedArray, 0, unmanagedMemory, 1024);
} finally {
Marshal.FreeHGlobal(unmanagedMemory);
}
以上是 Marshal 类的一些主要用法以及 Marshal.Copy 的具体示例。请注意,直接操作非托管代码时要格外小心,确保正确管理资源以避免内存泄漏和其他潜在问题。
GCHandle
GCHandle 类位于 System.Runtime.InteropServices 命名空间下,它提供了一种机制来阻止垃圾回收器回收特定的托管对象。这在需要确保对象在非托管代码中仍然可用时非常有用。以下是 GCHandle 的主要用法,并附有相应的例子。
1. 阻止垃圾回收
创建和释放句柄
Alloc 和 Free
object obj = new object();
GCHandle handle = GCHandle.Alloc(obj, GCHandleType.Normal); // 分配一个普通类型的句柄
try {
// 在这里可以安全地使用obj,因为它不会被GC回收
} finally {
if (handle.IsAllocated) {
handle.Free(); // 释放句柄,允许对象再次被回收
}
}
2. 强制固定对象(Pinning)
当您需要将托管对象的地址传递给非托管代码时,您可以使用 GCHandleType.Pinned 来分配句柄,这会防止对象在垃圾回收期间移动。
byte[] byteArray = new byte[1024];
GCHandle pinnedHandle = GCHandle.Alloc(byteArray, GCHandleType.Pinned);
try {
IntPtr pointer = pinnedHandle.AddrOfPinnedObject(); // 获取固定的数组地址
// 使用指针调用非托管代码...
} finally {
if (pinnedHandle.IsAllocated) {
pinnedHandle.Free(); // 释放句柄,取消固定
}
}
3. 检查句柄是否已分配
IsAllocated
if (handle.IsAllocated) {
Console.WriteLine("The GCHandle is allocated.");
} else {
Console.WriteLine("The GCHandle is not allocated.");
}
4. 获取目标对象
Target
object originalObj = new object();
GCHandle handle = GCHandle.Alloc(originalObj, GCHandleType.Normal);
try {
object retrievedObj = handle.Target; // 获取与句柄关联的对象
} finally {
if (handle.IsAllocated) {
handle.Free();
}
}
5. 设置目标对象
Target
object originalObj = new object();
GCHandle handle = GCHandle.Alloc(originalObj, GCHandleType.Normal);
try {
handle.Target = new object(); // 更换与句柄关联的对象
} finally {
if (handle.IsAllocated) {
handle.Free();
}
}
注意事项
避免长时间固定大量对象:因为固定对象会阻止垃圾收集器优化内存布局,所以应该尽量减少固定的时间和对象数量。
及时释放句柄:一旦不再需要 GCHandle,应立即调用 Free 方法释放它,以允许垃圾收集器回收对象。
通过上述方法,GCHandle 提供了对托管对象生命周期的更精细控制,尤其是在与非托管代码交互或处理需要稳定地址的场景下。然而,使用 GCHandle 时要谨慎,以避免潜在的性能问题或内存泄漏。