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

字节流与结构、类之间的转换
2011年04月26日
  1. StructLayoutAttribute类
  可将该属性应用于类或结构。
  通常,公共语言运行库控制类或结构的数据字段在托管内存中的物理布局。如果类或结构需要按某种方式排列,则可以使用 StructLayoutAttribute。如果要将类传递给需要指定布局的非托管代码,则显式控制类布局是重要的。
  参数说明
  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
  指示类或结构的绝对大小。
  必须大于或等于所有成员的总和。此字段主要由编译器编写器使用,以指定类或结构的总大小(以字节为单位),而且在扩展由结构占用的内存(用于直接的非托管访问)时,此字段也很有用。例如,在使用未直接在元数据中表示的联合时可使用此字段。
  示例
  view plaincopy to clipboardprint?
  [StructLayout(LayoutKind.Explicit, Size=16, CharSet=CharSet.Ansi)]
  public class MySystemTime
  {
  [FieldOffset(0)]public ushort wYear;
  [FieldOffset(2)]public ushort wMonth;
  [FieldOffset(4)]public ushort wDayOfWeek;
  [FieldOffset(6)]public ushort wDay;
  [FieldOffset(8)]public ushort wHour;
  [FieldOffset(10)]public ushort wMinute;
  [FieldOffset(12)]public ushort wSecond;
  [FieldOffset(14)]public ushort wMilliseconds;
  }
  [StructLayout(LayoutKind.Explicit, Size=16, CharSet=CharSet.Ansi)]
  public class MySystemTime
  {
  [FieldOffset(0)]public ushort wYear;
  [FieldOffset(2)]public ushort wMonth;
  [FieldOffset(4)]public ushort wDayOfWeek;
  [FieldOffset(6)]public ushort wDay;
  [FieldOffset(8)]public ushort wHour;
  [FieldOffset(10)]public ushort wMinute;
  [FieldOffset(12)]public ushort wSecond;
  [FieldOffset(14)]public ushort wMilliseconds;
  }
  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 且 运行库验证不安全代码。
  示例
  view plaincopy to clipboardprint?
  // cs_unsafe_keyword.cs
  // compile with: /unsafe
  using System;
  class UnsafeTest
  {
  // Unsafe method: takes pointer to int:
  unsafe static void SquarePtrParam(int* p)
  {
  *p *= *p;
  }
  unsafe static void Main()
  {
  int i = 5;
  // Unsafe method: uses address-of operator (&):
  SquarePtrParam(&i);
  Console.WriteLine(i);
  }
  }
  // cs_unsafe_keyword.cs
  // compile with: /unsafe
  using System;
  class UnsafeTest
  {
  // Unsafe method: takes pointer to int:
  unsafe static void SquarePtrParam(int* p)
  {
  *p *= *p;
  }
  unsafe static void Main()
  {
  int i = 5;
  // Unsafe method: uses address-of operator (&):
  SquarePtrParam(&i);
  Console.WriteLine(i);
  }
  }
  5. fixed
  fixed 语句禁止垃圾回收器重定位可移动的变量。fixed 语句只能出现在不安全的上下文中。
  fixed 语句设置指向托管变量的指针,并在执行该语句期间“固定”此变量。如果没有 fixed 语句,则指向可移动托管变量的指针的作用很小,因为垃圾回收可能不可预知地重定位变量。C# 编译器只允许在 fixed 语句中分配指向托管变量的指针。
  view plaincopy to clipboardprint?
  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;
  }
  }
  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;
  }
  }
  可以用数组或字符串的地址初始化指针:
  view plaincopy to clipboardprint?
  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.
  }
  }
  }
  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.
  }
  }
  }
  只要指针的类型相同,就可以初始化多个指针:
  view plaincopy to clipboardprint?
  fixed (byte* ps = srcarray, pd = dstarray) {...}
  fixed (byte* ps = srcarray, pd = dstarray) {...}
  示例
  view plaincopy to clipboardprint?
  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
  */
  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 还可用于创建固定大小的缓冲区。
  view plaincopy to clipboardprint?
  public struct MyArray // This code must appear in an unsafe block
  {
  public fixed char pathName[128];
  }
  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 关键字用于不安全的代码上下文中,以便在堆栈上分配内存块。
  view plaincopy to clipboardprint?
  int* fib = stackalloc int[100];
  int* fib = stackalloc int[100];
  下面的示例输出斐波那契数列的 100 个数字,其中每个数字都是前两个数字之和。在代码中,大小足够容纳 100 个 int 类型元素的内存块是在堆栈上分配的,而不是在堆上分配的。该块的地址存储在 fib 指针中。此内存不受垃圾回收的制约,因此不必将其钉住(通过使用 fixed)。内存块的生存期受限于定义它的方法的生存期。不能在方法返回之前释放内存。
  stackalloc 仅在局部变量的初始值设定项中有效。
  由于涉及指针类型,因此 stackalloc 要求不安全上下文。有关更多信息,请参见 不安全代码和指针(C# 编程指南)。
  stackalloc 类似于 C 运行时中的 _alloca。
  不安全代码的安全性低于安全替代代码。但是,通过使用 stackalloc 可以自动启用公共语言运行时 (CLR) 中的缓冲区溢出检测功能。如果检测到缓冲区溢出,进程将尽快终止,以最大限度地减小执行恶意代码的机会。
  view plaincopy to clipboardprint?
  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);
  }
  // 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
  */
  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);
  }
  // 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
  */
  --------------------------------------------------------------------------------
  字节流与结构、类之间的转换
  实际案例
  view plaincopy to clipboardprint?
  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();
  }
  }
  }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值