C# 托管代码与非托管代码之间的交互 Marshal ,GCHandle

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 时要谨慎,以避免潜在的性能问题或内存泄漏。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值