2014-01-30 2 views
6

C#/.NET에서 배열을 포함하는 조합을 마샬링하는 이상한 시나리오가 발생했습니다. 다음 프로그램을 고려 :이 프로그램을 실행하면배열로 마샬링 유니온

namespace Marshal 
{ 
    class Program 
    { 
     [StructLayout(LayoutKind.Sequential, Pack = 1)] 
     struct InnerType 
     { 
      byte Foo; 
      //[MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.Struct, SizeConst = 1)] 
      //byte[] Bar; 
     } 


     [StructLayout(LayoutKind.Explicit, Pack = 1)] 
     struct UnionType 
     { 
      [FieldOffset(0)] 
      InnerType UnionMember1; 

      [FieldOffset(0)] 
      [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.Struct, SizeConst = 1)] 
      byte[] UnionMember2; 
     } 

     static void Main(string[] args) 
     { 
      Console.WriteLine(@"SizeOf UnionType: {0}", System.Runtime.InteropServices.Marshal.SizeOf(typeof(UnionType))); 
     } 
    } 
} 

, 당신은 다음과 같은 예외를 얻을 수 있습니다 :

Could not load type 'UnionType' from assembly 'Marshal, 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. 

을 이제 두 개의 주석 라인의 주석을 해제하는 경우, 프로그램이 잘 실행됩니다. 왜 이런지 궁금하네요. InnerType에 추가 배열을 추가하면 문제가 해결되는 이유는 무엇입니까? 덧붙여 말하자면 배열을 어떤 크기로 만들지는 중요하지 않습니다. 배열이 없으면 UnionMember1과 UnionMember2는 서로 크기가 일치해야합니다. 배열에서는 크기가 일치하지 않지만 예외는 발생하지 않습니다. 다음에 InnerType 변경

업데이트 도 (InnerType에이 시간) 예외를 발생합니다

[StructLayout(LayoutKind.Explicit, Pack = 1)] 
struct InnerType 
{ 
    [FieldOffset(0)] 
    byte Foo; 

    [FieldOffset(1)] 
    [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.Struct, SizeConst = 1)] 
    byte[] Bar; 
} 

이 원래의 코드와 동일해야한다는 날 것으로 보인다 (LayoutKind.Sequential로) 여기서 byte[] Bar 주석 처리되지 않았습니다.

여기서 문제는 단어 경계와 관련이 없다고 생각합니다. 저는 Pack = 1을 사용하고 있습니다. 오히려 예외의 두 번째 부분이라고 생각합니다. "... 오프셋 0은 ... 비 - 객체 필드에 의해 중첩됩니다. " byte []는 참조 유형이고 byte 자체는 값 유형입니다. "byte Foo"가 "byte [] UnionMember2"와 겹쳐지는 것을 볼 수 있습니다. 그러나 이것은 여전히 ​​원래 코드에서 "byte [] bar"의 주석 처리를 제거하면 예외가 사라지는 이유를 설명하지 못합니다.

+0

이 게시물이 도움이 될 것입니다 http://stackoverflow.com/questions/1190079/incorrectly-aligned-or-overlapped-by-a-non-object-field-error, http://stackoverflow.com/questions/ 4673099/c-sharp-un-object-field와 잘못 정렬되거나 겹친 유니온. UnionMember2의 필드 오프셋을 8로 변경하면이 문제가 해결됩니다. –

+0

@David Venegoni - 감사합니다. 나는 이미 이것들을 보았다. # 1190079는 CF 마샬 러에만 적용됩니다. CF를 사용하지 않고 팩을 사용하고 있습니다. # 4673099 해당 사용자의 문제에 대한 해결책이 있지만 예외의 원인을 정확히 설명하지는 않습니다. 오프셋 8을 사용하면 예외 (및 공용체)가 제거되지만 InnerType.bar를 추가하면 예외가 표시되지 않는 이유에 대한 단서가 제공되지 않습니다. – watkipet

+0

pinvoke marshaller는 구조체를 byte []로 매핑 할 수 없습니다. 이는 의미있는 변환이 아닙니다. 크기 1도 기괴합니다. 이것은 단순한 바이트입니다. byte []와 같은 참조 유형을 값 유형과 겹치게 할 수도 없기 때문에 가비지 수집기는 필드에 참조가 저장되는지 정확하게 확인할 수 없습니다. 고정하는 대신 필드를 고정 크기 버퍼로 선언해야합니다. –

답변

0

내 가설은이 SO 응답에 설명 된대로 순차적 레이아웃이 무시됩니다. LayoutKind.Sequential not followed when substruct has LayoutKind.Explicit.

주 1 팩 설정이 패딩을 제거하지 않는, 즉 Bar 그것은 (X32)에 4 바이트로 정렬 1의 FieldOffset이없는, 예상대로 Marshal.OffsetOf()에 따라 4에 있어야합니다.

그러나, .NET 런타임은 실제로 제대로 UnionMember2와 노동 조합에 중복 것이다 경우 관리되는 메모리의 바이트 Foo, 전에 참조 형 Bar을 넣을 수 있습니다.

흥미로운 점은 Foo int와 float가 동일하지만 long과 double을 사용하면 다시 예외가 발생한다는 것입니다. 그것은 크기에 필드를 정렬하는 것처럼 보이지만 크기가 같으면 먼저 참조 형식을 넣습니다.

내가 x64로 전환했을 때 long Foo도이 이론을 뒷받침했습니다. 마지막으로, 메모리 창 (Debug-> Windows-> Memory)을 열고 &instance.UnionMember1.Foo 위치에 입력 한 다음 Foo 전에 바이트를 나타 내기 위해 비트를 위로 스크롤했습니다. 그런 다음이 당신이 의도 아닐입니다 불구하고, Byte[]

염두에 보관하십시오 (var instance = new UnionType()Main에 추가) Bar가 0이고 Foo 4.에있다 입증 직접 실행 창을 사용하여 푸과 바의 값을 설정 참조 유형으로 간주됩니다. object으로 바꿀 수 있습니다. 목표에 따라 fixed byte Bar[1]을 대신 사용할 수 있습니다.