4

我是 stackoverflow 的新手,我会对 C# 结构及其布局有疑问。

让我们假设以下结构:

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public unsafe struct Link
{
    // some primitive data (2 integers for example)
}

[StructLayout(LayoutKind.Explicit, Pack = 1)]
public unsafe struct Node
{
    [FieldOffset(0)]
    public int LinkCount;
    [FieldOffset(4)]
    public Link* Links;
    [FieldOffset(4)]
    private fixed byte _linksData[10 * sizeof(Link)];
}

这样做的原因是我需要 IO-Performance 的 blittable 类型。我必须处理具有数 GB 大小的非常大(并且每个节点最多稀疏 10 个链接)图。该图表示为节点结构数组。通过上面的设置,我希望能够从图形文件中读取例如 100 MB 到字节指针(当然指向字节缓冲区)并将其转换为 Node* 类型的指针,从而产生非常好的表现。起初,我的节点结构只有 10 个独立的 Link 类型变量(Link0,...,Link10),它们工作得很好。但是最好在编译时使其可配置,这会导致上面的 Node-struct。

我希望 Links 只指向与 _linksData 相同的内存位置,因为它具有相同的 FieldOffset。但实际上 Links 指针始终是空指针。

所以我的问题是:有没有一种方法可以使 Links 指向与 _linksData 相同的内存位置,或者是否有另一种方法可以将固定大小的结构数组嵌入到另一个结构中。

提前感谢您的每一个回答 - 马库斯

在阅读了 Ben Voigt 的帖子后,我尝试了类似的方法,而无需将结构更改为类。以下是它对我的工作方式:

[StructLayout(LayoutKind.Explicit, Pack = 1)]
public unsafe struct Node
{
    [FieldOffset(0)]
    public int LinkCount;
    [FieldOffset(4)]
    private fixed byte _linksData[10 * sizeof(Link)];

    public Link* GetLinks()
    {
       fixed(byte* pLinksData = _linksData)
       {
          return (Link*)pLinksData;
       }
    }
}
4

2 回答 2

1

我猜您实际上并没有尝试存储指针,只是有一种正确键入的方式来访问这 10 个元素。怎么样:

[StructLayout(LayoutKind.Explicit, Pack = 1)]
public unsafe struct Node
{
    [FieldOffset(0)]
    public int LinkCount;
    [FieldOffset(4)]
    private fixed byte _linksData[10 * sizeof(Link)];

    public Link* Links { get { return _linksData; } };
}

不,等等,.NET 支持内部指针,但 C# 不支持,所以这不起作用。如果您已将 .NET 对象固定或放置在堆栈上,您只能拥有指向 .NET 对象的指针,我们不知道这里是否是这种情况。

:(

完整的包装时间:

public class LinkCollection
{
    Node peer;
    public LinkCollection(Node node) { peer = node; }
    void CheckIndex(int index) { if (index < 0 || index >= 10) throw new ArgumentOutOfRangeException(); }
    public Link default[int index] {
        get { CheckIndex(index); return peer.GetLink(index); }
        set { CheckIndex(index); peer.SetLink(index, value); }
    }
}

[StructLayout(LayoutKind.Explicit, Pack = 1)]
public unsafe class Node
{
    [FieldOffset(0)]
    public int LinkCount;
    [FieldOffset(4)]
    private fixed byte _linksData[10 * sizeof(Link)];

    unsafe Link GetLink(int index) { fixed( Link* plink = (Link*)&_linksData[0] ) return plink[index]; }
    unsafe void SetLink(int index, Link newvalue) { fixed( Link* plink = (Link*)&linksData[0] ) plink[index] = newvalue; }
    public LinkCollection Links { get { return new LinkCollection(this); } };
}

请注意,我必须更改Node为一个类... p/invoke 的行为仍然应该几乎相同。

如果您不想这样做,扩展方法可能是一个答案。

于 2012-05-08T14:18:22.907 回答
0

您只能编组 blitable 类型。这将您限制为 IntPtr、string、byte、char int、float、double、decimal 和无符号对应项的实例。您不能定义指向您也感兴趣的其他数据结构的指针。目前不支持此功能。

Marhsaler 需要知道最终实例将占用多少空间,并且需要知道何时停止。当您定义指向托管类型的指针时,它还需要在访问它之前对指针进行编组,因为它现在是托管类型,您总是会得到一个副本。这些问题在一定程度上是可以解决的,但据我所知,目前尚不支持。也许.NET 4.5 带来了一些新特性。

编辑1:

要从结构中获取某些内容,您可以将指针保留为 IntPtr 并简单地使用扩展方法自己完成繁重的工作(编组)。我不知道原始链接是否已固定。这假定链接不可移动(非托管数据或固定托管对象)。

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;


unsafe class Program
{
    static void Main(string[] args)
    {
        Link[] arr = new Link[] { 
            new Link(1),
            new Link(2),
            new Link(3),
            new Link(4),
        };

        fixed (Link* pLinks = arr) // for demo purposes create a node instance
        {
            var nde = new Node
            {
                LinkCount = arr.Length,
                Links = new IntPtr(pLinks) // Marshal as IntPtr is safe, later the data can be retrieved via an Extension method.
            };

            foreach (var link in nde.GetLinks())
            {
                Console.WriteLine("Link {0}", link.I);
            }
        };
    }
}

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public unsafe struct Link
{
    // some primitive data (2 integers for example)
    public int I;

    public Link(int i)
    {
        I = i;
    }
}

[StructLayout(LayoutKind.Explicit, Pack = 1)]
public unsafe struct Node
{
    [FieldOffset(0)]
    public int LinkCount;
    [FieldOffset(4)]
    public IntPtr Links; // this assumes that the Links is some unsafe buffer which is not under GC control or it is pinned
}


static class LinkExtensions
{
    public static IEnumerable<Link> GetLinks(this Node node)
    {
        for (int i = 0; i < node.LinkCount; i++) // very efficient if you want to traverse millions of nodes without marshalling all of them at once
        {
            // alternatively you can also use a memcpy (PInvoke to msvcrt.dll) to fill in the data from a given offset.
            // it is up to you to decide which is faster
            yield return (Link)Marshal.PtrToStructure(node.Links + IntPtr.Size * i, typeof(Link));
        }
    }
}
于 2012-05-08T14:14:28.960 回答