C#中Struct與IntPtr轉換:實用擴展方法
在 C# 編程的世界里,我們常常會遇到需要與非托管代碼交互,或者進行一些底層內存操作的場景。這時,IntPtr
類型就顯得尤為重要,它可以表示一個指針或句柄,用來指向非托管內存中的數據。而結構體作為一種常用的數據結構,在與IntPtr
進行數據傳遞和轉換時,往往需要一些繁瑣的操作。為了簡化這些操作,提高開發效率,我們可以通過擴展方法來封裝相關的功能。接下來,就為大家介紹兩段非常實用的 C# 擴展方法代碼,它們實現了結構體與IntPtr
之間的轉換等功能。
一、代碼概覽
我們有兩個擴展方法類,分別是StructExtensions
和IntPtrExtensions
。StructExtensions
類主要提供將結構體和結構體數組轉換為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 };// 將結構體轉換為IntPtrvar 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 } };// 將結構體數組轉換為IntPtrvar arrayIntPtr = points.ToIntPtr();// 這里可以添加將數組數據復制到內存的操作// 釋放IntPtr占用的內存arrayIntPtr.Free();}}
在這個示例中,我們定義了一個Point
結構體,然后分別演示了將結構體和結構體數組轉換為IntPtr
,再將IntPtr
轉換回結構體,以及釋放IntPtr
所占用內存的整個過程。
三、注意事項
-
- 內存管理:正如前面多次提到的,使用
Marshal.AllocHGlobal
分配的非托管內存必須手動釋放,否則會造成內存泄漏。在實際應用中,要確保在合適的時機調用Free
方法。
- 內存管理:正如前面多次提到的,使用
-
- 數據一致性:在將結構體數組轉換為
IntPtr
時,如果需要將數組數據復制到內存中,需要額外編寫代碼實現,否則IntPtr
指向的內存中數據是未初始化的。
- 數據一致性:在將結構體數組轉換為
-
- 類型安全:在使用
ToStructure
方法時,要確保IntPtr
指向的內存中的數據與目標結構體類型一致,否則可能會導致類型轉換錯誤或程序異常。
- 類型安全:在使用
通過這些實用的擴展方法,我們可以更加便捷地在 C# 中處理結構體與IntPtr
之間的數據轉換和內存操作,提高代碼的可讀性和可維護性。希望本文對你在 C# 編程中處理相關問題有所幫助,如果你在使用過程中有任何疑問或遇到其他問題,歡迎在評論區交流討論。
以上博客介紹了代碼的功能和使用要點。你若覺得內容還需補充,比如增加更多示例或詳細解釋某個部分,歡迎告訴我。