C # 'da bir yapı bir bayt dizisine nasıl dönüştürülür?


83

C # 'da bir yapıyı bayt dizisine nasıl dönüştürebilirim?

Şöyle bir yapı tanımladım:

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;
}

Ana yöntemimde bunun bir örneğini oluşturuyorum ve ona değerler atıyorum:

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";

Şimdi bu Paketi soket ile göndermek istiyorum. Bunun için yapıyı bir bayt dizisine dönüştürmem gerekiyor. Nasıl yapabilirim?

Tam kodum aşağıdaki gibidir.

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);
}

Bir kod parçacığı ne olurdu?


Son satırda bir düzeltme MyPing.Send (Parametre olarak bayt dizisini alır); Gönderme Değildir ......
Swapnil Gupta

Merhaba Petar, seni anlamadım ...
Swapnil Gupta

3
Önceki sorularınıza bazı cevapları kabul etmeniz iyi olabilir.
jnoss

1
Beklediğiniz çıktı hakkında biraz daha spesifik olmanın yardımcı olacağından şüpheleniyorum; bunu bir bayta [] dönüştürmenin birçok yolu vardır ... Muhtemelen çoğu hakkında bazı varsayımlar yapabiliriz, alanların alan sıralı ağ bayt sıralı sabit boyutlu gösterimlerini istiyorsunuz - peki ya ip?
Marc Gravell

Marshall seçeneğini seçerseniz Grand Endian ve Little endian'a ve yaklaşık 32 Bit / 64 bit'e dikkat edin.
x77

Yanıtlar:


127

Bu, sıralamayı kullanarak oldukça kolaydır.

Dosyanın üstü

using System.Runtime.InteropServices

Fonksiyon

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;
}

Ve onu geri dönüştürmek için:

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;
}

Yapınızda, bunu bir dizeden önce koymanız gerekecek

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

Ve SizeConst'un mümkün olan en büyük diziniz kadar büyük olduğundan emin olun.

Ve muhtemelen şunu okumalısınız: http://msdn.microsoft.com/en-us/library/4ca6d5z7.aspx


Vincet teşekkürler. GetBytes (), [] baytı gönderildikten sonra çağrılmalıdır? ve frombytes () yöntemi baytları mı gönderiyor? Biraz kafam karıştı dostum?
Swapnil Gupta

1
GetBytes, yapınızdan bir diziye dönüşür. FromBytes, Byte'tan yapınıza geri dönüşür. Bu, işlev imzalarından anlaşılmaktadır.
Vincent McNabb

1
@Swapnil Bu ayrı ayrı sormanız gereken başka bir soru. Soketlerdeki birkaç CE öğreticisini tamamlamayı düşünmelisiniz. Google'da arama yapmanız yeterlidir.
Vincent McNabb

3
FromBytes yönteminizde, CIFSPacket'i iki kez ayırmanıza gerek yoktur. Marshal.SizeOf, parametre olarak mutlu bir şekilde Type alır ve Marshal.PtrToStructure yeni bir yönetilen nesne ayırır.
Jack Ukleja

1
Bazı durumlarda «StructureToPtr» fonksiyonunun bir istisna attığına dikkat edin. Bu, "doğru" yerine "yanlış" geçerek düzeltilebilir Marshal.StructureToPtr(str, ptr, false);. Ama bir jenere sarılmış işlevleri kullandığımı belirtmeliyim ...
Hi-Angel

30

Windows'ta gerçekten HIZLI olmasını istiyorsanız, bunu CopyMemory ile güvenli olmayan kod kullanarak yapabilirsiniz. CopyMemory yaklaşık 5 kat daha hızlıdır (örneğin, 800MB verinin sıralama yoluyla kopyalanması 3 saniye sürerken CopyMemory yoluyla kopyalamak yalnızca .6 saniye sürer). Bu yöntem sizi yalnızca yapı blobunun kendisinde depolanan verileri, örneğin sayılar veya sabit uzunluklu bayt dizilerini kullanmakla sınırlandırmaz.

    [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
Bu cevabı okuyanlara uyarı olarak .. Bu Platformlar arası uyumlu değildir (sadece Windows kernel32.dll kullanır). Ama sonra yine, 2014'te yazıldı. :)
Raid

2
Ayrıca yapının sıralı olmasını gerektirir.
Tomer W

25

Şu yöntemlere bir göz atın:

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);
}

Bu, Google'da bulduğum başka bir konunun utanmaz bir kopyası!

Güncelleme : Daha fazla ayrıntı için kaynağı kontrol edin


Şimdi Marshalling kullanarak byte dizisine çevirici yapısına sahibim.Yanıtı soketten alıp almadığımı nasıl kontrol edebilirim? Bunu nasıl kontrol edebilirim?
Swapnil Gupta

@Alastair, bunu kaçırdım !! Gösterdiğiniz için teşekkürler .. Cevabımı güncelledim.
Abdel Raoof

2
Bu seçenek platforma bağlıdır - Grand Endian ve Little endian ve yaklaşık 32 Bit / 64 bit konusunda dikkatli olun.
x77

@Abdel, ve -1 gitti :)
Alastair Pitts

Alloc'u gerçekleştirmek, ortadaki biti bir denemede sarmak ve sonra Free'yi bir lastiğin içine koymak mantıklı olur mu? İşlerin başarısız olması pek olası görünmüyor, ancak başarısız olursa, bellek hiç serbest kalır mı?
Casey

18

Vicent kodunun bir eksi bellek tahsisi olan varyantı:

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;
}

Kullandığım GCHandlebellekten "pin" için ve sonra ben doğrudan adresini kullanabilirsiniz h.AddrOfPinnedObject().


Kaldırmalı, where T : structaksi takdirde Tgeçme şikayeti olmayacak bir non-nullable type.
codenamezero

GCHandle.Allocyapı bölünemez verilere sahipse başarısız olur, örneğin bir dizi
joe

@joe Haklısın. Kod, yalnızca blittable türleri içeren verilen yapı için yazılmıştır ve string.
xanatos

5

Ana cevap, C #'da bulunmayan (veya artık bulunmayan) CIFSPacket türünü kullanmak olduğu için, doğru yöntemleri yazdım:

    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;
    }

Test edildi, çalışıyorlar.


4

Bunun gerçekten geç olduğunu biliyorum, ancak C # 7.3 ile bunu yönetilmeyen yapılar veya yönetilmeyen herhangi bir şey için yapabilirsiniz (int, bool vb ...):

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;
    }

O zaman şu şekilde kullanın:

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

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

2

Marshal (StructureToPtr, ptrToStructure) ve Marshal.copy'yi kullanabilirsiniz, ancak bu plataform bağımlıdır.


Serileştirme, Özel Serileştirme İşlevlerini içerir.

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

SerializationInfo, her üyeyi serileştirmek için işlevler içerir.


BinaryWriter ve BinaryReader ayrıca Bayt Dizisine (Akış) Kaydetme / Yükleme yöntemlerini içerir.

Bir Byte Dizisinden bir MemoryStream veya bir MemoryStream'den bir Byte Dizisi oluşturabileceğinizi unutmayın.

Yapınızda bir yöntem Kaydet ve Yeni bir yöntem oluşturabilirsiniz:

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

Ardından Kaydetmek / Akışa Yükle -> Bayt Dizisi için üyeleri seçersiniz.


1

Bu çok basit bir şekilde yapılabilir.

Yapınızı açıkça tanımlayın [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;
}

Bu kod yalnızca güvenli olmayan bir bağlamda yazılabilir. addrİşin bittiğinde özgür olmalısın.

Marshal.FreeHGlobal(addr);

Sabit boyutlu bir koleksiyonda açık sıralı işlemler yaparken, muhtemelen bir dizi ve for-döngüsü kullanmalısınız. Dizi, sabit boyutta olduğu için ve for-döngüsü de, listenizin türünün temelini oluşturan uygulamayı ve onun numaralandırıcısını bilmediğiniz ve hiçbir zaman değişmeyeceğini bilmediğiniz sürece, foreach'in beklediğiniz sırada olacağı garanti edilmez. Örneğin, numaralandırıcının sondan başlaması ve geriye gitmesi tanımlanabilir.

1

Uzunluğu sabitleme zahmetine girmeden herhangi birini dönüştürebilecek farklı bir yaklaşım buldum struct, ancak ortaya çıkan bayt dizisinin biraz daha fazla ek yükü olacaktır.

İşte bir örnek struct:

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

Gördüğünüz gibi, tüm bu yapılar sabit uzunluk niteliklerinin eklenmesini gerektirecektir. Bu genellikle gerekenden daha fazla yer kaplayabilirdi. Düşünmenin LayoutKind.Sequentialgerekli olduğuna dikkat edin , çünkü çekerken bize her zaman aynı sırayı verir FieldInfo. İlham kaynağım TLVType-Length-Value'dan. Koda bir bakalım:

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();
    }
}

Yukarıdaki işlev BinaryFormatter, bilinmeyen boyutu ham olarak serileştirmek için basitçe kullanır objectve ben de boyutu izlerim ve çıktının içinde MemoryStreamsaklarım.

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));
            }
        }
    }
}

Onu orijinal haline geri dönüştürmek istediğimizde, structsadece uzunluğu geriye okuruz ve doğrudan geri dökeriz ve BinaryFormatterbu da onu tekrar struct.

Bu 2 işlev geneldir ve herhangi biriyle çalışmalıdır struct, projemde yukarıdaki kodu C#bir sunucum ve bir istemcim olduğu, bağlandığım ve iletişim kurduğum NamedPipeStreamve structbayt dizimi olarak birinden diğerine ilettiğim ve geri dönüştürdüğüm projemde test ettim. .

Yaklaşımımın daha iyi olabileceğine inanıyorum, çünkü uzunluğu structkendi başına sabitlemiyor ve tek ek yük sadece yapınızda sahip olduğunuz inther alan için. Ayrıca, tarafından üretilen bayt dizisinin içinde bazı küçük bit yükü vardır BinaryFormatter, ancak bunun dışında çok fazla değildir.


6
Genel olarak, insanlar bu tür şeylerle uğraşmaya çalışırken aynı zamanda serileştirme performansıyla da ilgilenirler. Teoride, herhangi bir yapı dizisi, pahalı serileştirme ve kopyalama gerektirmeden bir bayt dizisi olarak yeniden yorumlanabilir.
Tanveer Badar


0

Bazı harici kitaplıklar için önceden tanımlanmış (C seviyesi) bir yapıya benziyor. Mareşal senin arkadaşın. Kontrol:

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

başlangıç ​​için bununla nasıl başa çıkılacağı. Özniteliklerle bayt düzeni ve dize işleme gibi şeyleri tanımlayabileceğinizi unutmayın. Aslında ÇOK güzel bir yaklaşım.

Bunun için Ne BinaryFormatter ne de MemoryStream yapılmaz.


0

@Abdel Olakara yanıtı donese .net 3.5'te çalışmıyor, aşağıdaki gibi değiştirilmelidir:

    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);

Bu hile çabucak yapmalı, değil mi?


GCHandle sürümü çok daha iyi.
Петър Петров

0

Buradaki bu örnek yalnızca saf bölünebilir türler için geçerlidir, örneğin, doğrudan C'de memcpy'lenebilen türler.

Örnek - iyi bilinen 64 bit yapı

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

Tam olarak bu şekilde tanımlandığında, yapı otomatik olarak 64 bit olarak paketlenecektir.

Şimdi voksel hacmi oluşturabiliriz:

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

Ve hepsini bir bayt dizisine kaydedin:

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.

Bununla birlikte, OP yapının kendisini nasıl dönüştüreceğini bilmek istediğinden, Voxel yapımız aşağıdaki yönteme sahip olabilir 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();
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.