C#如何从byte[]中直接读取Structure实例详解

序、前言

emmmmm,首先这篇文章讲的不是用BinaryFormatter来进行结构体的二进制转换,说真的BinaryFormatter这个类其实现在的作用并不是特别大了,因为BinaryFormatter二进制序列化出来的结果只能用于.net平台,现在可能就用于如存入Redis这种情况下会在使用。

去年年尾的样子,我阅读学习某C++开发的程序源码时,发现作者用了一个很骚的操作直接将byte[]数组转为了结构体对象:

上面的data变量是一个指向unsigned char类型的指针,就只要一个简单的类型转换就可以将一堆unsigned char转换成想要的结构体,这着实有点让笔者有点羡慕。

后来,笔者想用C#开发一个流量分析程序,由于需要对IP报文进行仔细的特征提取,所以不能直接使用第三方数据包解析库(如:PacketDotNet)直接解析,会丢失部分特征,然而使用BinaryReader进行报文头解析的话,整个解析代码会写的丧心病狂的恶(e)心(xin),正在苦恼的时候,突然想起上面提到的那个骚操作时,笔者突然冒出了一个想法,C#里也支持结构体,那我能不能也像C++这样直接从字节序列中读取出结构体呢?

注:本文所有代码在.net Standard 2.0上测试通过。

一、先声明,后调用~

那么在开始前,我们先定义一下要用到的IPv4报文头结构体,各位同学IPv4报文头结构有没有忘掉啊,如果忘了的话记得先去补补TCP网络基础哈~

因为IPv4头是允许可变长度的,所以我们的结构体只需要解析到目的地址就够了,后面的可变选项部分在报文头前16字节解析完成之前是不知道会有多长的。

IPv4头部结构体定义如下,由于IPv4头部定义中,各个字段并不是都8位整长的,所以有几个字段是相互并在一起的:

public struct IPv4Header
{
 /// <summary>
 /// IP协议版本及头部长度
 /// </summary>
 private byte _verHlen;

 /// <summary>
 /// 差异化服务及显式拥塞通告
 /// </summary>
 private byte _dscpEcn;

 /// <summary>
 /// 报文全长
 /// </summary>
 private ushort _totalLength;

 /// <summary>
 /// 标识符
 /// </summary>
 private ushort _identification;

 /// <summary>
 /// 标志位及分片偏移
 /// </summary>
 private ushort _flagsOffset;

 /// <summary>
 /// 存活时间
 /// </summary>
 private byte _ttl;

 /// <summary>
 /// 协议
 /// </summary>
 private byte _protocol;

 /// <summary>
 /// 头部检验和
 /// </summary>
 private ushort _checksum;

 /// <summary>
 /// 源地址
 /// </summary>
 private int _srcAddr;

 /// <summary>
 /// 目标地址
 /// </summary>
 private int _dstAddr;
}

当然,为了方便后续的使用,还可以在此技术上设置一些可读属性:

public struct IPv4Header
{
 /// <summary>
 /// IP协议版本及头部长度
 /// </summary>
 private byte _verHlen;

 /// <summary>
 /// 差异化服务及显式拥塞通告
 /// </summary>
 private byte _dscpEcn;

 /// <summary>
 /// 报文全长
 /// </summary>
 private ushort _totalLength;

 /// <summary>
 /// 标识符
 /// </summary>
 private ushort _identification;

 /// <summary>
 /// 标志位及分片偏移
 /// </summary>
 private ushort _flagsOffset;

 /// <summary>
 /// 存活时间
 /// </summary>
 private byte _ttl;

 /// <summary>
 /// 协议
 /// </summary>
 private byte _protocol;

 /// <summary>
 /// 头部检验和
 /// </summary>
 private ushort _checksum;

 /// <summary>
 /// 源地址
 /// </summary>
 private int _srcAddr;

 /// <summary>
 /// 目标地址
 /// </summary>
 private int _dstAddr;

 /// <summary>
 /// IP协议版本
 /// </summary>
 public int Version
 {
 get
 {
  return (this._verHlen & 0xF0) >> 4;
 }
 }

 /// <summary>
 /// 头部长度
 /// </summary>
 public int HeaderLength
 {
 get
 {
  return this._verHlen & 0x0F;
 }
 }

 /// <summary>
 /// 差异化服务
 /// </summary>
 public int DSCP
 {
 get
 {
  return (this._dscpEcn & 0xFC) >> 2;
 }
 }

 /// <summary>
 /// 显式拥塞通告
 /// </summary>
 public int ECN
 {
 get
 {
  return this._dscpEcn & 0x03;
 }
 }

 /// <summary>
 /// 报文全长
 /// </summary>
 public ushort TotalLength
 {
 get
 {
  return this._totalLength;
 }
 }

 /// <summary>
 /// 标识符
 /// </summary>
 public ushort Identification
 {
 get
 {
  return this._identification;
 }
 }

 /// <summary>
 /// 保留字段
 /// </summary>
 public int Reserved
 {
 get
 {
  return (this._flagsOffset & 0x80) >> 7;
 }
 }

 /// <summary>
 /// 禁止分片标志位
 /// </summary>
 public bool DF
 {
 get
 {
  return (this._flagsOffset & 0x40) == 1;
 }
 }

 /// <summary>
 /// 更多分片标志位
 /// </summary>
 public bool MF
 {
 get
 {
  return (this._flagsOffset & 0x20) == 1;
 }
 }

 /// <summary>
 /// 分片偏移
 /// </summary>
 public int FragmentOffset
 {
 get
 {
  return this._flagsOffset & 0x1F;
 }
 }

 /// <summary>
 /// 存活时间
 /// </summary>
 public byte TTL
 {
 get
 {
  return this._ttl;
 }
 }

 /// <summary>
 /// 协议
 /// </summary>
 public byte Protocol
 {
 get
 {
  return this._protocol;
 }
 }

 /// <summary>
 /// 头部检验和
 /// </summary>
 public ushort HeaderChecksum
 {
 get
 {
  return this._checksum;
 }
 }

 /// <summary>
 /// 源地址
 /// </summary>
 public IPAddress SrcAddr
 {
 get
 {
  return new IPAddress(BitConverter.GetBytes(this._srcAddr));
 }
 }

 /// <summary>
 /// 目的地址
 /// </summary>
 public IPAddress DstAddr
 {
 get
 {
  return new IPAddress(BitConverter.GetBytes(this._dstAddr));
 }
 }
}

二、byte[]转Structure第一版

首先笔者先看了一圈文档,看看C#有没有什么方法支持将byte[]转为结构体,逛了一圈发现一个有这么一个函数:

System.Runtime.InteropServices.Marshal.PtrToStructure<T>(IntPtr)

这个方法接收两个参数,一个结构体泛型和一个指向结构体数据的安全指针(IntPtr),然后这个方法就能返回一个结构体实例出来了。

那么现在的问题就是该如何取得一个byte[]对象的安全指针呢?这里笔者第一反应是利用System.Runtime.InteropServices.Marshal.AllocHGlobal方法分配一块堆外内存出来,然后将待转换的byte[]对象复制到这块堆外内存中,接着利用PtrToStructure<T>函数将byte[]对象转换成我们想要的结构体对象实例,最后释放掉堆外内存就可以了。

将上面的步骤转换为C#代码,就形成了第一版的BytesToStructure<T>函数:

/// <summary>
/// 将 byte[] 转为指定结构体实例
/// </summary>
/// <typeparam name="T">目标结构体类型</typeparam>
/// <param name="bytes">待转换 byte[]</param>
/// <returns>转换后的结构体实例</returns>
public static T BytesToStructure<T>(byte[] bytes) where T : struct
{
 int size = Marshal.SizeOf(typeof(T));
 IntPtr ptr = Marshal.AllocHGlobal(size);
 try
 {
 Marshal.Copy(bytes, 0, ptr, size);
 return Marshal.PtrToStructure<T>(ptr);
 }
 finally
 {
 Marshal.FreeHGlobal(ptr);
 }
}

之后就只要抓一下包看看效果就好了。

抓包我们用SharpPcap,顺便让它帮我们过滤一下仅捕获IP报文。代码如下:

public static void Main(string[] args)
{
 CaptureDeviceList devices = CaptureDeviceList.Instance;
 if (devices.Count <= 0)
 {
 Console.WriteLine("No device found on this machine");
 return;
 }
 else
 {
 Console.WriteLine("available devices:");
 Console.WriteLine("-----------------------------");
 }

 int index = 0;
 foreach (ICaptureDevice item in devices)
 {
 Console.WriteLine($"{index++}) {item.Name}");
 }
 Console.Write("enter your choose: ");
 index = int.Parse(Console.ReadLine());
 Console.WriteLine();

 ICaptureDevice device = devices[index];
 device.OnPacketArrival += new PacketArrivalEventHandler((sender, e) =>
 {
 Packet packet = Packet.ParsePacket(e.Packet.LinkLayerType, e.Packet.Data);
 if (packet.Extract(typeof(IPPacket)) is IPPacket ipPacket)
 {
  IPv4Header header = StructHelper.BytesToStructure<IPv4Header>(ipPacket.Bytes);
  Console.WriteLine($"{header.SrcAddr} ==> {header.DstAddr}");
 }
 });

 device.Open(DeviceMode.Promiscuous, 1000);
 device.Filter = "ip";
 Console.CancelKeyPress += new ConsoleCancelEventHandler((sender, e) => device.Close());

 device.Capture();
}

启动上面的代码,选择需要捕获数据包的网卡,就可以看到此网卡上所有IP报文记录及其源地址与目标地址了:

三、大端字节序、小端字节序……

刚刚上面我们已经成功的将byte[]对象转换为我们想要的结构体了,但我们转换出来的结构体真的正确吗,我们可以将我们读取出来的结构体和PacketDotNet包解析出来的IP报文头数据进行比较:

我们可以看到我们转换出来的IPv4报文头结构体中的报文总长字段和PacketDotNet解析出来的数据不一致,我们的转换函数出来的包总长是15872,而PacketDotNet解析出来的包总长只有62。

到底谁是对的呢,不用猜,肯定是我们的转换函数有问题,如果看官您不相信,可以用WireShark抓包做比较,看看WireShark会挺谁的结果。

那么到底是哪里错了呢?相信不少有实战经验的看官已经知道问题的原因了:大小字节序。

我们分别将15872和62转为二进制格式:

数值 15872 62
二进制 00111110 00000000 00000000 00111110

15872和62这两个数字转换为二进制之后,15872的00111110在前面,00000000在后面,而62则正好相反。

一般来说计算机硬件有两种储存数据的方式:大端字节序(big endian)和小端字节序(little endian)。

举例来说,数值0x2211使用两个字节储存:高位字节是0x22,低位字节是0x11。

大端字节序:

高位字节在前,低位字节在后,这是人类读写数值的方法。

小端字节序:

低位字节在前,高位字节在后,即以0x1122形式储存。

在网络中传输数据,一般使用的是大端字节序,然而在计算机内部中,为了方便计算,大多都会使用小端字节序进行储存。

.net CLR默认会使用当前计算机系统使用的字节顺序,而笔者测试时用的系统是Windows 7 x64,内部默认用的是小端字节序,所以在一切均为默认的情况下,多字节字段在转换后都会因为字节序不正确而读取为错误值。

.net提供了一个属性用于开发者获取当前计算机系统使用的字节序:

System.BitConverter.IsLittleEndian

如果此属性为true,则表示当前计算机正在使用小端字节序,否则为大端字节序。

回到刚刚的问题,为了防止大小端字节序对转换产生影响,我们可以使用Attribute对结构体中各个多字节字段进行标记,并在转换前判断字节序是否一致,如果不一致则进行顺序调整,代码如下:

首先定义一个大小端字节序枚举:

/// <summary>
/// 字节序枚举
/// </summary>
public enum Endianness
{
 /// <summary>
 /// 大端字节序
 /// </summary>
 BigEndian,

 /// <summary>
 /// 小端字节序
 /// </summary>
 LittleEndian
}

然后定义大小端字节序声明特性

/// <summary>
/// 字节序特性
/// </summary>
[AttributeUsage(AttributeTargets.Field)]
public class EndianAttribute : Attribute
{
 /// <summary>
 /// 标记字段的字节序
 /// </summary>
 public Endianness Endianness { get; private set; }

 /// <summary>
 /// 构造函数
 /// </summary>
 /// <param name="endianness">字节序</param>
 public EndianAttribute(Endianness endianness)
 {
 this.Endianness = endianness;
 }
}

我们在这里使用AttributeUsage特性限制此EndianAttribute特性仅限字段使用。

然后是转换函数:

/// <summary>
/// 调整字节顺序
/// </summary>
/// <typeparam name="T">待调整字节顺序的结构体类型</typeparam>
/// <param name="bytes">字节数组</param>
private static byte[] RespectEndianness<T>(byte[] bytes)
{
 Type type = typeof(T);

 var fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
 .Where(f => f.IsDefined(typeof(EndianAttribute), false)).Select(field => new
 {
  Field = field,
  Attribute = (EndianAttribute)field.GetCustomAttributes(typeof(EndianAttribute), false).First(),
  Offset = Marshal.OffsetOf(type, field.Name).ToInt32()
 }).ToList();

 foreach (var field in fields)
 {
 if ((field.Attribute.Endianness == Endianness.BigEndian && BitConverter.IsLittleEndian) ||
 (field.Attribute.Endianness == Endianness.LittleEndian && !BitConverter.IsLittleEndian))
 {
  Array.Reverse(bytes, field.Offset, Marshal.SizeOf(field.Field.FieldType));
 }
 }

 return bytes;
}

此函数会先使用反射获取所有含有EndianAttribute特性的公开或非公开实例字段,然后依次求出其偏移,最后判断字段标注的字节序是否与当前计算机的字节序相同,如果不同则进行顺序翻转。另外上面的函数使用了Linq,需要引入System.Linq命名空间,且Linq函数中还使用到了匿名类。

接下来要对转换函数进行修改,只需要在转换前调用一下调序函数即可。

/// <summary>
/// 将 byte[] 转为指定结构体实例
/// </summary>
/// <typeparam name="T">目标结构体类型</typeparam>
/// <param name="bytes">待转换 byte[]</param>
/// <returns>转换后的结构体实例</returns>
public static T BytesToStructure<T>(byte[] bytes) where T : struct
{
 bytes = RespectEndianness<T>(bytes);
 int size = Marshal.SizeOf(typeof(T));
 IntPtr ptr = Marshal.AllocHGlobal(size);
 try
 {
 Marshal.Copy(bytes, 0, ptr, size);
 return Marshal.PtrToStructure<T>(ptr);
 }
 finally
 {
 Marshal.FreeHGlobal(ptr);
 }
}

当然了,我们还要对结构体中的各个多字节字段标记上EndianAttribute特性:

public struct IPv4Header
{
 /// <summary>
 /// IP协议版本及头部长度
 /// </summary>
 private byte _verHlen;

 /// <summary>
 /// 差异化服务及显式拥塞通告
 /// </summary>
 private byte _dscpEcn;

 /// <summary>
 /// 报文全长
 /// </summary>
 [Endian(Endianness.BigEndian)]
 private ushort _totalLength;

 /// <summary>
 /// 标识符
 /// </summary>
 [Endian(Endianness.BigEndian)]
 private ushort _identification;

 /// <summary>
 /// 标志位及分片偏移
 /// </summary>
 [Endian(Endianness.BigEndian)]
 private ushort _flagsOffset;

 /// <summary>
 /// 存活时间
 /// </summary>
 private byte _ttl;

 /// <summary>
 /// 协议
 /// </summary>
 private byte _protocol;

 /// <summary>
 /// 头部检验和
 /// </summary>
 [Endian(Endianness.BigEndian)]
 private ushort _checksum;

 /// <summary>
 /// 源地址
 /// </summary>
 private int _srcAddr;

 /// <summary>
 /// 目标地址
 /// </summary>
 private int _dstAddr;
}

需要说一点,就是最后的源地址和目标地址,笔者上面用的是

public IPAddress(byte[] address)

这个构造函数来构造IPAddress类,并且是使用BitConverter.GetBytes这个方法将int类型转为byte[]并传入构造函数的,所以不用注明大端序,否则会导致转换结果不正确(错误结果和正确结果会正好颠倒)。

重启程序,看看现在我们的转换函数转换出来的结果是不是和PacketDotNet转换结果一样了?

四、性能提升!性能提升!

在解决了大字节序小字节序的问题之后,让我们重新审视一下刚刚上面的转换函数,可以看到在刚才的函数中,每次要从byte[]中读取结构体时,都要经过“申请堆外内存——复制对象——读取结构体——释放堆外内存”这四步,申请堆外内存,复制对象和释放堆外内存这三步照理来说是浪费性能的,明明byte[]已经在内存中了,但就是为了获取它的安全句柄而大费周章的再去申请一块内存,毕竟申请和释放内存也算是一笔不小的开支了。

那除了Marshal.AllocHGlobal以外还有别的什么方法能获取到托管对象的安全句柄呢?笔者又去网上找了一下,您还别说,这还真的有。朋友,您听说过GCHandle吗?

System.Runtime.InteropServices.GCHandle.Alloc

此方法允许传入任意一个object对象,它将返回一个GCHandle实例并保护传入的对象不会被GC回收掉,当使用完毕后,需要调用此GCHandle实例的Free方法进行释放。而GCHandle结构体有一个实例方法AddrOfPinnedObject,此方法将返回此固定对象的地址及安全指针(IntPtr)。

利用GCHandle.Alloc方法,就可以避免重复的申请、复制和释放内存了,由此我们对刚刚的第一版BytesToStructure函数进行改进,第二版BytesToStructure函数闪亮登场:

/// <summary>
/// 将 byte[] 转为指定结构体实例
/// </summary>
/// <typeparam name="T">目标结构体类型</typeparam>
/// <param name="bytes">待转换 byte[]</param>
/// <returns>转换后的结构体实例</returns>
public static T BytesToStructureV2<T>(byte[] bytes) where T : struct
{
 bytes = RespectEndianness<T>(bytes);
 GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
 try
 {
 return Marshal.PtrToStructure<T>(handle.AddrOfPinnedObject());
 }
 finally
 {
 handle.Free();
 }
}

现在我们来比较两个转换函数的效率试试:

我们使用相同的数据包,让两个转换函数重复运行1000w次,查看两个函数使用的时间差距:

注意:因为调整大小端字节序会使用到反射,会严重的影响到函数本身的运行效率(运行时间大部份都在用于反射),所以在测试时,笔者会注释掉调整字节序调整的代码。

BytesToStructure<T> BytesToStructureV2<T>
1000w次转换耗时 5069 ms 2914 ms.

五、榨干潜能,使用不安全代码!

我们在刚刚的代码里通过避免“申请内存——复制数据——释放内存”的步骤来提升函数的执行效率,那经过上面的改造,我们的转换函数还有提升的空间吗?

答案是有的。

C#和Java最大的不同点在于C#允许程序员使用不安全代码,这里的不安全代码并不是指一定存在漏洞会被攻击者利用的不安全,而是使用指针的代码。是的!C#允许使用指针!只需要在编译时打开/unsafe开关。

文章一开始的C++代码利用指针进行转换,C#其实也可以:

/// <summary>
/// 将 byte[] 转为指定结构体实例
/// </summary>
/// <typeparam name="T">目标结构体类型</typeparam>
/// <param name="bytes">待转换 byte[]</param>
/// <returns>转换后的结构体实例</returns>
public static unsafe T BytesToStructureV3<T>(byte[] bytes) where T : struct
{
 bytes = RespectEndianness<T>(bytes);
 fixed (byte* ptr = &bytes[0])
 {
 return (T)Marshal.PtrToStructure((IntPtr)ptr, typeof(T));
 }
}

这个第三版函数使用了两个关键字unsafe和fixed,unsafe表示此代码为不安全代码,C#中不安全代码必须在unsafe标识区域内使用,且编译时要启用/unsafe开关。fixed在这里主要是为了将指针所指向的变量“钉住”,避免GC误重定位变量以产生错误。

同样,我们注释掉大小端字节序调整函数,再次重复运行1000w次,看看三个函数的用时:

BytesToStructure<T> BytesToStructureV2<T> BytesToStructureV3<T>
1000w次转换耗时 5069 ms 2914 ms. 2004 ms

又比之前缩短了进1s的时间。当然了因为这是重复1000w次的耗时,而因为我们注释掉了大小端字节序调整函数,实际情况下启用大小端字节序调整函数的话,时间会爆炸性的增长。可见反射是一个多么浪费性能的操作。

六、小结

emmmmm,说一个比较尴尬的事情,其实本文讨论的这种byte[]转为结构体的情况其实在日常开发中很少会用到,首先是因为结构体这种数据结构在日常开发中就很少会用到,平时开发的话类才是大头,另外如果是因为要和C/C++开发的Dll交互,可以利用.net中System.Runtime.InteropServices命名空间下的StructLayoutAttribute、FieldOffset等特性自定义标记结构体的结构,CLR会在结构体传入或传出时自动进行托管内存与非托管内存之间内存格式的转换。

所以本文其实是为了后面的博客做服务的,那就是不安全代码,这个看似神秘,实则锋利无比的双刃剑,尽请期待。

(0)

相关推荐

  • C#中Byte[]和String之间转换的方法

    本文给大家介绍如何在Byte[]和String之间进行转换? 比特(b):比特只有0 1,1代表有脉冲,0代表无脉冲.它是计算机物理内存保存的最基本单元. 字节(B):8个比特,0-255的整数表示 编码:字符必须编码后才能被计算机处理.早期计算机使用7为AscII编码,为了处理汉字设计了中文简体GB2312和big5 字符串与字节数组之间的转换,事实上是现实世界的信息和数字世界信息之间的转换,势必涉及到某种编码方式,不同的编码方式将导致不同的转换结果.C#中常使用System.Text.Enc

  • C#中两个byte如何相加

    发现问题 有人会想相加?还不简单,用 + 呀. byte a = 1; byte b = 2; byte c = a + b; 以上代码是通不过编译的,因为编译器对待 + 时,有 int 相加.有 decimal 相加.有字符串相加--就是没有 byte 相加,所以它会用最接近的 int 相加,自然返回的结果也是 int,而 int 类型是不能直接赋值给更小的 byte 类型的. 解决方法 所以,得改成这样: byte a = 1; byte b = 2; byte c = (byte)(a +

  • C#实现Stream与byte[]之间的转换实例教程

    本文以实例形式详细介绍了C#实现Stream与byte[]之间的转换的方法,分享给大家供大家参考之用.具体方法如下: 一.二进制转换成图片 MemoryStream ms = new MemoryStream(bytes); ms.Position = 0; Image img = Image.FromStream(ms); ms.Close(); this.pictureBox1.Image 二.C#中byte[]与string的转换代码 1. System.Text.UnicodeEncod

  • C# byte数组与Image相互转换的方法

    功能需求: 1.把一张图片(png bmp jpeg bmp gif)转换为byte数组存放到数据库. 2.把从数据库读取的byte数组转换为Image对象,赋值给相应的控件显示. 3.从图片byte数组得到对应图片的格式,生成一张图片保存到磁盘上. 这里的Image是System.Drawing.Image. 以下三个函数分别实现了上述三个需求: 复制代码 代码如下: // Convert Image to Byte[]        private byte[] ImageToByte(Im

  • C#中图片.BYTE[]和base64string的转换方法

    在C#中 图片到byte[]再到base64string的转换: Bitmap bmp = new Bitmap(filepath); MemoryStream ms = new MemoryStream(); bmp.Save(ms, System.Drawing.Imaging.ImageFormat.Gif); byte[] arr = new byte[ms.Length]; ms.Position = 0; ms.Read(arr, 0, (int)ms.Length); ms.Clo

  • C#中Byte转换相关的函数

     1.将一个对象转换为byte对象 public static byte GetByte(object o) { byte retInt = 0; if (o != null) { byte tmp; if (byte.TryParse(o.ToString().Trim(), out tmp)) { retInt = tmp; } } return retInt; } 2.将一个十六进制字符串转换为byte对象,字符串以0x开头 public static byte GetByteFormHe

  • C# 字符串string和内存流MemoryStream及比特数组byte[]之间相互转换

    定义string变量为str,内存流变量为ms,比特数组为bt 1.字符串转比特数组 复制代码 代码如下: (1)byte[] bt=System.Text.Encoding.Default.GetBytes("字符串"); (2)byte[] bt=Convert.FromBase64String("字符串"); 2.字符串转流 复制代码 代码如下: (1)MemoryStream ms=new MemoryStream(System.Text.Encoding.

  • C# Stream 和 byte[] 之间的转换

    /* - - - - - - - - - - - - - - - - - - - - - - - -   * Stream 和 byte[] 之间的转换  * - - - - - - - - - - - - - - - - - - - - - - - */ /// <summary> /// 将 Stream 转成 byte[] /// </summary> public byte[] StreamToBytes(Stream stream) {     byte[] bytes 

  • C#中string与byte[]的转换帮助类-.NET教程,C#语言

    主要实现了以下的函数 代码中出现的sidle是我的网名. /**//*  * @author wuerping  * @version 1.0  * @date 2004/11/30  * @description:  */  using system;  using system.text;  namespace sidlehelper  {  /**//// <summary>  /// summary description for strhelper.  /// 命名缩写:  /// 

  • C#如何从byte[]中直接读取Structure实例详解

    序.前言 emmmmm,首先这篇文章讲的不是用BinaryFormatter来进行结构体的二进制转换,说真的BinaryFormatter这个类其实现在的作用并不是特别大了,因为BinaryFormatter二进制序列化出来的结果只能用于.net平台,现在可能就用于如存入Redis这种情况下会在使用. 去年年尾的样子,我阅读学习某C++开发的程序源码时,发现作者用了一个很骚的操作直接将byte[]数组转为了结构体对象: 上面的data变量是一个指向unsigned char类型的指针,就只要一个

  • Python中读取文件名中的数字的实例详解

    我们在使用计算机时,我们创建一个个文件夹,可以节省桌面空间,做好整理归纳.python中,每个文件中有着不同的内容,我们要想使用文件,就要读取文件.本文向大家介绍Python读取文件名中的数字的方法:1.使用正则表达式:2.获取匹配的字符串:3.需要整数,可以使用int:4.生成数字. 第一步:可以使用正则表达式 regex = re.compile(r'\d+') 第二步:然后获取匹配的字符串 regex.findall(filename) 这将返回包含数字的字符串列表. 第三步:如果您实际需

  • Java中IO流 字节流实例详解

    Java中IO流 字节流实例详解 IO流(输入流.输出流),又分为字节流.字符流. 流是磁盘或其它外围设备中存储的数据的源点或终点. 输入流:程序从输入流读取数据源.数据源包括外界(键盘.文件.网络-),即是将数据源读入到程序的通信通道. 输出流:程序向输出流写入数据.将程序中的数据输出到外界(显示器.打印机.文件.网络-)的通信通道. 字节流 1.InputStream.OutputStream InputStream抽象了应用程序读取数据的方式 OutputStream抽象了应用程序写出数据

  • JSP中out对象的实例详解

    JSP中out对象的实例详解 一 什么是缓冲区 缓冲区:Buffer,所谓缓冲区就是内存的一块区域用来保存临时数据. 二 out对象 out对象是JspWrite类的实例,是向浏览器输出内容常用的对象. 三 常用方法 四 实例 <%@ page language="java" import="java.util.*" contentType="text/html; charset=utf-8"%> <% String path

  • java中Spring Security的实例详解

    java中Spring Security的实例详解 spring security是一个多方面的安全认证框架,提供了基于JavaEE规范的完整的安全认证解决方案.并且可以很好与目前主流的认证框架(如CAS,中央授权系统)集成.使用spring security的初衷是解决不同用户登录不同应用程序的权限问题,说到权限包括两部分:认证和授权.认证是告诉系统你是谁,授权是指知道你是谁后是否有权限访问系统(授权后一般会在服务端创建一个token,之后用这个token进行后续行为的交互). spring

  • IOS 调整内存中的图片大小实例详解

    IOS 调整内存中的图片大小实例详解 在从网路download图片,或者从相册读取图片的时候,如果ImageView的本身就是固定的300*200,那么载入2000*2000的图片是很浪费内存的. 2000*2000的内存占用是2000*2000*4bit 以下两个函数可以用来创建一个新的按照固定大小的图片.简单来说,就是Core Graphics来创建一个bitmap,然后生成一个图片. - (UIImage*)imageWithImage:(UIImage*)image scaledToSi

  • Mysql中Join的使用实例详解

    在前几章节中,我们已经学会了如果在一张表中读取数据,这是相对简单的,但是在真正的应用中经常需要从多个数据表中读取数据. 本章节我们将向大家介绍如何使用MySQL 的 JOIN 在两个或多个表中查询数据. 你可以在SELECT, UPDATE 和 DELETE 语句中使用Mysql 的 join 来联合多表查询. 以下我们将演示MySQL LEFT JOIN 和 JOIN 的使用的不同之处. 在命令提示符中使用JOIN 我们在RUNOOB数据库中有两张表 tcount_tbl 和 runoob_t

  • Android 中 Tweened animation的实例详解

    Android 中 Tweened animation的实例详解 Tweened animation有四种类型,下面主要介绍Scale类型. 运行效果如下: Android SDK提供了2种方法:直接从XML资源中读取Animation,使用Animation子类的构造函数来初始化Animation对象,第二种方法在看了Android SDK中各个类的说明就知道如何使用了,下面简要说明从XML资源中读取Animation.XML资源中的动画文件animation.xml内容为: <?xml ve

  • JavaScript 中调用 Kotlin 方法实例详解

    JavaScript 中调用 Kotlin 方法实例详解 Kotlin 编译器生成正常的 JavaScript 类,可以在 JavaScript 代码中自由地使用的函数和属性 .不过,你应该记住一些微妙的事情. 用独立的 JavaScript 隔离声明 为了防止损坏全局对象,Kotlin 创建一个包含当前模块中所有 Kotlin 声明的对象 .所以如果你把模块命名为 myModule,那么所有的声明都可以通过 myModule 对象在 JavaScript 中可用.例如: fun foo() =

  • Android中mvp模式使用实例详解

    MVP 是从经典的模式MVC演变而来,它们的基本思想有相通的地方:Controller/Presenter负责逻辑的处理,Model提供数据,View负 责显示.作为一种新的模式,MVP与MVC有着一个重大的区别:在MVP中View并不直接使用Model,它们之间的通信是通过Presenter (MVC中的Controller)来进行的,所有的交互都发生在Presenter内部,而在MVC中View会从直接Model中读取数据而不是通过 Controller. 在MVC里,View是可以直接访问

随机推荐