C#における構造体定義とバイト配列変換の詳細

6495 ワード

最近のプロジェクトはsocket通信メッセージ解析を行う際に,構造体とバイト配列の変換を用いた.クライアントはC++開発を採用し、サービス側はC#開発を採用するため、双方が各カスタム構造体のメンバータイプと長さが一致することを保証しなければ、メッセージ解析の正確性を保証できないことが重要である.
まず、構造体の定義です.いくつかの基本的なデータ型は、C#とC++が一致します.

  [StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
  public struct Head
  {
    public ushort proMagic;     //     :  0x7e7e
    public ushort proPackLen;    //   :   +     +     ,            
    public long  proSrcAddr;    //   :   , 0
    public ushort proSrcPort;    //     :   , 0
    public long  proDstAddr;    //    :   , 0
    public ushort proDstPort;    //    :   , 0
    public ushort proCmdCode;    //   :         

    public ushort proVersion;    //   :   , 1
    public char  proSerial;     //    :            ,      ,0-255  
    public ushort proPackSum;    //   :            ,    ,       ,    1
    public ushort proPackId;     //    :            ,    0

  }


一、まず[StructLayoutAttribute(LayoutKind.Sequential,CharSet=CharSet.Ansi,Pack=1)]であり、これはC#が管理されていないC/C++のDLLを参照する定義構造体を定義する方法であり、主にメモリのソートのためであり、LayoutKindには2つの属性SequentialとExplicitがあり、Sequentialは順序記憶を表し、構造内のデータはメモリに順次格納、CharSet=CharSet.Ansiは符号化方式を表す.これはすべて管理されていないポインタを使うために用意されています.この2つを覚えておけばいいです.
注意が必要なのはPack=1という特性で、それは構造体のバイトの位置合わせ方式を代表して、実際の開発の中で、C++開発環境はデフォルトで2バイトの位置合わせ方式で、上の報告文の包頭構造体を例にして、charタイプはメモリの中で1バイトを占有しますが、構造体がバイトの配列に変わる時、システムは自動的に2バイトを補充します.したがって、C#の面をPack=1、C++のデフォルトを2バイト揃えと定義すると、双方の構造体に長さが一致しない場合があり、互いに変換する際に必然的にずれが発生するため、1バイト揃えをデフォルトで設定する必要があり、C#定義Pack=1、C++に#pragma pack 1を追加し、構造体におけるバイト揃えが一致することを保証する.
二、配列の定義、構造体の各メンバーの長さは明確である必要がある.メモリはこの分配空間に基づいている必要があるため、C#構造体の配列は初期化できない.ここではメンバー宣言時に定義する必要がある.

  /// 
  ///       
  /// 
  [StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
  public struct PackTerminalSearch5001
  {
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 6)]
    /// 
    ///     
    /// 
    public string stationCode;

    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
    /// 
    ///     
    /// 
    public Byte[] order;
  }
  /// 
  ///       
  /// 

  [StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
  public struct PackTerminalSearch3004
  {
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 6)]
    /// 
    ///     
    /// 
    public string stationCode;
    /// 
    ///   IP
    /// 
    public long terminalIP;
    /// 
    ///     
    /// 
    public ushort terminalPort;
    /// 
    ///   IP
    /// 
    public long serverIP;
    /// 
    ///     
    /// 
    public ushort serverPort;
    /// 
    ///       
    /// 
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
    public PackDiskInfo[] diskInfoArray;
  }

  /// 
  ///     
  /// 
  [StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
  public struct PackDiskInfo
  {
    /// 
    ///   
    /// 
    public char drive;
    /// 
    ///    
    /// 
    public double totalSize;
    /// 
    ///     
    /// 
    public double usableSize;
  }


 
上のコードはstringタイプが実際にChar[6]の長さの配列であることに注意しなければならない.実際の使用では、char[6]の最後のデフォルト0のため、最初の5文字しか有効に使用できない.
三、構造体とバイト配列の相互回転

    PackTerminalSearch5001 info;
    info.stationCode = "12345";
    info.order = new byte[6] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05 };
    Byte[] recv = StructToBytes(info);

    object obj = BytesToStuct(recv, typeof(PackTerminalSearch5001));
    PackTerminalSearch5001 info5001 = (PackTerminalSearch5001)obj;
    byte[] order = info5001.order;


    //// 
    ///     byte  
    /// 
    ///        
    ///     byte  
    public static byte[] StructToBytes(object structObj)
    {
      //        
      int size = Marshal.SizeOf(structObj);
      //  byte  
      byte[] bytes = new byte[size];
      //            
      IntPtr structPtr = Marshal.AllocHGlobal(size);
      //              
      Marshal.StructureToPtr(structObj, structPtr, false);
      //       byte  
      Marshal.Copy(structPtr, bytes, 0, size);
      //      
      Marshal.FreeHGlobal(structPtr);
      //  byte  
      return bytes;
    }

    /// 
    /// byte      
    /// 
    /// byte  
    ///      
    ///        
    public static object BytesToStuct(byte[] bytes, Type type)
    {
      //        
      int size = Marshal.SizeOf(type);
      //byte            
      if (size > bytes.Length)
      {
        //   
        return null;
      }
      //            
      IntPtr structPtr = Marshal.AllocHGlobal(size);
      // byte            
      Marshal.Copy(bytes, 0, structPtr, size);
      //             
      object obj = Marshal.PtrToStructure(structPtr, type);
      //      
      Marshal.FreeHGlobal(structPtr);
      //     
      return obj;
    }

C#では構造とクラスが驚くべき類似度を持っていますが、実際の応用では、いくつかの特殊ななどのために誤って使用されることがよくあります.以下の点は筆者が注意すべきことです.
構造の場合
1)方法と属性2)を密封する、継承できない、または他の構造3)構造を暗黙的にSystemから継承することができる.ValueType 4)構造にはデフォルトの無パラメータ構造関数があり、各フィールドをデフォルト値に初期化することができるが、このデフォルトの構造関数を置き換えることはできない.パラメータ付き構造関数5をリロードした場合)構造に構造解析関数がない6)constメンバーを除き、構造のワードセグメントは構造宣言時に初期化できない7)構造は値タイプである.定義時には(new演算子も使用するが)スタックスペースが割り当てられ、その値もスタック8に格納される)構造は主に小さなデータ構造に用いられ、より良い性能のために膨大な構造9は使用しない)クラスのように構造にClose()またはDispose()メソッドを提供することができる
通信に関するプログラムを頻繁に行うと、構造体は非常に有用です(データをより効率的に整理するために、構造体を使用することをお勧めします)