2012-05-08 2 views
4

저는 stackoverflow에 처음이므로 C# 구조체와 해당 레이아웃에 대한 질문이 있습니다. 구조체에 대한 포인터의 FieldOffset을 고정 바이트 배열과 동일한 값으로 설정

는 이제 다음과 같은 구조체를 가정 해 봅시다 :

[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-성능에 대한 blittable 유형을 필요로한다는 것입니다. 크기가 몇 GB 인 매우 큰 (노드 당 최대 10 개의 링크) 그래프를 처리해야합니다. 그래프는 노드 구조체의 배열로 표현됩니다. 위와 같은 설정으로 그래프 파일에서 바이트 포인터 (물론 바이트 버퍼를 가리킴)로 100MB를 읽을 수 있고 Node 유형의 포인터로 캐스트 할 수 있기를 바랬습니다. 아주 좋은 성능을냅니다. 처음에는 Node-struct에 Link (Link0, ..., Link10) 유형의 10 개의 개별 변수가 있었지만 정상적으로 작동했습니다. 그러나 위의 Node-struct로 이어지는 컴파일 타임에이 설정을 구성하는 것이 좋을 것입니다.

나는 그것이 동일한 FieldOffset을 가지고 있기 때문에 _linksData와 동일한 메모리 위치를 가리킬 것이라고 기대했다. 그러나 실제로 Links 포인터는 항상 null 포인터입니다.

내 질문은 : 링크가 _linksData와 같은 메모리 위치를 가리키는 방법이 있거나 다른 구조체에 포함 된 구조체의 고정 된 크기의 배열을 갖는 다른 방법이 있습니까? 사전에 모든 답변을

감사합니다 -

마르쿠스

벤 보이트의 게시물을 읽은 후 나는 클래스에 구조체를 변경하지 않고도 비슷한 tryed. 다음은 나를 위해 작동하는 방법입니다.

[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; 
     } 
    } 
} 
+2

마지막 조각이 곤경에 당신을 얻을 위하여려고하고있다. 포인터는 고정 블록 내부에서만 유효합니다. ONC 당신이 빠져 나왔을 때, 반환 된 포인터는 구조체가 저장되어있는 곳에 매달려 있습니다. .NET에는 좋은 곳이 없으며 스택이나 gc 힙이 없습니다. 구조체를 관리되지 않는 메모리로 명시 적으로 마샬링하지 않는 한. –

+0

@ 한스 : Markus는 객체가 사라진 후에 포인터를 사용하지 않을 정도로 똑똑하다고 생각합니다. 이는 스택 변수에 충분합니다. 그러나 클래스 인스턴스의 멤버에게는 실제로 문제가 있으며이를 언급했습니다. –

+0

@ Hans Uhhh - 감사합니다. 그러나 내 실제 프로젝트에서 필자는 예를 들어 Ben과 같은 하나의 Link 만 반환 할 것입니다. – Markus

답변

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 객체에 대한 포인터를 가질 수 있습니다. 여기에 해당되는지는 알 수 없습니다.

:(

전체에 래퍼 시간 :. 내가 클래스에 Node을 변경했다

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); } }; 
} 

주 ... P/호출은 여전히 ​​거의 동일하지만 행동해야

당신이 그렇게하지 않으려면

, 확장 방법은 해답이 될 수 있습니다.

+0

안녕하세요 - 답변 주셔서 감사합니다 (나는 얼마나 빨리 여기 주위에 작동 믿을 수 없어). 이 접근법은 고정 크기 버퍼가 Alois에서 언급 한 것과 같은 기본 유형에서만 작동한다는 메시지를 제공합니다. – Markus

+0

@Markus : 그가 말한 것은 blitable type이 필요하다는 것입니다. 원시 타입의 구조를 제외하고 원시 타입에 대한 제한은 미친 짓이다. 나는 해결 방법을 보았다고 생각합니다 ... 실제로는 아닙니다. C#에서는 내부 포인터가 지원되지 않습니다. –

+0

재산에 대한 귀하의 아이디어는 실제로 저를 테스트 해 보았습니다. 나는 다음 몇 분 안에 그것을 게시 할 것이다. 그것은 값 유형이 필요하기 때문에 클래스로 변경하는 것은 불가능합니다. – Markus

0

블리터블 형식 만 마샬링 할 수 있습니다. 이렇게하면 IntPtr, string, byte, char int, float, double, decimal 및 부호없는 대응 항목의 인스턴스로 제한됩니다. 관심이있는 다른 데이터 구조에 대한 포인터를 정의 할 수 없습니다. 현재 지원되지 않습니다.

Marhsaler는 최종 인스턴스가 얼마나 많은 공간을 차지할 것인지를 알아야하며 언제 중지해야 하는지를 알아야합니다. 관리 유형에 대한 포인터를 정의 할 때 포인터를 마샬링해야합니다. 포인터를 마샬링해야합니다. 포인터가 현재 관리 유형이기 때문에 항상 사본을 가져 오기 때문입니다. 이러한 문제는 어느 정도 해결할 수 있지만 현재까지는 지원되지 않습니다. 아마도 .NET 4.5는 새로운 기능을 제공합니다.

EDIT1 :

당신을 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)); 
     } 
    } 
} 
+0

안녕하세요 - (정말로 빠름) 답변에 감사드립니다. 하지만 실제로는 고정 된 노드 (Node * pNodes = NodesArray)와 같은 포인터를 이미 정의했습니다. NodesArray는 Node [] 유형이고, 네이티브 FileWrite 메소드에서 사용 된 바이트 포인터로 캐스팅되었습니다. 아니면 완전히 다른 경우입니까? – Markus

관련 문제