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*タイプのポインタにキャストできることを望んでいました。非常に良いパフォーマンス。最初、私のNode-structには、Link型の10個の個別の変数(Link0、...、Link10)があり、問題なく機能していました。ただし、これをコンパイル時に構成可能にすると、上記のNode-structが発生します。

私は、Linksが同じFieldOffsetを持っているので、_linksDataと同じメモリ位置を指すことを望んでいました。しかし実際には、リンクポインタは常にnullポインタです。

だから私の質問は:Linksが_linksDataと同じメモリ位置を指す方法はありますか、それとも構造体の固定サイズの配列を別の構造体に埋め込む別の方法がありますか?

事前にすべての回答をありがとう-Markus

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オブジェクトへのポインターは、それを固定するかスタックに配置した場合にのみ使用できます。ここでは、それが当てはまるかどうかはわかりません。

:(

フルオンラッパー時間:

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

ブリット可能なタイプのみをマーシャリングできます。これにより、IntPtr、string、byte、char int、float、double、decimal、および対応する符号なしのインスタンスに制限されます。関心のある他のデータ構造へのポインタを定義することはできません。これは現在サポートされていません。

Marhsalerは、最終インスタンスに必要なスペースを知る必要があり、いつ停止するかを知る必要があります。マネージ型へのポインターを定義する場合、ポインターをマーシャリングしてからアクセスする必要があります。これは、ポインターがマネージ型になっているため、常にコピーが返されるためです。これらの問題はある程度解決可能ですが、私が知る限り、これは現在サポートされていません。おそらく、.NET4.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 に答える