我有一个非托管C/C++库,用于解码可变大小的字节缓冲区。这些字节构成一个从SQLite BLOB中提取的复杂数据报。在C/C++代码中,很容易使用指针在数据的基础上覆盖一个结构。在C#中还有同样的事情要做吗?通过使用Marshal类,我将向Byte[]传递一个引用到非托管端。我还可以传递对托管端结构的引用,这些结构将被填充。我想把这些都拉到C#那里去。有什么想法吗有人吗?
蒂娅,道格
发布于 2022-05-26 11:28:50
这个答案假设了以下几点:
decimal
值、不包含string
等)。可以在字节数组的一个部分上“覆盖”一个结构,这样您就可以通过结构访问数组中的数据,而无需对数据进行额外的复制。为此,您可以使用Span
和MemoryMarshal.AsRef
。
当然,在这样做的时候,结构填料很重要。您必须指定结构的打包,以匹配字节数组中的打包。这与声明用于P/Invoke的结构时所做的完全相同。
(尽管有些人可能声称,StructLayoutAttribute.Pack
设置确实会影响内存中结构的布局;它不仅仅是针对P/Invoke的。)
如果在结构中使用char
值,还必须将CharSet
指定为Unicode。如果源结构没有对字符使用UTF16 (如果它们使用ANSI或ASCII),则需要将这些字段声明为byte
而不是char
,因为C#字符总是UTF16。
一旦您正确地布局了您的结构,并且您有一个包含一个或多个结构的byte[]
数组,您就可以在数组的一个部分上覆盖一个结构,如下所示:
Span<byte>(byte[] array, int start, int length)
](https://learn.microsoft.com/en-us/dotnet/api/system.span-1.-ctor?view=net-6.0#system-span-1-ctor(-0(%29-system-int32-system-int32%29)构造函数创建一个https://learn.microsoft.com/en-us/dotnet/api/system.span-1.-ctor?view=net-6.0#system-span-1-ctor(-0(%29-system-int32-system-int32%29,以引用数组的子集,该子集对应于结构的字节。MemoryMarshal.AsRef
引用字节数组中的结构。这直接引用字节数组中的结构,而不复制任何数据。下面的程序演示了这种方法。
它定义了三个不同的结构,Demo1
、Demo2
和Demo3
,每个结构都有不同的包装。
它实现了一个toByteArray()
方法,其唯一目的是将结构转换为一个字节数组,在开始和结束时有一些备用字节。实现这一点并不重要;您可以从P/Invoke中获取字节数组。
它还实现了一个助手方法AsSpan()
。这是一个非常有用的方法;它将任何闪存(非托管)结构转换为Span<byte>()
。
有趣的方法是consumeStructs()
。这将在给定的byte[]
数组上覆盖三个不同的结构,然后打印出它们的内容。
如果运行此程序,您将看到输出与Main()
中的结构初始化匹配。
注意,这些结构被声明为ref var
。这意味着没有复制数据;结构实际上是直接引用传递给方法的data[]
数组中的数据。
我通过修改data[]
数组中对应于Demo2
结构开头的一个字节来演示这种情况,如下所示:
data[prefixByteCount + Marshal.SizeOf<Demo1>()] = (byte)'*';
在进行更改并重新打印结构后,输出显示CharValue
已从0
更改为*
。这说明结构确实直接在data[]
数组中引用数据。
下面是可编译的控制台应用程序:
(在网上试试)
using System;
using System.Linq;
using System.Runtime.InteropServices;
namespace Demo
{
static class Program
{
static void Main()
{
var demo1 = new Demo1
{
BoolValue = true,
DoubleValue = -1
};
var demo2 = new Demo2
{
CharValue = '0',
ShortValue = 1,
IntValue = 2,
LongValue = 3,
FloatValue = 4,
DoubleValue = 5.4321
};
var demo3 = new Demo3
{
ByteValue = 128,
FloatValue = 1.23f
};
const int PREFIX_BYTE_COUNT = 29;
const int POSTFIX_BYTE_COUNT = 17;
var bytes = toByteArray(PREFIX_BYTE_COUNT, POSTFIX_BYTE_COUNT, ref demo1, ref demo2, ref demo3);
consumeStructs(PREFIX_BYTE_COUNT, bytes);
}
static byte[] toByteArray(int prefixByteCount, int postfixByteCount, ref Demo1 demo1, ref Demo2 demo2, ref Demo3 demo3)
{
var demo1Bytes = AsSpan(ref demo1);
var demo2Bytes = AsSpan(ref demo2);
var demo3Bytes = AsSpan(ref demo3);
var prefixBytes = Enumerable.Repeat((byte)0, prefixByteCount);
var postfixBytes = Enumerable.Repeat((byte)0, postfixByteCount);
return prefixBytes
.Concat(demo1Bytes.ToArray())
.Concat(demo2Bytes.ToArray())
.Concat(demo3Bytes.ToArray())
.Concat(postfixBytes)
.ToArray();
}
static void consumeStructs(int prefixByteCount, byte[] data)
{
var demo1Bytes = new Span<byte>(data, prefixByteCount, Marshal.SizeOf<Demo1>());
var demo2Bytes = new Span<byte>(data, prefixByteCount + Marshal.SizeOf<Demo1>(), Marshal.SizeOf<Demo2>());
var demo3Bytes = new Span<byte>(data, prefixByteCount + Marshal.SizeOf<Demo1>() + Marshal.SizeOf<Demo2>(), Marshal.SizeOf<Demo3>());
ref var demo1Ref = ref MemoryMarshal.AsRef<Demo1>(demo1Bytes);
ref var demo2Ref = ref MemoryMarshal.AsRef<Demo2>(demo2Bytes);
ref var demo3Ref = ref MemoryMarshal.AsRef<Demo3>(demo3Bytes);
Console.WriteLine(demo1Ref);
Console.WriteLine(demo2Ref);
Console.WriteLine(demo3Ref);
Console.WriteLine("Modifying first byte of Demo2 struct in byte buffer.");
data[prefixByteCount + Marshal.SizeOf<Demo1>()] = (byte)'*';
Console.WriteLine(demo2Ref);
}
public static Span<byte> AsSpan<T>(ref T val) where T : unmanaged
{
var valSpan = MemoryMarshal.CreateSpan(ref val, 1);
return MemoryMarshal.Cast<T, byte>(valSpan);
}
}
[StructLayout(LayoutKind.Sequential, Pack = 8)]
public struct Demo1
{
public bool BoolValue;
public double DoubleValue;
public override string ToString()
{
return $"byte = {BoolValue}, double = {DoubleValue}";
}
}
[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Unicode)]
public struct Demo2
{
public char CharValue;
public short ShortValue;
public int IntValue;
public long LongValue;
public float FloatValue;
public double DoubleValue;
public override string ToString()
{
return $"char = {CharValue}, short = {ShortValue}, int = {IntValue}, long = {LongValue}, float = {FloatValue}, double = {DoubleValue}";
}
}
[StructLayout(LayoutKind.Sequential, Pack = 2)]
public struct Demo3
{
public byte ByteValue;
public float FloatValue;
public override string ToString()
{
return $"byte = {ByteValue}, float = {FloatValue}";
}
}
}
https://stackoverflow.com/questions/72383841
复制相似问题