[英]C# - Cast a byte array to an array of struct and vice-versa (reverse)
I would like to save a Color[] to a file.我想将 Color[] 保存到文件中。 To do so, I found that saving a byte array to a file using "System.IO.File.WriteAllBytes" should be very efficient.为此,我发现使用“System.IO.File.WriteAllBytes”将字节数组保存到文件应该非常有效。
I would like to cast my Color[] (array of struct) to a byte array into a safe way considering:我想以一种安全的方式将我的 Color[](结构数组)转换为字节数组:
If it is possible, it would be nice to have a generic way to cast array of byte to array of any struct (T struct) and vice-versa.如果可能,最好有一种通用的方法将字节数组转换为任何结构(T 结构)数组,反之亦然。
If not possible, why ?如果不可能,为什么?
Thanks, Eric谢谢,埃里克
I think that those 2 solutions make a copy that I would like to avoid and also they both uses Marshal.PtrToStructure which is specific to structure and not to array of structure:我认为这两个解决方案制作了一个我想避免的副本,而且它们都使用 Marshal.PtrToStructure 特定于结构而不是结构数组:
Since .NET Core 2.1, yes we can!从 .NET Core 2.1 开始,是的,我们可以! Enter MemoryMarshal
.输入MemoryMarshal
。
We will treat our Color[]
as a ReadOnlySpan<Color>
.我们将Color[]
视为ReadOnlySpan<Color>
。 We reinterpret that as a ReadOnlySpan<byte>
.我们将其重新解释为ReadOnlySpan<byte>
。 Finally, since WriteAllBytes
has no span-based overload, we use a FileStream
to write the span to disk.最后,由于WriteAllBytes
没有基于跨度的重载,我们使用FileStream
将跨度写入磁盘。
var byteSpan = MemoryMarshal.AsBytes(colorArray.AsSpan());
fileStream.Write(byteSpan);
As an interesting side note, you can also experiment with the [StructLayout(LayoutKind.Explicit)]
as an attribute on your fields.作为一个有趣的旁注,您还可以尝试将[StructLayout(LayoutKind.Explicit)]
作为您的字段的属性。 It allows you to specify overlapping fields, effectively allowing the concept of a union.它允许您指定重叠字段,有效地允许联合的概念。
Here is a blog post on MSDN that illustrates this. 这是 MSDN 上的一篇博客文章,说明了这一点。 It shows the following code:它显示了以下代码:
[StructLayout(LayoutKind.Explicit)]
public struct MyUnion
{
[FieldOffset(0)]
public UInt16 myInt;
[FieldOffset(0)]
public Byte byte1;
[FieldOffset(1)]
public Byte byte2;
}
In this example, the UInt16
field overlaps with the two Byte
fields.在此示例中, UInt16
字段与两个Byte
字段重叠。
This seems to be strongly related to what you are trying to do.这似乎与您尝试做的事情密切相关。 It gets you very close, except for the part of writing all the bytes (especially of multiple Color
objects) efficiently.除了高效写入所有字节(尤其是多个Color
对象)的部分之外,它让您非常接近。 :) :)
Regarding Array Type Conversion关于数组类型转换
C# as a language intentionally makes the process of flattening objects or arrays into byte arrays difficult because this approach goes against the principals of .NET strong typing. C# 作为一种语言故意使将对象或数组扁平化为字节数组的过程变得困难,因为这种方法违背了 .NET 强类型的原则。 The conventional alternatives include several serialization tools which are generally seen a safer and more robust, or manual serialization coding such as BinaryWriter
.传统的替代方案包括几种序列化工具,这些工具通常被认为是更安全、更健壮的,或者手动序列化编码,例如BinaryWriter
。
Having two variables of different types point to the same object in memory can only be performed if the types of the variables can be cast, implicitly or explicitly.只有当变量的类型可以隐式或显式转换时,才能让两个不同类型的变量指向内存中的同一个对象。 Casting from an array of one element type to another is no trivial task: it would have to convert the internal members that keep track of things such as array length, etc.从一种元素类型的数组转换为另一种元素类型并非易事:它必须转换跟踪数组长度等内容的内部成员。
A simple way to write and read Color[] to file一种将 Color[] 写入和读取到文件的简单方法
void WriteColorsToFile(string path, Color[] colors)
{
BinaryWriter writer = new BinaryWriter(File.OpenWrite(path));
writer.Write(colors.Length);
foreach(Color color in colors)
{
writer.Write(color.ToArgb());
}
writer.Close();
}
Color[] ReadColorsFromFile(string path)
{
BinaryReader reader = new BinaryReader(File.OpenRead(path));
int length = reader.ReadInt32();
Colors[] result = new Colors[length];
for(int n=0; n<length; n++)
{
result[n] = Color.FromArgb(reader.ReadInt32());
}
reader.Close();
}
You could use pointers if you really want to copy each byte and not have a copy but the same object, similar to this:如果您真的想复制每个字节并且没有副本但具有相同的对象,则可以使用指针,类似于:
var structPtr = (byte*)&yourStruct;
var size = sizeof(YourType);
var memory = new byte[size];
fixed(byte* memoryPtr = memory)
{
for(int i = 0; i < size; i++)
{
*(memoryPtr + i) = *structPtr++;
}
}
File.WriteAllBytes(path, memory);
I just tested this and after adding the fixed
block and some minor corrections it looks like it is working correctly.我刚刚对此进行了测试,在添加了fixed
块和一些小的更正后,它看起来工作正常。
This is what I used to test it:这是我用来测试它的:
public static void Main(string[] args)
{
var a = new s { i = 1, j = 2 };
var sPtr = (byte*)&a;
var size = sizeof(s);
var mem = new byte[size];
fixed (byte* memPtr = mem)
{
for (int i = 0; i < size; i++)
{
*(memPtr + i) = *sPtr++;
}
}
File.WriteAllBytes("A:\\file.txt", mem);
}
struct s
{
internal int i;
internal int j;
}
The result is the following:结果如下:
(I manually resolved the hex bytes in the second line, only the first line was produced by the program) (我手动解析了第二行的十六进制字节,只有第一行是程序产生的)
Working code for reference (take care, I did not need the alpha channel in my case):供参考的工作代码(注意,在我的情况下我不需要 alpha 通道):
// ************************************************************************
// If someday Microsoft make Color serializable ...
//public static void SaveColors(Color[] colors, string path)
//{
// BinaryFormatter bf = new BinaryFormatter();
// MemoryStream ms = new MemoryStream();
// bf.Serialize(ms, colors);
// byte[] bytes = ms.ToArray();
// File.WriteAllBytes(path, bytes);
//}
// If someday Microsoft make Color serializable ...
//public static Colors[] LoadColors(string path)
//{
// Byte[] bytes = File.ReadAllBytes(path);
// BinaryFormatter bf = new BinaryFormatter();
// MemoryStream ms2 = new MemoryStream(bytes);
// return (Colors[])bf.Deserialize(ms2);
//}
// ******************************************************************
public static void SaveColorsToFile(Color[] colors, string path)
{
var formatter = new BinaryFormatter();
int count = colors.Length;
using (var stream = File.OpenWrite(path))
{
formatter.Serialize(stream, count);
for (int index = 0; index < count; index++)
{
formatter.Serialize(stream, colors[index].R);
formatter.Serialize(stream, colors[index].G);
formatter.Serialize(stream, colors[index].B);
}
}
}
// ******************************************************************
public static Color[] LoadColorsFromFile(string path)
{
var formatter = new BinaryFormatter();
Color[] colors;
using (var stream = File.OpenRead(path))
{
int count = (int)formatter.Deserialize(stream);
colors = new Color[count];
for (int index = 0; index < count; index++)
{
byte r = (byte)formatter.Deserialize(stream);
byte g = (byte)formatter.Deserialize(stream);
byte b = (byte)formatter.Deserialize(stream);
colors[index] = Color.FromRgb(r, g, b);
}
}
return colors;
}
// ******************************************************************
public struct MyX
{
public int IntValue;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3, ArraySubType = UnmanagedType.U1)]
public byte[] Array;
MyX(int i, int b)
{
IntValue = b;
Array = new byte[3];
}
public MyX ToStruct(byte []ar)
{
byte[] data = ar;//= { 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);
MyX x = (MyX)Marshal.PtrToStructure(ptPoit, typeof(MyX));
Marshal.FreeHGlobal(ptPoit);
return x;
}
public byte[] ToBytes()
{
Byte[] bytes = new Byte[Marshal.SizeOf(typeof(MyX))];
GCHandle pinStructure = GCHandle.Alloc(this, GCHandleType.Pinned);
try
{
Marshal.Copy(pinStructure.AddrOfPinnedObject(), bytes, 0, bytes.Length);
return bytes;
}
finally
{
pinStructure.Free();
}
}
}
void function()
{
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 = (MyX)Marshal.PtrToStructure(ptPoit, typeof(MyX));
Marshal.FreeHGlobal(ptPoit);
var MYstruc = x.ToStruct(data);
Console.WriteLine("x.IntValue = {0}", x.IntValue);
Console.WriteLine("x.Array = ({0}, {1}, {2})", x.Array[0], x.Array[1], x.Array[2]);
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.