web-dev-qa-db-ja.com

バイト配列をマネージ構造にキャストする

更新:この質問への回答は、オープンソースプロジェクトのコーディングに役立ちました GitHub上のAlicanCのModern Warfare 2ツールMW2Packets.cs でこれらのパケットを読み取る方法と、ビッグエンディアンデータを読み取るようにコーディングした拡張機能を Extensions.csで確認できます。

私のC#アプリケーションで Pcap.Net を使用して、 Call of Duty:Modern Warfare 2 のUDPパケットをキャプチャしています。ライブラリからbyte[]を受け取りました。文字列のように解析しようとしましたが、うまくいきませんでした。

byte[]には一般的なパケットヘッダーがあり、次にパケットタイプに固有の別のヘッダーがあり、その後ロビーの各プレーヤーに関する情報があります。

親切な人が私のためにいくつかのパケットを検査し、これらの構造を考え出しました:

// Fields are big endian unless specified otherwise.
struct packet_header
{
    uint16_t magic;
    uint16_t packet_size;
    uint32_t unknown1;
    uint32_t unknown2;
    uint32_t unknown3;
    uint32_t unknown4;
    uint16_t unknown5;
    uint16_t unknown6;
    uint32_t unknown7;
    uint32_t unknown8;
    cstring_t packet_type; // \0 terminated string
};

// Fields are little endian unless specified otherwise.
struct header_partystate //Header for the "partystate" packet type
{
    uint32_t unknown1;
    uint8_t unknown2;
    uint8_t player_entry_count;
    uint32_t unknown4;
    uint32_t unknown5;
    uint32_t unknown6;
    uint32_t unknown7;
    uint8_t unknown8;
    uint32_t unknown9;
    uint16_t unknown10;
    uint8_t unknown11;
    uint8_t unknown12[9];
    uint32_t unknown13;
    uint32_t unknown14;
    uint16_t unknown15;
    uint16_t unknown16;
    uint32_t unknown17[10];
    uint32_t unknown18;
    uint32_t unknown19;
    uint8_t unknown20;
    uint32_t unknown21;
    uint32_t unknown22;
    uint32_t unknown23;
};

// Fields are little endian unless specified otherwise.
struct player_entry
{
    uint8_t player_id;

    // The following fields may not actually exist in the data if it's an empty entry.
    uint8_t unknown1[3];
    cstring_t player_name;
    uint32_t unknown2;
    uint64_t Steam_id;
    uint32_t internal_ip;
    uint32_t external_ip;
    uint16_t unknown3;
    uint16_t unknown4;
    uint32_t unknown5;
    uint32_t unknown6;
    uint32_t unknown7;
    uint32_t unknown8;
    uint32_t unknown9;
    uint32_t unknown10;
    uint32_t unknown11;
    uint32_t unknown12;
    uint16_t unknown13;
    uint8_t unknown14[???];     // Appears to be a bit mask, sometimes the length is zero, sometimes it's one. (First entry is always zero?)
    uint8_t unknown15;
    uint32_t unknown16;
    uint16_t unknown17;
    uint8_t unknown18[???];     // Most of the time this is 4 bytes, other times it is 3 bytes.
};

次のように、C#アプリケーションでパケットヘッダー構造を再作成しました。

[StructLayout(LayoutKind.Sequential, Pack=1)]
struct PacketHeader
{
    public UInt16 magic;
    public UInt16 packetSize;
    public UInt32 unknown1;
    public UInt32 unknown2;
    public UInt32 unknown3;
    public UInt32 unknown4;
    public UInt16 unknown5;
    public UInt16 unknown6;
    public UInt32 unknown7;
    public UInt32 unknown8;
    public String packetType;
}

次に、「partystate」ヘッダーの構造を作成しようとしましたが、fixedキーワードが安全でないというエラーが発生しました。

[StructLayout(LayoutKind.Sequential, Pack=1)]
struct PartyStateHeader
{
    UInt32 unknown1;
    Byte unknown2;
    Byte playerEntryCount;
    UInt32 unknown4;
    UInt32 unknown5;
    UInt32 unknown6;
    UInt32 unknown7;
    Byte unknown8;
    UInt32 unknown9;
    UInt16 unknown10;
    Byte unknown11;
    fixed Byte unknown12[9];
    UInt32 unknown13;
    UInt32 unknown14;
    UInt16 unknown15;
    UInt16 unknown16;
    fixed UInt32 unknown17[10];
    UInt32 unknown18;
    UInt32 unknown19;
    Byte unknown20;
    UInt32 unknown21;
    UInt32 unknown22;
    UInt32 unknown23;
}

unknown14unknown18のサイズが異なるため、プレーヤーのエントリに対して何もできませんでした。 (プレイヤーのエントリーが最も重要です。)

どういうわけか、byte[]をこれらのPacketHeader構造体にキャストする必要があります。残念ながら、それは(PacketHeader)bytesほど簡単ではありません。インターネットで見つけたこの方法を試しましたが、AccessViolationExceptionがスローされました:

GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
PacketHeader packetHeader = (PacketHeader)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(PacketHeader));

どうすればこれを達成できますか?

18
AlicanC

バイト配列をメモリストリームに変換します。次に、そのストリームでバイナリリーダーをインスタンス化します。次に、バイナリリーダーを使用して単一のクラスを解析するヘルパー関数を定義します。

組み込みのBinaryReaderクラスは常にリトルエンディアンを使用します。

ここでは構造体の代わりにクラスを使用します。

class PacketHeader 
{
    uint16_t magic;
    uint16_t packet_size;
    uint32_t unknown1;
    uint32_t unknown2;
    uint32_t unknown3;
    uint32_t unknown4;
    uint16_t unknown5;
    uint16_t unknown6;
    uint32_t unknown7;
    uint32_t unknown8;
    string packet_type; // replaced with a real string
};

PacketHeader ReadPacketHeader(BinaryReader reader)
{
  var result=new PacketHeader();
  result.magic = reader.ReadInt16();
  ...
  result.packet_type=ReadCString();//Some helper function you might need to define yourself
  return result;
}
5
CodesInChaos

//私はこれを次の場所で見つけました: http://code.cheesydesign.com/?p=572 (まだテストしていませんが、//一見すると、うまく機能します。)

    /// <summary>
    /// Reads in a block from a file and converts it to the struct
    /// type specified by the template parameter
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="reader"></param>
    /// <returns></returns>
    private static T FromBinaryReader<T>(BinaryReader reader)
    {

        // Read in a byte array
        byte[] bytes = reader.ReadBytes(Marshal.SizeOf(typeof(T)));

        // Pin the managed memory while, copy it out the data, then unpin it
        GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
        T theStructure = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T));
        handle.Free();

        return theStructure;
    }
18
Roberto Santos

これは私がやった方法です:

using System;
using System.Runtime.InteropServices;
public static object GetObjectFromBytes(byte[] buffer, Type objType)
{
    object obj = null;
    if ((buffer != null) && (buffer.Length > 0))
    {
        IntPtr ptrObj = IntPtr.Zero;
        try
        {
            int objSize = Marshal.SizeOf(objType);
            if (objSize > 0)
            {
                if (buffer.Length < objSize)
                    throw new Exception(String.Format("Buffer smaller than needed for creation of object of type {0}", objType));
                ptrObj = Marshal.AllocHGlobal(objSize);
                if (ptrObj != IntPtr.Zero)
                {
                    Marshal.Copy(buffer, 0, ptrObj, objSize);
                    obj = Marshal.PtrToStructure(ptrObj, objType);
                }
                else
                    throw new Exception(String.Format("Couldn't allocate memory to create object of type {0}", objType));
            }
        }
        finally
        {
            if (ptrObj != IntPtr.Zero)
                Marshal.FreeHGlobal(ptrObj);
        }
    }
    return obj;
}

構造体の定義では、fixed領域を使用しませんでしたが、標準のマーシャリングが機能しない場合は、代わりにMarshalAs属性を使用しました。これはおそらく文字列に必要なものです。

この関数は次のように使用します。

PacketHeader ph = (PacketHeader)GetObjectFromBytes(buffer, typeof(PacketHeader));

編集:コード例にBigEndianの「制限」が表示されませんでした。このソリューションは、バイトがLittleEndianの場合にのみ機能します。

編集2:あなたの例の文字列で、あなたはそれを次のように装飾します:

[MarshalAs(UnmanagedType.LPStr)]

配列では、nサイズの配列に対して次のようなものを使用します。

[MarshalAs(UnmanagedType.ByValArray, SizeConst = n)]
6
Vladimir

さて、あなたは本当にここに2つのタスクがあります。 1つ目は本質的にbyte []を構造体として解釈することであり、2つ目は考えられる異なるエンディアンを処理することです。

そのため、それらはやや異なっています。マーシャリングを使用する場合は、AFAIK-バイトを、管理されている構造であるかのように解釈します。したがって、あるエンディアンから別のエンディアンへの変換はあなたに任されています。難しくはありませんが、自動ではありません。

したがって、byte []を構造体として解釈するには、次のようなものが必要です。

[StructLayout(LayoutKind.Sequential)]
internal struct X
{
    public int IntValue;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3, ArraySubType = UnmanagedType.U1)] 
    public byte[] Array;
}

static void Main(string[] args)
{
    byte[] data = {1, 0, 0, 0, 9, 8, 7}; // IntValue = 1, Array = {9,8,7}
    IntPtr ptPoit = Marshal.AllocHGlobal(data.Length);
    Marshal.Copy(data, 0, ptPoit, data.Length);
    var x = (X) Marshal.PtrToStructure(ptPoit, typeof (X));
    Marshal.FreeHGlobal(ptPoit);

    Console.WriteLine("x.IntValue = {0}", x.IntValue);
    Console.WriteLine("x.Array = ({0}, {1}, {2})", x.Array[0], x.Array[1], x.Array[2]);
}

したがって、最初の4バイトはIntValue(1,0,0,0)に移動します-> [リトルエンディアン]-> 1次の3バイトは直接配列に移動します。

BigEndianが必要な場合は、自分で行う必要があります。

int LittleToBigEndian(int littleEndian)
{
    byte[] buf = BitConverter.GetBytes(littleEndian).Reverse().ToArray();
    return BitConverter.ToInt32(buf, 0);
}

それはそのようにやや乱雑なので、おそらく、あなたは、ソースbyte []から1つずつバイトを取得するカスタム作成されたパーサーに固執し、StructLayoutや他のネイティブ相互運用機能なしでデータクラスを埋める方が良いでしょう。

3
Ivan Danilov

C#7.3の機能にアクセスできる人のために、私はこの安全でないコードを使用してバイトを「シリアル化」します。

public static class Serializer
{
    public static unsafe byte[] Serialize<T>(T value) where T : unmanaged
    {
        byte[] buffer = new byte[sizeof(T)];

        fixed (byte* bufferPtr = buffer)
        {
            Buffer.MemoryCopy(&value, bufferPtr, sizeof(T), sizeof(T));
        }

        return buffer;
    }

    public static unsafe T Deserialize<T>(byte[] buffer) where T : unmanaged
    {
        T result = new T();

        fixed (byte* bufferPtr = buffer)
        {
            Buffer.MemoryCopy(bufferPtr, &result, sizeof(T), sizeof(T));
        }

        return result;
    }
}

unmanagedタイプは、構造体(参照タイプのない単純な構造体、マネージド構造体と見なされるもの)、またはintshortなどのネイティブタイプにすることができます。

2
Philippe Paré

私のアプローチは異なります。バイトをコピーしたくありませんでした。
それらを使用し、一部を変更し、変更されたbyte []配列を他の場所でbyte []として使用したかっただけです。
グーグルとスタックオーバーフローを掘り下げた後、私は安全でない/修正済みに入ることにしました。
コードをいじって、コピーなしの高速コードを見つけました。
これはデバッグ/テストコードです。デバッグモードでこれを確認してください。
この方法ではコピーを作成せず、生のバイト[]データで作業していることに注意してください。
構造体の変更はすべて、byte []配列の変更に反映され、その逆も同様です。
++ TESTED ++ WORKS

//FOR DEBUG/TEST ONLY
using System.Runtime.InteropServices;
namespace ByteStructCast1
{
    class Program
    {
        [StructLayout(LayoutKind.Sequential, Pack = 1)]
        unsafe struct StructTest//4B
        {
            [MarshalAs(UnmanagedType.U2)]
            public ushort item1;//2B
            public fixed byte item2[2];//2B =2x 1B
        }
        static void Main(string[] args)
        {
            //managed byte array
            byte[] DB1 = new byte[7];//7B more than we need. byte buffer usually is greater.
            DB1[0] = 2;//test data |> LITTLE ENDIAN
            DB1[1] = 0;//test data |
            DB1[2] = 3;//test data
            DB1[3] = 4;//test data
            unsafe //OK we are going to pin unmanaged struct to managed byte array
            {
                fixed(byte* db1 = DB1) //db1 is pinned pointer to DB1 byte[] array
                {
                    //StructTest t1 = *(StructTest*)db1;    //does not change DB1/db1
                    //t1.item1 = 11;                        //does not change DB1/db1
                    db1[0] = 22;                            //does CHANGE DB1/db1
                    DB1[0] = 33;                            //does CHANGE DB1/db1
                    StructTest* ptest = (StructTest*)db1;   //does CHANGE DB1/db1
                    ptest->item1 = 44;                      //does CHANGE DB1/db1
                    ptest->item2[0]++;                      //does CHANGE DB1/db1
                    ptest->item2[1]--;                      //does CHANGE DB1/db1
                }
            }
        }
    }
}
2
MrHIDEn