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. 1.   [StructLayout(LayoutKind.Explicit, Size=16, CharSet=CharSet.Ansi)]
  2. 2.   publicclass MySystemTime
  3. 3.   {
  4. 4.   [FieldOffset(0)]publicushort wYear;
  5. 5.   [FieldOffset(2)]publicushort wMonth;
  6. 6.   [FieldOffset(4)]publicushort wDayOfWeek;
  7. 7.   [FieldOffset(6)]publicushort wDay;
  8. 8.   [FieldOffset(8)]publicushort wHour;
  9. 9.   [FieldOffset(10)]publicushort wMinute;
  10. 10.  [FieldOffset(12)]publicushort wSecond;
  11. 11.  [FieldOffset(14)]publicushort wMilliseconds;
  12. 12.  }

2. MarshalAsAttribute

可将该属性应用于参数、字段或返回值。

该属性为可选属性,因为每个数据类型都有默认的封送处理行为。仅在可以将给定类型封送到多个类型时需要此属性。例如,可将字符串作为 LPStrLPWStrLPTStrBStr 封送到非托管代码。默认情况下,公共语言运行时将字符串参数作为 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 枚举的 ByValArrayByValTStr 成员,此字段是必需的。因为元数据的压缩格式限制为 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. 1.   // cs_unsafe_keyword.cs
  2. 2.   // compile with: /unsafe
  3. 3.   using System;
  4. 4.   class UnsafeTest
  5. 5.   {
  6. 6.   // Unsafe method: takes pointer to int:
  7. 7.   unsafestaticvoid SquarePtrParam(int* p)
  8. 8.   {
  9. 9.   *p *= *p;
  10. 10.  }
  11. 11.  unsafestaticvoid Main()
  12. 12.  {
  13. 13.  int i = 5;
  14. 14.  // Unsafe method: uses address-of operator (&):
  15. 15.  SquarePtrParam(&i);
  16. 16.  Console.WriteLine(i);
  17. 17.  }
  18. 18.  }

5. fixed

fixed 语句禁止垃圾回收器重定位可移动的变量。fixed 语句只能出现在不安全的上下文中。

fixed 语句设置指向托管变量的指针,并在执行该语句期间“固定”此变量。如果没有 fixed 语句,则指向可移动托管变量的指针的作用很小,因为垃圾回收可能不可预知地重定位变量。C# 编译器只允许在 fixed 语句中分配指向托管变量的指针。

[c-sharp] view plaincopyprint?

  1. unsafe static void TestMethod()
  2. {
  3. // assume class Point { public int x, y; }
  4. // pt is a managed variable, subject to garbage collection.
  5. Point pt = new Point();
  6. // Using fixed allows the address of pt members to be
  7. // taken, and "pins" pt so it isn't relocated.
  8. fixed (int* p = &pt.x)
  9. {
  10. *p = 1;
  11. }
  12. }

可以用数组或字符串的地址初始化指针:

[c-sharp] view plaincopyprint?

  1. unsafe void Test2()
  2. {
  3. Point point = new Point();
  4. double[] arr = { 0, 1.5, 2.3, 3.4, 4.0, 5.9 };
  5. string str = "Hello World";
  6. fixed (double* p = arr) { /*...*/ } // equivalent to p = &arr[0]
  7. fixed (char* p = str) { /*...*/ } // equivalent to p = &str[0]
  8. fixed (int* p1 = &point.x)
  9. {
  10. fixed (double* p2 = &arr[5])
  11. {
  12. // Do something with p1 and p2.
  13. }
  14. }
  15. }

只要指针的类型相同,就可以初始化多个指针:

[c-sharp] view plaincopyprint?

  1. fixed (byte* ps = srcarray, pd = dstarray) {...}

示例

[c-sharp] view plaincopyprint?

  1. class Point
  2. {
  3. public int x, y;
  4. }
  5. class FixedTest2
  6. {
  7. // Unsafe method: takes a pointer to an int.
  8. unsafe static void SquarePtrParam (int* p)
  9. {
  10. *p *= *p;
  11. }
  12. unsafe static void Main()
  13. {
  14. Point pt = new Point();
  15. pt.x = 5;
  16. pt.y = 6;
  17. // Pin pt in place:
  18. fixed (int* p = &pt.x)
  19. {
  20. SquarePtrParam (p);
  21. }
  22. // pt now unpinned
  23. Console.WriteLine ("{0} {1}", pt.x, pt.y);
  24. }
  25. }
  26. /*
  27. Output:
  28. 25 6
  29. */

Fixed 还可用于创建固定大小的缓冲区

[c-sharp] view plaincopyprint?

  1. public struct MyArray // This code must appear in an unsafe block
  2. {
  3. public fixed char pathName[128];
  4. }

在此结构中,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?

  1. int* fib = stackalloc int[100];

下面的示例输出斐波那契数列的 100 个数字,其中每个数字都是前两个数字之和。在代码中,大小足够容纳 100 个 int 类型元素的内存块是在堆栈上分配的,而不是在堆上分配的。该块的地址存储在 fib 指针中。此内存不受垃圾回收的制约,因此不必将其钉住(通过使用 fixed)。内存块的生存期受限于定义它的方法的生存期。不能在方法返回之前释放内存。

stackalloc 仅在局部变量的初始值设定项中有效。

由于涉及指针类型,因此 stackalloc 要求不安全上下文。有关更多信息,请参见 不安全代码和指针(C# 编程指南)

stackalloc 类似于 C 运行时中的 _alloca

不安全代码的安全性低于安全替代代码。但是,通过使用 stackalloc 可以自动启用公共语言运行时 (CLR) 中的缓冲区溢出检测功能。如果检测到缓冲区溢出,进程将尽快终止,以最大限度地减小执行恶意代码的机会。

[c-sharp] view plaincopyprint?

  1. class Test
  2. {
  3. static unsafe void Main()
  4. {
  5. const int arraySize = 20;
  6. int* fib = stackalloc int[arraySize];
  7. int* p = fib;
  8. *p++ = 1;
  9. for (int i = 2; i < arraySize; ++i, ++p)
  10. {
  11. // Sum the previous two numbers.
  12. *p = p[-1] + p[-2];
  13. }
  14. for (int i = 0; i < arraySize - 1; ++i)
  15. {
  16. Console.WriteLine(fib[i]);
  17. }
  18. // Keep the console window open in debug mode.
  19. System.Console.WriteLine("Press any key to exit.");
  20. System.Console.ReadKey();
  21. }
  22. }
  23. /*
  24. Output
  25. 1
  26. 1
  27. 2
  28. 3
  29. 5
  30. 8
  31. 13
  32. 21
  33. 34
  34. 55
  35. 89
  36. 144
  37. 233
  38. 377
  39. 610
  40. 987
  41. 1597
  42. 2584
  43. 4181
  44. */


字节流与结构、类之间的转换

实际案例

[c-sharp] view plaincopyprint?

  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Text;
  5. using System.Runtime.InteropServices;
  6. namespace ConsoleApplication1
  7. {
  8. [StructLayout(LayoutKind.Sequential,CharSet=CharSet.Ansi,Pack=1)]
  9. public struct ConfigInfo
  10. {
  11. public int i;
  12. public short s;
  13. public byte c;
  14. //public fixed byte str[10];
  15. //public string GetStr() {
  16. // fixed (byte* p = str) {
  17. // return Marshal.PtrToStringAnsi((IntPtr)p);
  18. // }
  19. //}
  20. [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 11)]
  21. public string str;
  22. }
  23. class Program
  24. {
  25. static void Main(string[] args)
  26. {
  27. FileStream f = new FileInfo(@"e:/test.dat").OpenRead();
  28. byte[] data = new byte[17];
  29. f.Read(data, 0, 17);
  30. f.Close();
  31. unsafe {
  32. fixed (byte* pContext = data) {
  33. ConfigInfo info = (ConfigInfo)Marshal.PtrToStructure((IntPtr)pContext, typeof(ConfigInfo));
  34. Console.WriteLine(info.str);
  35. }
  36. }
  37. Console.ReadLine();
  38. }
  39. }
  40. }

 

posted on 2013-03-08 15:59  Q&A  阅读(944)  评论(0)    收藏  举报