byte[] Dizisinden C#‘de C/C++ Veri Yapılarını Okuma İle İlgili Kapsamlı Bir Kılavuz

Diller arasında, özellikle C/C++‘dan C#‘ye veri yapılarını aktaran veya bu verilerle çalışan geliştiriciler, genellikle bir C/C++ yapısının byte temsilini yönetilebilir bir C# karşılığına dönüştürme zorluğuyla karşılaşır. Bu blog yazısı, C/C++ yapı verisini içeren bir byte[] dizisini C# yapısına verimli bir şekilde dönüştürmeyi ele almaktadır; gereksiz veri kopyalamaktan kaynaklanan hantallığın önüne geçilmiştir.

Problemi Anlamak

Aşağıdaki C yapısı tanımını düşünelim:

typedef OldStuff {
    CHAR Name[8];
    UInt32 User;
    CHAR Location[8];
    UInt32 TimeStamp;
    UInt32 Sequence;
    CHAR Tracking[16];
    CHAR Filler[12];
}

Bu temsil, sabit bir karakter ve tam sayılar dizisinden oluşmaktadır. C#‘de böyle ikili verilerle çalışırken geliştiriciler, yapıyı doğru bir şekilde hizalamak ve yönetilen kodda kullanılmak üzere dönüştürmek zorluği ile karşılaşırlar.

Karşılık gelen C# yapısı, verinin bellekte nasıl düzenlendiğini kontrol etmek için StructLayout kullanır:

[StructLayout(LayoutKind.Explicit, Size = 56, Pack = 1)]
public struct NewStuff
{
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 8)]
    [FieldOffset(0)]
    public string Name;

    [MarshalAs(UnmanagedType.U4)]
    [FieldOffset(8)]
    public uint User;

    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 8)]
    [FieldOffset(12)]
    public string Location;

    [MarshalAs(UnmanagedType.U4)]
    [FieldOffset(20)]
    public uint TimeStamp;

    [MarshalAs(UnmanagedType.U4)]
    [FieldOffset(24)]
    public uint Sequence;

    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 16)]
    [FieldOffset(28)]
    public string Tracking;
}

Geleneksel Yaklaşım

Geliştiricilerin tipik olarak izlediği bir yaklaşım, byte dizisini ayrı bir tampon içine kopyalamak ve ardından istenen C# yapısına marşallama girişimidir. İşte sıkça görülen bir yöntem:

GCHandle handle;
NewStuff MyStuff;

int BufferSize = Marshal.SizeOf(typeof(NewStuff));
byte[] buff = new byte[BufferSize];

Array.Copy(SomeByteArray, 0, buff, 0, BufferSize);

handle = GCHandle.Alloc(buff, GCHandleType.Pinned);

MyStuff = (NewStuff)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(NewStuff));

handle.Free();

Bu yaklaşım işe yarasa da, verinin ara bir kopyasını oluşturarak gereksiz yük, özellikle bellek kullanımı ve performans açısından getirmektedir.

Daha İyi Bir Çözüm

Veriyi kopyalamak yerine, byte dizisini doğrudan kullanarak yapınızı çıkarabilirsiniz. İşte daha verimli bir yöntem:

GCHandle Kullanarak Doğrudan

NewStuff ByteArrayToNewStuff(byte[] bytes)
{
    GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
    try
    {
        NewStuff stuff = (NewStuff)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(NewStuff));
        return stuff;
    }
    finally
    {
        handle.Free();
    }
}

Bu yöntem, orijinal byte dizisine bir handle ayırır ve ek bir tampon oluşturma ihtiyacını ortadan kaldırır.

Esneklik İçin Genel Yöntem

Bir byte dizisinden herhangi bir yapıyı dönüştürmek isterseniz, genel bir yöntem oluşturabilirsiniz:

T ByteArrayToStructure<T>(byte[] bytes) where T : struct
{
    T stuff;
    GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
    try
    {
        stuff = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T));
    }
    finally
    {
        handle.Free();
    }
    return stuff;
}

Ek Performans İçin Güvensiz Yaklaşım

Gelişmiş kullanıcılar için, unsafe bağlamını kullanmak pratikte daha iyi performans sağlayabilir; çünkü işaretçilerle doğrudan çalışmanıza olanak tanır:

unsafe T ByteArrayToStructure<T>(byte[] bytes) where T : struct
{
    fixed (byte* ptr = &bytes[0])
    {
        return (T)Marshal.PtrToStructure((IntPtr)ptr, typeof(T));
    }
}

Sonuç

C/C++ veri yapısını C#‘de bir byte[] dizisinden etkili bir şekilde okumak zahmetli olmak zorunda değildir. Byte dizisi üzerinde GCHandle kullanarak veya unsafe kod kullanarak performansı artırabilir ve kodunuzu basitleştirebilirsiniz. Bu yaklaşımları benimsemek, gereksiz bellek yükünden kaçınırken daha temiz ve sürdürülebilir bir kod sağlamaktadır.

Bir dahaki sefere dil sorunu verisi dönüşümü yaparken, sürecinizi basitleştirmek için bu yöntemleri hatırlamayı ihmal etmeyin!