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

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

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

dataLength参数是通过IntPtr Data参数传递的struct的长度。 如果两者不匹配,则抛出exception。 外部方法使用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; } 

只需直接使用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 ); 

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

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

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

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

如果您无法更改本机代码,那么您可以按照现在的方式声明四个结构ABCD ,并且只需直接使用它们,而无需使用联合。 只需为同一本机函数声明四个不同的pinvoke声明(使用[DllImport]属性上的EntryPoint属性)。