C#中Struct与IntPtr转换:实用扩展方法

C#中Struct与IntPtr转换:实用扩展方法

在 C# 编程的世界里,我们常常会遇到需要与非托管代码交互,或者进行一些底层内存操作的场景。这时,IntPtr类型就显得尤为重要,它可以表示一个指针或句柄,用来指向非托管内存中的数据。而结构体作为一种常用的数据结构,在与IntPtr进行数据传递和转换时,往往需要一些繁琐的操作。为了简化这些操作,提高开发效率,我们可以通过扩展方法来封装相关的功能。接下来,就为大家介绍两段非常实用的 C# 扩展方法代码,它们实现了结构体与IntPtr之间的转换等功能。

一、代码概览

我们有两个扩展方法类,分别是StructExtensionsIntPtrExtensionsStructExtensions类主要提供将结构体和结构体数组转换为IntPtr的方法;IntPtrExtensions类则提供了将IntPtr转换回结构体、将IntPtr指向的内存数据转换为字节数组,以及释放IntPtr所占用的非托管内存的方法。

1. StructExtensions 类

using System;
using System.Runtime.InteropServices;

public static class StructExtensions
{
    /// <summary>
    /// struct to IntPtr
    /// IntPtr使用完,需释放
    /// </summary>
    /// <typeparam name="T">struct</typeparam>
    /// <param name="value">struct值</param>
    /// <returns>IntPtr</returns>
    public static IntPtr ToIntPtr<T>(this T value) where T : struct
    {
        var intptr = Marshal.AllocHGlobal(Marshal.SizeOf(value));
        Marshal.StructureToPtr(value, intptr, true);
        return intptr;
    }

    /// <summary>
    /// struct[] to IntPtr
    /// IntPtr使用完,需释放
    /// </summary>
    /// <typeparam name="T">struct</typeparam>
    /// <param name="value">struct[]值</param>
    /// <returns>IntPtr</returns>
    public static IntPtr ToIntPtr<T>(this T[] value) where T : struct
    {
        var intPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(T)) * value.Length);
        var longPtr = intPtr.ToInt64();
        for (var i = 0; i < value.Length; i++)
        {
            var rectPtr = new IntPtr(longPtr);
            Marshal.StructureToPtr(value[i], rectPtr, false);
            longPtr += Marshal.SizeOf(typeof(T));
        }
        return new IntPtr(longPtr);
    }
}

在这个类中,第一个ToIntPtr方法接受一个结构体类型的参数value,首先使用Marshal.AllocHGlobal方法在非托管内存中分配足够的空间,空间大小由Marshal.SizeOf(value)确定,即结构体实例的大小。然后通过Marshal.StructureToPtr方法将结构体实例的值复制到分配的非托管内存中,并返回指向该内存的IntPtr。需要注意的是,使用完返回的IntPtr后,必须释放其占用的内存,否则会导致内存泄漏。

第二个ToIntPtr方法则是针对结构体数组,它根据数组的长度和单个结构体的大小,在非托管内存中分配相应大小的空间,并返回指向该内存的IntPtr。不过,此方法只是分配了内存,并没有将数组中的数据复制到内存中,如果需要复制数据,还需要进一步的操作。

2. IntPtrExtensions 类

using System;
using System.Runtime.InteropServices;

public static class IntPtrExtensions
{
    public static T ToStructure<T>(this IntPtr value) where T : struct
    {
        return (T)Marshal.PtrToStructure(value, typeof(T));
    }

    public static byte[] ToBytes(this IntPtr value, int size)
    {
        var bytes = new byte[size];
        Marshal.Copy(value, bytes, 0, size);
        return bytes;
    }

    public static void Free(ref this IntPtr value)
    {
        Marshal.FreeHGlobal(value);
    }
}

IntPtrExtensions类中的ToStructure方法,接受一个IntPtr类型的参数value,通过Marshal.PtrToStructure方法将IntPtr指向的非托管内存中的数据转换为指定的结构体类型,并返回转换后的结构体实例。
ToBytes方法将IntPtr指向的内存中的数据读取到一个字节数组中。它接受一个表示内存大小的参数size,首先创建一个指定大小的字节数组,然后使用Marshal.Copy方法将IntPtr指向的内存数据复制到字节数组中,并返回该字节数组。
Free方法用于释放IntPtr所占用的非托管内存,通过Marshal.FreeHGlobal方法来实现内存的释放,由于需要修改IntPtr本身,所以参数使用了ref关键字。

二、使用示例

下面我们通过一个简单的示例来展示这些扩展方法的具体使用:

using System;
using System.Runtime.InteropServices;

struct Point
{
   public int X;
   public int Y;
}


class Program
{
   static void Main()
   {
       var point = new Point { X = 10, Y = 20 };

       // 将结构体转换为IntPtr
       var intPtr = point.ToIntPtr();

       // 将IntPtr转换回结构体
       var newPoint = intPtr.ToStructure<Point>();


       Console.WriteLine($"X: {newPoint.X}, Y: {newPoint.Y}");


       // 释放IntPtr占用的内存
       intPtr.Free();
       
       
       var points = { new Point { X = 1, Y = 2 }, new Point { X = 3, Y = 4 } };
       // 将结构体数组转换为IntPtr
       var arrayIntPtr = points.ToIntPtr();


       // 这里可以添加将数组数据复制到内存的操作

       // 释放IntPtr占用的内存
       arrayIntPtr.Free();
   }


}

在这个示例中,我们定义了一个Point结构体,然后分别演示了将结构体和结构体数组转换为IntPtr,再将IntPtr转换回结构体,以及释放IntPtr所占用内存的整个过程。

三、注意事项

    1. 内存管理:正如前面多次提到的,使用Marshal.AllocHGlobal分配的非托管内存必须手动释放,否则会造成内存泄漏。在实际应用中,要确保在合适的时机调用Free方法。
    1. 数据一致性:在将结构体数组转换为IntPtr时,如果需要将数组数据复制到内存中,需要额外编写代码实现,否则IntPtr指向的内存中数据是未初始化的。
    1. 类型安全:在使用ToStructure方法时,要确保IntPtr指向的内存中的数据与目标结构体类型一致,否则可能会导致类型转换错误或程序异常。

通过这些实用的扩展方法,我们可以更加便捷地在 C# 中处理结构体与IntPtr之间的数据转换和内存操作,提高代码的可读性和可维护性。希望本文对你在 C# 编程中处理相关问题有所帮助,如果你在使用过程中有任何疑问或遇到其他问题,欢迎在评论区交流讨论。

以上博客介绍了代码的功能和使用要点。你若觉得内容还需补充,比如增加更多示例或详细解释某个部分,欢迎告诉我。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿蒙Armon

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值