C# struct 的使用
参数说明
- ·LayoutKind
LayoutKind 值之一,它指定类或结构的排列方式。
Sequential 对象的成员按照它们在被导出到非托管内存时出现的顺序依次布局。这些成员根据在StructLayoutAttribute.Pack中指定的封装进行布局,并且可以是不连续的。
Explicit 对象的各个成员在非托管内存中的精确位置被显式控制。每个成员必须使用 FieldOffsetAttribute 指示该字段在类型中的位置。
Auto 运行库自动为非托管内存中的对象的成员选择适当的布局。使用此枚举成员定义的对象不能在托管代码的外部公开。尝试这样做将引发异常。
- ·CharSet
指示在默认情况下应如何将类中的字符串数据字段作为 LPWSTR 或 LPSTR 进行封送处理。
如果 CharSet 字段设置为 CharSet.Unicode,则所有字符串参数在传递到非托管实现之前都转换成 Unicode 字符(LPWSTR)。如果该字段设置为 CharSet.Ansi,则字符串将转换成 ANSI 字符串 (LPSTR)。如果 CharSet 字段设置为CharSet.Auto,则转换与平台相关(在 Windows NT、Windows 2000、Windows XP 和 Windows Server 2003 系列上为 Unicode;在 Windows 98 和 Windows Me 上为 ANSI)。
- ·Pack
控制类或结构的数据字段在内存中的对齐方式。
此字段指示在指定 LayoutKind.Sequential 值时应使用的封装大小。Pack 的值必须为 0、1、2、4、8、16、32、64 或 128。值为 0 则指示封装对齐方式设置为当前平台的默认值。
- ·Size
指示类或结构的绝对大小。
必须大于或等于所有成员的总和。此字段主要由编译器编写器使用,以指定类或结构的总大小(以字节为单位),而且在扩展由结构占用的内存(用于直接的非托管访问)时,此字段也很有用。例如,在使用未直接在元数据中表示的联合时可使用此字段。
示例
[c-sharp] view plaincopyprint?
- 1. [StructLayout(LayoutKind.Explicit, Size=16, CharSet=CharSet.Ansi)]
- 2. publicclass MySystemTime
- 3. {
- 4. [FieldOffset(0)]publicushort wYear;
- 5. [FieldOffset(2)]publicushort wMonth;
- 6. [FieldOffset(4)]publicushort wDayOfWeek;
- 7. [FieldOffset(6)]publicushort wDay;
- 8. [FieldOffset(8)]publicushort wHour;
- 9. [FieldOffset(10)]publicushort wMinute;
- 10. [FieldOffset(12)]publicushort wSecond;
- 11. [FieldOffset(14)]publicushort wMilliseconds;
- 12. }
2. MarshalAsAttribute类
可将该属性应用于参数、字段或返回值。
该属性为可选属性,因为每个数据类型都有默认的封送处理行为。仅在可以将给定类型封送到多个类型时需要此属性。例如,可将字符串作为 LPStr、LPWStr、LPTStr 或 BStr 封送到非托管代码。默认情况下,公共语言运行时将字符串参数作为 BStr 封送到 COM 方法。可将 MarshalAsAttribute 属性应用于个别的字段或参数,使该字符串作为 LPStr 而不是 BStr 进行封送。类型库导出程序 (Tlbexp.exe) 将封送处理首选项传递给公共语言运行时。
当用于 COM 互操作或平台调用时,某些参数和返回值将具有不同的默认封送处理行为。默认情况下,运行时将字符串参数(以及值类型中的字段)作为 LPStr 封送到平台调用方法或函数。有关更多信息,请参见 默认封送处理行为。
参数说明
- ·UnmanagedType
数据将被作为该值封送。
在 System.Runtime.InteropServices.MarshalAsAttribute 中使用 UnmanagedType 枚举,以指定在与非托管代码进行交互的过程中如何对类型进行封送处理。可以使用此枚举对使用简单值类型(I1、I2、I4、I8、R4、R8、U2、U4 和 U8)、.NET Framework 中未提供的非托管类型以及各种杂项类型的代码进行封送处理。
- ·SizeConst
指示固定长度数组中的元素数,或要导入的字符串中的字符(不是字节)数。
对于 System.Runtime.InteropServices.UnmanagedType 枚举的 ByValArray 和 ByValTStr 成员,此字段是必需的。因为元数据的压缩格式限制为 0x1FFFFFFF,所以 SizeConst 的允许值的范围为(>= 0 且 <= 0x1FFFFFFF)。
3. 不安全代码
为了保持类型安全,默认情况下,C# 不支持指针运算。不过,通过使用 unsafe 关键字,可以定义可使用指针的不安全上下文。
不安全代码具有下列属性:
- ·方法、类型和可被定义为不安全的代码块。
- ·在某些情况下,通过移除数组界限检查,不安全代码可提高应用程序的性能。
- ·当调用需要指针的本机函数时,需要使用不安全代码。
- ·使用不安全代码将引起安全风险和稳定性风险。
- ·在 C# 中,为了编译不安全代码,必须用 /unsafe 编译应用程序。
4. Unsafe
unsafe 关键字表示不安全上下文,该上下文是任何涉及指针的操作所必需的。
可以在类型或成员的声明中使用 unsafe 修饰符。因此,类型或成员的整个正文范围均被视为不安全上下文。例如,以下是用 unsafe 修饰符声明的方法:
unsafe static void FastCopy(byte[] src, byte[] dst, int count)
{
// Unsafe context: can use pointers here.
}
不安全上下文的范围从参数列表扩展到方法的结尾,因此指针在以下参数列表中也可以使用:
unsafe static void FastCopy ( byte* ps, byte* pd, int count ) {...}
还可以使用不安全块从而能够使用该块内的不安全代码。例如:
unsafe
{
// Unsafe context: can use pointers here.
}
若要编译不安全代码,必须指定 /unsafe 编译器选项。无法通过公共语言运行库验证不安全代码。
示例
[c-sharp] view plaincopyprint?
- 1. // cs_unsafe_keyword.cs
- 2. // compile with: /unsafe
- 3. using System;
- 4. class UnsafeTest
- 5. {
- 6. // Unsafe method: takes pointer to int:
- 7. unsafestaticvoid SquarePtrParam(int* p)
- 8. {
- 9. *p *= *p;
- 10. }
- 11. unsafestaticvoid Main()
- 12. {
- 13. int i = 5;
- 14. // Unsafe method: uses address-of operator (&):
- 15. SquarePtrParam(&i);
- 16. Console.WriteLine(i);
- 17. }
- 18. }
5. fixed
fixed 语句禁止垃圾回收器重定位可移动的变量。fixed 语句只能出现在不安全的上下文中。
fixed 语句设置指向托管变量的指针,并在执行该语句期间“固定”此变量。如果没有 fixed 语句,则指向可移动托管变量的指针的作用很小,因为垃圾回收可能不可预知地重定位变量。C# 编译器只允许在 fixed 语句中分配指向托管变量的指针。
[c-sharp] view plaincopyprint?
- unsafe static void TestMethod()
- {
- // assume class Point { public int x, y; }
- // pt is a managed variable, subject to garbage collection.
- Point pt = new Point();
- // Using fixed allows the address of pt members to be
- // taken, and "pins" pt so it isn't relocated.
- fixed (int* p = &pt.x)
- {
- *p = 1;
- }
- }
可以用数组或字符串的地址初始化指针:
[c-sharp] view plaincopyprint?
- unsafe void Test2()
- {
- Point point = new Point();
- double[] arr = { 0, 1.5, 2.3, 3.4, 4.0, 5.9 };
- string str = "Hello World";
- fixed (double* p = arr) { /*...*/ } // equivalent to p = &arr[0]
- fixed (char* p = str) { /*...*/ } // equivalent to p = &str[0]
- fixed (int* p1 = &point.x)
- {
- fixed (double* p2 = &arr[5])
- {
- // Do something with p1 and p2.
- }
- }
- }
只要指针的类型相同,就可以初始化多个指针:
[c-sharp] view plaincopyprint?
- fixed (byte* ps = srcarray, pd = dstarray) {...}
示例
[c-sharp] view plaincopyprint?
- class Point
- {
- public int x, y;
- }
- class FixedTest2
- {
- // Unsafe method: takes a pointer to an int.
- unsafe static void SquarePtrParam (int* p)
- {
- *p *= *p;
- }
- unsafe static void Main()
- {
- Point pt = new Point();
- pt.x = 5;
- pt.y = 6;
- // Pin pt in place:
- fixed (int* p = &pt.x)
- {
- SquarePtrParam (p);
- }
- // pt now unpinned
- Console.WriteLine ("{0} {1}", pt.x, pt.y);
- }
- }
- /*
- Output:
- 25 6
- */
Fixed 还可用于创建固定大小的缓冲区。
[c-sharp] view plaincopyprint?
- public struct MyArray // This code must appear in an unsafe block
- {
- public fixed char pathName[128];
- }
在此结构中,pathName 数组具有固定的大小和位置,因此可用在其他不安全的代码中。
128 个元素的 char 数组的大小为 256 字节。在固定大小的 char 缓冲区中,每个字符始终占用两个字节,而与编码无关。即使将 char 缓冲区封送到具有 CharSet = CharSet.Auto 或 CharSet = CharSet.Ansi 的 API 方法或结构,也是如此。有关更多信息,请参见 CharSet。
另一种常见的固定大小的数组是 bool 数组。bool 数组中的元素的大小始终为一个字节。bool 数组不适合用于创建位数组或位缓冲区。
|
说明: |
|
除了用 stackalloc 创建的内存之外,C# 编译器和公共语言运行时 (CLR) 不执行任何安全缓冲区溢出检查。与所有不安全代码一样,请谨慎使用。 |
不安全缓冲区与常规数组在以下方面不同:
- ·不安全缓冲区只能用在不安全上下文中。
- ·不安全缓冲区始终是向量(或一维数组)。
- ·数组的声明应包括计数,如 char id[8]。而不能使用 char id[]。
- ·不安全缓冲区只能是不安全上下文中的结构的实例字段。
6. stackalloc
stackalloc 关键字用于不安全的代码上下文中,以便在堆栈上分配内存块。
[c-sharp] view plaincopyprint?
- int* fib = stackalloc int[100];
下面的示例输出斐波那契数列的 100 个数字,其中每个数字都是前两个数字之和。在代码中,大小足够容纳 100 个 int 类型元素的内存块是在堆栈上分配的,而不是在堆上分配的。该块的地址存储在 fib 指针中。此内存不受垃圾回收的制约,因此不必将其钉住(通过使用 fixed)。内存块的生存期受限于定义它的方法的生存期。不能在方法返回之前释放内存。
stackalloc 仅在局部变量的初始值设定项中有效。
由于涉及指针类型,因此 stackalloc 要求不安全上下文。有关更多信息,请参见 不安全代码和指针(C# 编程指南)。
stackalloc 类似于 C 运行时中的 _alloca。
不安全代码的安全性低于安全替代代码。但是,通过使用 stackalloc 可以自动启用公共语言运行时 (CLR) 中的缓冲区溢出检测功能。如果检测到缓冲区溢出,进程将尽快终止,以最大限度地减小执行恶意代码的机会。
[c-sharp] view plaincopyprint?
- class Test
- {
- static unsafe void Main()
- {
- const int arraySize = 20;
- int* fib = stackalloc int[arraySize];
- int* p = fib;
- *p++ = 1;
- for (int i = 2; i < arraySize; ++i, ++p)
- {
- // Sum the previous two numbers.
- *p = p[-1] + p[-2];
- }
- for (int i = 0; i < arraySize - 1; ++i)
- {
- Console.WriteLine(fib[i]);
- }
- // Keep the console window open in debug mode.
- System.Console.WriteLine("Press any key to exit.");
- System.Console.ReadKey();
- }
- }
- /*
- Output
- 1
- 1
- 2
- 3
- 5
- 8
- 13
- 21
- 34
- 55
- 89
- 144
- 233
- 377
- 610
- 987
- 1597
- 2584
- 4181
- */
字节流与结构、类之间的转换
实际案例
[c-sharp] view plaincopyprint?
- using System;
- using System.Collections.Generic;
- using System.IO;
- using System.Text;
- using System.Runtime.InteropServices;
- namespace ConsoleApplication1
- {
- [StructLayout(LayoutKind.Sequential,CharSet=CharSet.Ansi,Pack=1)]
- public struct ConfigInfo
- {
- public int i;
- public short s;
- public byte c;
- //public fixed byte str[10];
- //public string GetStr() {
- // fixed (byte* p = str) {
- // return Marshal.PtrToStringAnsi((IntPtr)p);
- // }
- //}
- [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 11)]
- public string str;
- }
- class Program
- {
- static void Main(string[] args)
- {
- FileStream f = new FileInfo(@"e:/test.dat").OpenRead();
- byte[] data = new byte[17];
- f.Read(data, 0, 17);
- f.Close();
- unsafe {
- fixed (byte* pContext = data) {
- ConfigInfo info = (ConfigInfo)Marshal.PtrToStructure((IntPtr)pContext, typeof(ConfigInfo));
- Console.WriteLine(info.str);
- }
- }
- Console.ReadLine();
- }
- }
- }
浙公网安备 33010602011771号