คู่มือครบวงจรในการอ่านโครงสร้างข้อมูล C/C++ ใน C# จากอาร์เรย์ byte[]
เมื่อเราต้องการย้ายหรือทำงานกับโครงสร้างข้อมูลระหว่างภาษา โดยเฉพาะจาก C/C++ ไปยัง C# นักพัฒนามักจะพบความท้าทายในการแปลรูปแบบข้อมูลไบต์ของโครงสร้าง C/C++ ให้เป็นโครงสร้าง C# ที่จัดการได้ง่าย บล็อกโพสต์นี้จะพูดถึงวิธีการแปลงอาร์เรย์ byte[]
ที่มีข้อมูลโครงสร้าง C/C++ เป็นโครงสร้าง C# ได้อย่างมีประสิทธิภาพ โดยไม่ต้องประสบกับปัญหาที่เกิดจากการคัดลอกข้อมูลที่ไม่จำเป็น
ทำความเข้าใจกับปัญหา
พิจารณาความหมายของโครงสร้าง C ต่อไปนี้:
typedef OldStuff {
CHAR Name[8];
UInt32 User;
CHAR Location[8];
UInt32 TimeStamp;
UInt32 Sequence;
CHAR Tracking[16];
CHAR Filler[12];
}
การแทนค่าดังกล่าวประกอบด้วยลำดับของตัวอักษรและจำนวนเต็มที่กำหนดไว้ เมื่อนักพัฒนาทำงานกับข้อมูลไบนารีเช่นนี้ใน C# พวกเขาจะต้องเผชิญกับความท้าทายในการจัดเรียงโครงสร้างให้ถูกต้องและทำการแปลงเพื่อใช้ในโค้ดที่จัดการได้
โครงสร้าง C# ที่เกี่ยวข้องจะใช้ StructLayout
เพื่อควบคุมรูปแบบการจัดเรียงข้อมูลในหน่วยความจำ:
[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;
}
วิธีการแบบดั้งเดิม
วิธีการทั่วไปที่นักพัฒนานำมาใช้คือการคัดลอกอาร์เรย์ไบต์ไปยังบัฟเฟอร์แยกต่างหากก่อนที่จะพยายามแปลงเป็นโครงสร้าง C# ที่ต้องการ นี่คือวิธีการที่พบเห็นได้บ่อย:
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();
แม้ว่าวิธีการนี้จะทำงานได้ แต่ก็ทำให้เกิดภาระที่ไม่จำเป็นโดยเฉพาะในเรื่องการใช้หน่วยความจำและประสิทธิภาพ เนื่องจากสร้างสำเนาข้อมูลกลางขึ้นมา
ทางออกที่ดีกว่า
แทนที่จะทำการสำรองข้อมูล คุณสามารถใช้อาร์เรย์ไบต์โดยตรงในการดึงโครงสร้างของคุณ นี่คือวิธีการที่มีประสิทธิภาพมากขึ้น:
การใช้ GCHandle โดยตรง
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();
}
}
วิธีการนี้จัดสรร handle ให้กับอาร์เรย์ไบต์ต้นฉบับและกำจัดความจำเป็นในการสร้างบัฟเฟอร์เพิ่มเติม
วิธีการทั่วไปสำหรับความยืดหยุ่น
หากคุณต้องการแปลงโครงสร้างใด ๆ จากอาร์เรย์ไบต์ คุณสามารถสร้างวิธีทั่วไปได้:
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;
}
วิธีที่ไม่ปลอดภัยเพื่อเพิ่มประสิทธิภาพ
สำหรับผู้ใช้ที่มีความก้าวหน้า การใช้สถานการณ์ unsafe
สามารถทำให้ประสิทธิภาพดีขึ้น เนื่องจากช่วยให้คุณทำงานกับตัวชี้ได้โดยตรง:
unsafe T ByteArrayToStructure<T>(byte[] bytes) where T : struct
{
fixed (byte* ptr = &bytes[0])
{
return (T)Marshal.PtrToStructure((IntPtr)ptr, typeof(T));
}
}
สรุป
การอ่านโครงสร้างข้อมูล C/C++ ไปยัง C# จากอาร์เรย์ byte[]
อย่างมีประสิทธิภาพไม่จำเป็นต้องเป็นเรื่องยุ่งยาก ด้วยการใช้ GCHandle
โดยตรงกับอาร์เรย์ไบต์หรือการใช้โค้ด unsafe
คุณจะเพิ่มประสิทธิภาพและทำให้โค้ดของคุณเรียบง่ายขึ้น การนำวิธีการเหล่านี้ไปใช้จะก่อให้เกิดโค้ดที่สะอาดและบำรุงรักษาได้ง่ายขึ้นในขณะที่หลีกเลี่ยงภาระหน่วยความจำที่ไม่จำเป็น
ดังนั้น ครั้งต่อไปที่คุณต้องจัดการการแปลงข้อมูลข้ามภาษา ให้นึกถึงวิธีการเหล่านี้เพื่อทำให้กระบวนการของคุณมีความคล่องตัวขึ้น!