2011-01-12 2 views
2

다음 호출이 필요한 네이티브 C dll에 PInvoke를 통해 마샬링하고 있습니다.C#의 유니온이 잘못 정렬되었거나 비 개체 필드와 겹침

private static extern int externalMethod(IntPtr Data, [MarshalAs(UnmanagedType.U4)] ref int dataLength); 

dataLength 매개 변수는 IntPtr Data 매개 변수를 통해 전달되는 구조체의 길이입니다. 두 요소가 일치하지 않으면 예외가 발생합니다. 외부 메서드는 4 가지 유형을 함께 결합하는 C Union을 사용합니다.

FieldOffsetAttribute를 사용하여 C#에서 유니온을 다시 생성 할 수있었습니다. 그때는 C# 노조의 길이를 계산하고 다음과 같이 메서드를 호출하고 있습니다 :

int len = Marshal.SizeOf(data); 
IntPtr ptr = Marshal.AllocCoTaskMem(len); 
externalMethod(ptr, len); 

그러나, 나는 다음과 같은 코드 오류 System.TypeLoadException : ... Could not load type because it contains an object field at offset 0 that is incorrectly aligned or overlapped by a non-object field.를 얻을. 아마도 그것은 문자열 중 하나 또는 정수 배열 (변수 B7) 중 하나라고 생각하십니까? 정수 배열을 여러 변수로 나누어야합니까?

[StructLayoutAttribute(LayoutKind.Explicit)] 
public struct Union{ 
    [FieldOffset(0)] 
    public A a; 

    [FieldOffset(0)] 
    public B b; 

    [FieldOffset(0)] 
    public C c; 

    [FieldOffset(0)] 
    public D d; 
} 

[StructLayout(LayoutKind.Sequential)] 
public struct A 
{ 
    public int A1; 
    public int A2; 
    public int A3; 
    [MarshalAs(UnmanagedType.LPTStr, SizeConst = 17)] 
    public string A4; 
    [MarshalAs(UnmanagedType.LPTStr, SizeConst = 4)] 
    public string A5; 
} 

[StructLayout(LayoutKind.Sequential)] 
public struct B 
{ 
    public int B1; 
    [MarshalAs(UnmanagedType.LPTStr, SizeConst = 2)] 
    public string B2; 
    [MarshalAs(UnmanagedType.LPTStr, SizeConst = 4)] 
    public string B3; 
    [MarshalAs(UnmanagedType.LPTStr, SizeConst = 6)] 
    public string B4; 
    public int B5; 
    public int B6; 
    [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U4, SizeConst = 255)] 
    public int[] B7; 
} 

[StructLayout(LayoutKind.Sequential)] 
public struct C 
{ 
    public int C1; 
    public int C2; 
    public int C3; 
    public int C4; 
    [MarshalAs(UnmanagedType.LPTStr, SizeConst = 32)] 
    public string C5; 
    public float C6; 
    public float C7; 
    public float C8; 
    public float C9; 
    public float C10; 
    public float C11; 
    public float C12; 
    public float C13; 
    public float C14; 
} 

[StructLayout(LayoutKind.Sequential)] 
public struct D 
{ 
    public int D1; 
    [MarshalAs(UnmanagedType.LPTStr, SizeConst = 36)] 
    public string D2; 
} 

답변

4

A/B/C/D 구조체를 직접 사용하고 공용체를 건너 뜁니다. extern 호출에서 메서드 선언의 올바른 구조체를 간단히 대체하십시오. 관리되지 않는 방법이 실제로 노동 조합을 승인하고 통과 유형에 따라 다르게 동작하는 경우

extern void UnionMethodExpectingA(A a); 

는, 당신은 모두가 같은 관리되지 않는 진입 점을 호출 결국 다른 통근 방법을 선언 할 수 있습니다.

[DllImport("unmanaged.dll", EntryPoint="ScaryMethod")] 
extern void ScaryMethodExpectingA(A a); 

[DllImport("unmanaged.dll", EntryPoint="ScaryMethod")] 
extern void ScaryMethodExpectingB(B b); 

는 "길이"매개 변수에 업데이트되었습니다. 논리가 여전히 적용됩니다. "래퍼 (wrapper)"방법을 만들고 똑같은 일을하십시오.

void CallScaryMethodExpectingA(A a) 
{ 
    ScaryMethodExpectingA(a, Marshal.SizeOf(a)); 
} 
3

달성하려는 내용을 알지 못하면이 질문에 대답하기가 어렵습니다. 명시 적으로 레이아웃 된 구조체는 일반적인 사용 사례의 경우 매우 나쁜 선택입니다. 이것은 네이티브 호출 (pinvoke)에서 데이터를 사용하는 경우에만 의미가 있으며,이 경우에는 관리되는 클래스 string을 사용하고 싶지 않을 것입니다. [MarshalAs] 특성은 호출 호출 중에 만 적용되며, 관리 코드에서 필드를 읽거나 쓸 때마다 항상 적용됩니다. 그렇게하면 포인터를 무의미한 값으로 설정하고 문자열에 액세스하면 CLR이 손상 될 수 있으므로 int로 문자열 포인터를 겹치게 할 수 없습니다. 어레이에 대해서도 마찬가지이므로 char[]을 사용할 수 없습니다.

네가 호출해야하는 네이티브 코드의 작성자라면 네 가지 완전히 다른 데이터 구조를 허용하는 네 가지 별도의 메소드 대신 네 가지 별도의 메소드를 작성하는 것이 좋습니다. 네이티브 코드를 변경할 수없는 경우

는, 당신은 항상 노동 조합없이, 당신의 네 구조체 A, B, CD 당신이 지금 그냥 직접 사용하는 방법을 선언 할 수있다. 동일한 네이티브 함수에 대해 네 가지 다른 pinvoke 선언을 선언하면됩니다 ([DllImport] 속성의 EntryPoint 속성 사용).

+0

답변을 주셔서 감사합니다. 나는 PInvoke 호출에서 외부 파티의 레거시 코드를 사용하고 있습니다. 확실히 4 가지 다른 통화가 선호되지만, 전적으로 변경할 수는 없습니다. 각 문자에 대해 별도의 변수를 사용할 수 있습니까? –

+0

@sprocketonline : 마지막 단락에서 제안을 계속 사용할 수 있습니다.폴 알렉산더 (Paul Alexander)의 대답은이 전략을 모범으로 보여줍니다. – Timwi

+0

외부 메서드에서 길이를 전달할 것으로 예상되므로 질문을 업데이트했습니다. Union의 길이는 구조체 B에서 가장 큰 멤버의 길이가 될 것이라고 가정하고, 개별 구조체에 대해 계산하면 올바르지 않습니다. –