C#で構造体をバイト配列に変換する方法は?


83

C#で構造体をバイト配列に変換するにはどうすればよいですか?

私はこのような構造を定義しました:

public struct CIFSPacket
{
    public uint protocolIdentifier; //The value must be "0xFF+'SMB'".
    public byte command;

    public byte errorClass;
    public byte reserved;
    public ushort error;

    public byte flags;

    //Here there are 14 bytes of data which is used differently among different dialects.
    //I do want the flags2. However, so I'll try parsing them.
    public ushort flags2;

    public ushort treeId;
    public ushort processId;
    public ushort userId;
    public ushort multiplexId;

    //Trans request
    public byte wordCount;//Count of parameter words defining the data portion of the packet.
    //From here it might be undefined...

    public int parametersStartIndex;

    public ushort byteCount; //Buffer length
    public int bufferStartIndex;

    public string Buffer;
}

私のメインメソッドでは、そのインスタンスを作成し、それに値を割り当てます。

CIFSPacket packet = new CIFSPacket();
packet.protocolIdentifier = 0xff;
packet.command = (byte)CommandTypes.SMB_COM_NEGOTIATE;
packet.errorClass = 0xff;
packet.error = 0;
packet.flags = 0x00;
packet.flags2 = 0x0001;
packet.multiplexId = 22;
packet.wordCount = 0;
packet.byteCount = 119;

packet.Buffer = "NT LM 0.12";

このパケットをソケットで送信したいと思います。そのためには、構造体をバイト配列に変換する必要があります。どうすればいいですか?

私の完全なコードは次のとおりです。

static void Main(string[] args)
{

  Socket MyPing = new Socket(AddressFamily.InterNetwork,
  SocketType.Stream , ProtocolType.Unspecified ) ;


  MyPing.Connect("172.24.18.240", 139);

    //Fake an IP Address so I can send with SendTo
    IPAddress IP = new IPAddress(new byte[] { 172,24,18,240 });
    IPEndPoint IPEP = new IPEndPoint(IP, 139);

    //Local IP for Receiving
    IPEndPoint Local = new IPEndPoint(IPAddress.Any, 0);
    EndPoint EP = (EndPoint)Local;

    CIFSPacket packet = new CIFSPacket();
    packet.protocolIdentifier = 0xff;
    packet.command = (byte)CommandTypes.SMB_COM_NEGOTIATE;
    packet.errorClass = 0xff;
    packet.error = 0;
    packet.flags = 0x00;
    packet.flags2 = 0x0001;
    packet.multiplexId = 22;
    packet.wordCount = 0;
    packet.byteCount = 119;

    packet.Buffer = "NT LM 0.12";

    MyPing.SendTo(It takes byte array as parameter);
}

コードスニペットは何でしょうか?


最後の行で1つの修正MyPing.Send(パラメーターとしてバイト配列を取ります); それはないのSendTo ......送信される
Swapnilグプタ

こんにちはペタル、私はあなたを取得できませんでした...
Swapnil Gupta 2010

3
以前の質問に対するいくつかの回答を受け入れるのは良いことかもしれません。
jnoss 2010

1
期待する出力についてもう少し具体的にすると役立つと思います。それをバイトに変換する方法はたくさんあります[] ...おそらく、フィールドのフィールド順ネットワークバイト順固定サイズ表現が必要であるといういくつかの仮定を立てることができますが、どうでしょうか。文字列?
MarcGravell

マーシャルオプションを選択した場合は、グランドエンディアンとリトルエンディアン、および約32ビット/ 64ビットに注意してください。
x77 2010

回答:


127

これは、マーシャリングを使用するとかなり簡単です。

ファイルの先頭

using System.Runtime.InteropServices

関数

byte[] getBytes(CIFSPacket str) {
    int size = Marshal.SizeOf(str);
    byte[] arr = new byte[size];

    IntPtr ptr = Marshal.AllocHGlobal(size);
    Marshal.StructureToPtr(str, ptr, true);
    Marshal.Copy(ptr, arr, 0, size);
    Marshal.FreeHGlobal(ptr);
    return arr;
}

そしてそれを元に戻すには:

CIFSPacket fromBytes(byte[] arr) {
    CIFSPacket str = new CIFSPacket();

    int size = Marshal.SizeOf(str);
    IntPtr ptr = Marshal.AllocHGlobal(size);

    Marshal.Copy(arr, 0, ptr, size);

    str = (CIFSPacket)Marshal.PtrToStructure(ptr, str.GetType());
    Marshal.FreeHGlobal(ptr);

    return str;
}

構造体では、これを文字列の前に置く必要があります

[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 100)]
public string Buffer;

そして、SizeConstが可能な限り最大の文字列と同じ大きさであることを確認してください。

そして、おそらくこれを読む必要があります:http//msdn.microsoft.com/en-us/library/4ca6d5z7.aspx


Vincetに感謝します。GetBytes()は、byte [] ??を送信した後に呼び出す必要があります。そしてfrombytes()メソッドはバイトを送信していますか?私は少し混乱した仲間ですか?
Swapnil Gupta 2010

1
GetBytesは、構造体から配列に変換します。FromBytesは、Bytesから構造体に変換し直します。これは、関数のシグネチャから明らかです。
ヴィンセント・マクナブ2010

1
@Swapnilこれは別の質問であり、別途質問する必要があります。ソケットに関するCEチュートリアルをいくつか完了することを検討する必要があります。Googleを検索するだけです。
ヴィンセント・マクナブ2010

3
fromBytesメソッドでは、CIFSPacketを2回割り当てる必要はありません。Marshal.SizeOfは、Typeをパラメーターとして受け取り、Marshal.PtrToStructureは新しい管理対象オブジェクトを割り当てます。
Jack Ukleja 2014年

1
状況によっては、関数«StructureToPtr»が例外をスローすることに注意してください。これは、«true»の代わりに«false»をに渡すことで修正できますMarshal.StructureToPtr(str, ptr, false);。しかし、私はジェネリックにラップされた関数を使用していることに言及する必要があります…
Hi-Angel

30

Windowsで本当に高速にしたい場合は、CopyMemoryで安全でないコードを使用して実行できます。CopyMemoryは約5倍高速です(たとえば、800MBのデータはマーシャリングを介してコピーするのに3秒かかりますが、CopyMemoryを介してコピーするのに0.6秒しかかかりません)。この方法では、実際に構造体BLOB自体に格納されているデータ(数値や固定長のバイト配列など)のみを使用するように制限されます。

    [DllImport("kernel32.dll", EntryPoint = "CopyMemory", SetLastError = false)]
    private static unsafe extern void CopyMemory(void *dest, void *src, int count);

    private static unsafe byte[] Serialize(TestStruct[] index)
    {
        var buffer = new byte[Marshal.SizeOf(typeof(TestStruct)) * index.Length];
        fixed (void* d = &buffer[0])
        {
            fixed (void* s = &index[0])
            {
                CopyMemory(d, s, buffer.Length);
            }
        }

        return buffer;
    }

4
この回答を読んでいる人への注意として..これはクロスプラットフォームフレンドリーではありません(Windowsのみkernel32.dllを使用します)。しかし、再び、それは2014年に書かれました。:)
襲撃

2
さらに、構造がシーケンシャルである必要があります。
TomerW20年

25

これらの方法を見てください:

byte [] StructureToByteArray(object obj)
{
    int len = Marshal.SizeOf(obj);

    byte [] arr = new byte[len];

    IntPtr ptr = Marshal.AllocHGlobal(len);

    Marshal.StructureToPtr(obj, ptr, true);

    Marshal.Copy(ptr, arr, 0, len);

    Marshal.FreeHGlobal(ptr);

    return arr;
}

void ByteArrayToStructure(byte [] bytearray, ref object obj)
{
    int len = Marshal.SizeOf(obj);

    IntPtr i = Marshal.AllocHGlobal(len);

    Marshal.Copy(bytearray,0, i,len);

    obj = Marshal.PtrToStructure(i, obj.GetType());

    Marshal.FreeHGlobal(i);
}

これは、グーグルで見つけた別のスレッドの恥知らずなコピーです!

更新:詳細については、ソースを確認してください


マーシャリングを使用して構造体をバイト配列に変換しましたが、ソケットから応答を取得しているかどうかを確認するにはどうすればよいですか?それを確認する方法は?
Swapnil Gupta 2010

@アラステア、私はそれを逃した!それを指摘してくれてありがとう..私は私の答えを更新しました。
Abdel Raoof 2010

2
このオプションはプラットフォームに依存します-グランドエンディアンとリトルエンディアン、および約32ビット/ 64ビットに注意してください。
x77 2010

@Abdel、そして-1はなくなった:)
Alastair Pitts

Allocを実行し、真ん中のビットを試してラップし、最後にFreeを中に入れるのは理にかなっていますか?物事が失敗する可能性は低いようですが、失敗した場合、メモリが解放されることはありますか?
ケーシー

18

メモリ割り当てが1つ少ないVicentのコードのバリアント:

public static byte[] GetBytes<T>(T str)
{
    int size = Marshal.SizeOf(str);

    byte[] arr = new byte[size];

    GCHandle h = default(GCHandle);

    try
    {
        h = GCHandle.Alloc(arr, GCHandleType.Pinned);

        Marshal.StructureToPtr<T>(str, h.AddrOfPinnedObject(), false);
    }
    finally
    {
        if (h.IsAllocated)
        {
            h.Free();
        }
    }

    return arr;
}

public static T FromBytes<T>(byte[] arr) where T : struct
{
    T str = default(T);

    GCHandle h = default(GCHandle);

    try
    {
        h = GCHandle.Alloc(arr, GCHandleType.Pinned);

        str = Marshal.PtrToStructure<T>(h.AddrOfPinnedObject());

    }
    finally
    {
        if (h.IsAllocated)
        {
            h.Free();
        }
    }

    return str;
}

GCHandleはメモリを「固定」するために使用し、次にそのアドレスをh.AddrOfPinnedObject()。で直接使用します。


where T : structそれ以外の場合は削除する必要Tがありますが、合格であるという苦情はありませんnon-nullable type
codenamezero

GCHandle.Alloc構造体に分割不可能なデータ(配列など)がある場合は失敗します
joe

@joeその通りです。コードは、blittable型とstring。のみを含む特定の構造体用に記述されています。
ザナトス

5

主な答えは、C#では使用できない(または使用できなくなった)CIFSPacketタイプを使用することなので、正しいメソッドを作成しました。

    static byte[] getBytes(object str)
    {
        int size = Marshal.SizeOf(str);
        byte[] arr = new byte[size];
        IntPtr ptr = Marshal.AllocHGlobal(size);

        Marshal.StructureToPtr(str, ptr, true);
        Marshal.Copy(ptr, arr, 0, size);
        Marshal.FreeHGlobal(ptr);

        return arr;
    }

    static T fromBytes<T>(byte[] arr)
    {
        T str = default(T);

        int size = Marshal.SizeOf(str);
        IntPtr ptr = Marshal.AllocHGlobal(size);

        Marshal.Copy(arr, 0, ptr, size);

        str = (T)Marshal.PtrToStructure(ptr, str.GetType());
        Marshal.FreeHGlobal(ptr);

        return str;
    }

テスト済みで、動作します。


4

これは本当に遅いことはわかっていますが、C#7.3では、管理されていない構造体やその他の管理されていないもの(int、boolなど)に対してこれを行うことができます。

public static unsafe byte[] ConvertToBytes<T>(T value) where T : unmanaged {
        byte* pointer = (byte*)&value;

        byte[] bytes = new byte[sizeof(T)];
        for (int i = 0; i < sizeof(T); i++) {
            bytes[i] = pointer[i];
        }

        return bytes;
    }

次に、次のように使用します。

struct MyStruct {
        public int Value1;
        public int Value2;
        //.. blah blah blah
    }

    byte[] bytes = ConvertToBytes(new MyStruct());

2

Marshal(StructureToPtr、ptrToStructure)、およびMarshal.copyを使用できますが、これはプラットフォームに依存します。


シリアル化には、カスタムシリアル化の機能が含まれます。

public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
Protected Sub New(ByVal info As SerializationInfo, ByVal context As StreamingContext) 

SerializationInfoには、各メンバーをシリアル化する関数が含まれています。


BinaryWriterとBinaryReaderには、バイト配列(ストリーム)に保存/ロードするメソッドも含まれています。

バイト配列からMemoryStreamを作成することも、MemoryStreamからバイト配列を作成することもできることに注意してください。

構造にSaveメソッドとNewメソッドを作成できます。

   Save(Bw as BinaryWriter)
   New (Br as BinaryReader)

次に、保存/ストリームへのロード->バイト配列のメンバーを選択します。


1

これは非常に簡単に行うことができます。

構造体を明示的に定義する [StructLayout(LayoutKind.Explicit)]

int size = list.GetLength(0);
IntPtr addr = Marshal.AllocHGlobal(size * sizeof(DataStruct));
DataStruct *ptrBuffer = (DataStruct*)addr;
foreach (DataStruct ds in list)
{
    *ptrBuffer = ds;
    ptrBuffer += 1;
}

このコードは、安全でない状況でのみ記述できます。使いaddr終わったら解放しなければなりません。

Marshal.FreeHGlobal(addr);

固定サイズのコレクションで明示的な順序付けされた操作を実行する場合は、おそらく配列とforループを使用する必要があります。配列は固定サイズであるため、forループはforeachが期待どおりの順序であることが保証されていないためです。ただし、リストのタイプとその列挙子の基本的な実装がわかっていて、変更されることはありません。たとえば、列挙子を最後から開始して逆方向に進むように定義できます。

1

私は任意のものを変換できる別のアプローチを考え出しました structしかし、結果のバイト配列は、より多くのオーバーヘッドが少しを持っているでしょう、長さを固定する手間をかけずに。

ここにサンプルがありますstruct

[StructLayout(LayoutKind.Sequential)]
public class HelloWorld
{
    public MyEnum enumvalue;
    public string reqtimestamp;
    public string resptimestamp;
    public string message;
    public byte[] rawresp;
}

ご覧のとおり、これらの構造はすべて、固定長の属性を追加する必要があります。多くの場合、必要以上のスペースを占めることになります。をLayoutKind.Sequentialプルするときにリフレクションが常に同じ順序を与えるようにするため、が必要であることに注意してくださいFieldInfo。私のインスピレーションはTLVType-Length-Valueからです。コードを見てみましょう:

public static byte[] StructToByteArray<T>(T obj)
{
    using (MemoryStream ms = new MemoryStream())
    {
        FieldInfo[] infos = typeof(T).GetFields(BindingFlags.Public | BindingFlags.Instance);
        foreach (FieldInfo info in infos)
        {
            BinaryFormatter bf = new BinaryFormatter();
            using (MemoryStream inms = new MemoryStream()) {

                bf.Serialize(inms, info.GetValue(obj));
                byte[] ba = inms.ToArray();
                // for length
                ms.Write(BitConverter.GetBytes(ba.Length), 0, sizeof(int));

                // for value
                ms.Write(ba, 0, ba.Length);
            }
        }

        return ms.ToArray();
    }
}

上記の関数は、単にを使用しBinaryFormatterて不明なサイズをrawobjectでシリアル化し、サイズも追跡して出力内に格納するだけMemoryStreamです。

public static void ByteArrayToStruct<T>(byte[] data, out T output)
{
    output = (T) Activator.CreateInstance(typeof(T), null);
    using (MemoryStream ms = new MemoryStream(data))
    {
        byte[] ba = null;
        FieldInfo[] infos = typeof(T).GetFields(BindingFlags.Public | BindingFlags.Instance);
        foreach (FieldInfo info in infos)
        {
            // for length
            ba = new byte[sizeof(int)];
            ms.Read(ba, 0, sizeof(int));

            // for value
            int sz = BitConverter.ToInt32(ba, 0);
            ba = new byte[sz];
            ms.Read(ba, 0, sz);

            BinaryFormatter bf = new BinaryFormatter();
            using (MemoryStream inms = new MemoryStream(ba))
            {
                info.SetValue(output, bf.Deserialize(inms));
            }
        }
    }
}

structの長さに戻す場合は、長さを読み取り、直接ダンプしBinaryFormatterて、にダンプしますstruct

これらの2つの関数は汎用であり、どの関数でも機能するはずstructです。C#サーバーとクライアントがあり、接続して通信しNamedPipeStreamstructバイト配列として転送して変換し直すプロジェクトで上記のコードをテストしました。 。

私のアプローチの方が良いかもしれないと思います。なぜなら、structそれ自体の長さは固定されず、唯一のオーバーヘッドはint、構造体にあるすべてのフィールドに対するものだからです。によって生成されたバイト配列内にもわずかなオーバーヘッドがありますBinaryFormatterが、それ以外はそれほど多くありません。


6
一般に、人々がそのようなものを処理しようとするとき、彼らはシリアル化のパフォーマンスについても心配します。理論的には、構造体の配列は、コストのかかるシリアル化やコピーを必要とせずに、バイト配列として再解釈できます。
TanveerBadar19年


0

一部の外部ライブラリの事前定義された(Cレベル)構造のように見えます。元帥はあなたの友達です。小切手:

http://geekswithblogs.net/taylorrich/archive/2006/08/21/88665.aspx

手始めにこれに対処する方法。属性を使用して、バイトレイアウトや文字列処理などを定義できることに注意してください。実際、非常に素晴らしいアプローチです。

BinaryFormatterもMemoryStreamもそのために行われません。


0

@Abdel Olakaraの回答は、.net 3.5では機能しません。以下のように変更する必要があります:

    public static void ByteArrayToStructure<T>(byte[] bytearray, ref T obj)
    {
        int len = Marshal.SizeOf(obj);
        IntPtr i = Marshal.AllocHGlobal(len);
        Marshal.Copy(bytearray, 0, i, len);
        obj = (T)Marshal.PtrToStructure(i, typeof(T));
        Marshal.FreeHGlobal(i);
    }

0
        Header header = new Header();
        Byte[] headerBytes = new Byte[Marshal.SizeOf(header)];
        Marshal.Copy((IntPtr)(&header), headerBytes, 0, headerBytes.Length);

これですぐにうまくいくはずですよね?


GCHandleバージョンははるかに優れています。
ПетърПетров

0

ここでのこの例は、純粋なblittableタイプ、たとえばCで直接memcpyできるタイプにのみ適用できます。

例-よく知られている64ビット構造体

[StructLayout(LayoutKind.Sequential)]  
public struct Voxel
{
    public ushort m_id;
    public byte m_red, m_green, m_blue, m_alpha, m_matid, m_custom;
}

このように定義すると、構造体は自動的に64ビットとしてパックされます。

これで、ボクセルのボリュームを作成できます。

Voxel[,,] voxels = new Voxel[16,16,16];

そして、それらをすべてバイト配列に保存します。

int size = voxels.Length * 8; // Well known size: 64 bits
byte[] saved = new byte[size];
GCHandle h = GCHandle.Alloc(voxels, GCHandleType.Pinned);
Marshal.Copy(h.AddrOfPinnedObject(), saved, 0, size);
h.Free();
// now feel free to save 'saved' to a File / memory stream.

ただし、OPは構造体自体を変換する方法を知りたいので、Voxel構造体は次のメソッドを持つことができますToBytes

byte[] bytes = new byte[8]; // Well known size: 64 bits
GCHandle h = GCHandle.Alloc(this, GCHandleType.Pinned);
Marshal.Copy(hh.AddrOfPinnedObject(), bytes, 0, 8);
h.Free();
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.