在 C# 中是否可以有一个具有固定缓冲区的联合结构和另一个具有数组或字符串字段的结构?

发布于 2025-01-20 12:20:36 字数 1826 浏览 4 评论 0原文

我试图为数十万个对象分配内存,以便稍后从字节数组中初始化它们。我的目标是跳过每个对象的内存分配。这就是我使用 C# 结构的原因。

Union:

[StructLayout(LayoutKind.Explicit)]
struct HeaderUnion
{
    [FieldOffset(0)]
    public unsafe fixed char Data[8];

    [FieldOffset(0)]
    public HeaderSeq HeaderSeq;
}

HeaderSeq:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
struct HeaderSeq
{
    [MarshalAs(UnmanagedType.ByValTStr,SizeConst = 2)]
    public string FirstName;

    [MarshalAs(UnmanagedType.ByValTStr,SizeConst = 2)]
    public string LastName;
}

在我想写的程序中:

var bytes = File.ReadAllBytes("image.dat");
var headerUnions = new HeaderUnion[100000];
var size = Marshal.SizeOf<HeaderSeq>();

for (int i = 0; i < 100000; i++)
{
    var positionIndex = i * size;

    unsafe
    {
        fixed (char* charPtr = headerUnions[i].Data)
        {
            Marshal.Copy(bytes, positionIndex, (IntPtr)charPtr, size);
        }
    }
}

但是,它给了我运行时错误:

未处理的异常。 System.TypeLoadException:无法从程序集“MyProject.Console,Version=1.0.0.0,Culture=neutral,PublicKeyToken=null”加载类型“HeaderUnion”,因为它在偏移量 0 处包含一个对象字段,该对象字段未正确对齐或与非-对象字段。

如果我将 HeaderSeq 重新定义为仅包含单个 char 字段,而不是 stringchar[],则效果很好。

如果我不将 HeaderSeq 作为字段包含到 HeaderUnion 中,它也可以正常工作,但我应该重写我的代码以利用 Marshal.PtrToStructure

// It does not suit me, because it allocates a struct on each cycle.
// Instead, I want all the memory to be pre-allocated already in the fixed buffers.
var headerSeq = Marshal.PtrToStructure<HeaderSeq>((IntPtr) charPtr);

我可以考虑在托管堆中预分配 char 数组,并在 HeaderSeq 中仅存储索引。这种方法对我来说不太优雅。

我的目标在 C# 中可以实现吗?我应该如何定义我的结构?

I'm trying to allocate memory for hundreds of thousands objects to initialize them later from an array of bytes. My goal is to skip memory allocation on each object. That is why I am using C# structs.

Union:

[StructLayout(LayoutKind.Explicit)]
struct HeaderUnion
{
    [FieldOffset(0)]
    public unsafe fixed char Data[8];

    [FieldOffset(0)]
    public HeaderSeq HeaderSeq;
}

HeaderSeq:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
struct HeaderSeq
{
    [MarshalAs(UnmanagedType.ByValTStr,SizeConst = 2)]
    public string FirstName;

    [MarshalAs(UnmanagedType.ByValTStr,SizeConst = 2)]
    public string LastName;
}

In the program I want to write:

var bytes = File.ReadAllBytes("image.dat");
var headerUnions = new HeaderUnion[100000];
var size = Marshal.SizeOf<HeaderSeq>();

for (int i = 0; i < 100000; i++)
{
    var positionIndex = i * size;

    unsafe
    {
        fixed (char* charPtr = headerUnions[i].Data)
        {
            Marshal.Copy(bytes, positionIndex, (IntPtr)charPtr, size);
        }
    }
}

However, it gives me the runtime error:

Unhandled exception. System.TypeLoadException: Could not load type 'HeaderUnion' from assembly 'MyProject.Console, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' because it contains an object field at offset 0 that is incorrectly aligned or overlapped by a non-object field.

If I redefine HeaderSeq to only contain single-char fields instead of string or char[], it works fine.

If I do not include HeaderSeq to the HeaderUnion as a field, it also works fine but I should rewrite my code to utilize Marshal.PtrToStructure:

// It does not suit me, because it allocates a struct on each cycle.
// Instead, I want all the memory to be pre-allocated already in the fixed buffers.
var headerSeq = Marshal.PtrToStructure<HeaderSeq>((IntPtr) charPtr);

I can think of preallocating arrays of char in the managed heap, and storing only indexes in the HeaderSeq. This approach is less elegant for me.

Is my goal achievable at all in C#? How should I define my structs?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

最笨的告白 2025-01-27 12:20:37

没有办法使用托管类型(String)执行此操作,您将不得不使用char []char*而不是。

在一个不相关的说明中,我不确定为什么,但是数据字段占16个字节而不是8个字段(根据.net 6 x86上的sizeof )。将其更改为字节阵列可以修复。没有实际的差异,它会让您分配更少的内存。

编辑:在再看第二位之后,C#的字符似乎是16位(即Unicode),但自动被编成8位,这使我感到困惑。如果您打算使用Unicode,则需要炭。

There is no way to do this with managed types (string), you're going to have to use char[] or char* instead.

On an unrelated note, I'm not sure why but the Data field takes up 16 bytes rather than 8 (according to sizeof on .NET 6 x86). Changing it to a byte array fixes that. There's no practical difference and it would have you allocate less memory.

EDIT: After looking into the second bit a little more, it seems C#'s chars are 16 bit (i.e. Unicode), but automatically get marshalled as 8 bit, which confused me. If you plan on using Unicode, chars are desired.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文