C#中的联合 - 与非对象字段错误对齐或重叠

时间:2011-01-12 19:27:16

标签: c# marshalling union

我正在通过PInvoke编组到一个本地C dll,它需要以下调用。

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

dataLength参数是通过IntPtr Data参数传递的struct的长度。如果两者不匹配则抛出异常。外部方法使用C Union连接四种类型。

我已经设法使用FieldOffsetAttribute在C#中重新创建了联合。然后我计算C#union的长度并使用以下方法调用该方法:

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

2 个答案:

答案 0 :(得分:4)

直接使用A / B / C / D结构并跳过联合。在你的extern调用中,只需在方法声明中替换正确的结构。

extern void UnionMethodExpectingA( A a );

如果非托管方法实际上接受了一个联合并且根据传递的类型表现不同,那么你可以声明所有最终调用相同非托管入口点的不同extern方法。

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

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

针对“长度”参数进行了更新。 逻辑仍然适用。只需创建一个“包装器”方法并执行相同的操作。

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

答案 1 :(得分:3)

如果不知道自己想要实现什么,就很难回答这个问题。对于任何正常的用例,明确布局的结构是一个非常糟糕的选择;这只有在你使用本机调用(pinvoke)中的数据时才有意义,在这种情况下你肯定不想使用托管类string[MarshalAs]属性仅在调用调用期间生效,而不是每次从托管代码读取或写入字段时都会生效。它不允许您将字符串指针与int重叠,因为这样做会允许您将指针设置为无意义的值,然后访问该字符串会使CLR崩溃。对于数组也是如此,因此您也不能使用char[]

如果您是需要调用的本机代码的作者,那么我强烈建议您编写四种不同的方法,而不是一种接受四种完全不同的数据结构的方法。

如果您无法更改原生代码,那么您始终可以按照现在的方式声明四个结构ABCD并使用他们直接,没有工会。只需为同一本机函数声明四个不同的pinvoke声明(使用EntryPoint属性上的[DllImport]属性。