C# 中的联合 - 与非对象字段不正确对齐或重叠
我通过 PInvoke 编组到本机 C dll,它需要以下调用。
private static extern int externalMethod(IntPtr Data, [MarshalAs(UnmanagedType.U4)] ref int dataLength);
dataLength 参数是通过 IntPtr Data 参数传递的结构的长度。如果两者不匹配,它将引发异常。外部方法使用 C Union 将四种类型连接在一起。
我已成功使用 FieldOffsetAttribute 在 C# 中重新创建联合。然后,我计算 C# 联合的长度并使用以下内容调用该方法:
int len = Marshal.SizeOf(data);
IntPtr ptr = Marshal.AllocCoTaskMem(len);
externalMethod(ptr, len);
但是,我收到错误 System.TypeLoadException : ... 无法加载类型,因为它在偏移量 0 处包含错误的对象字段由非对象字段对齐或重叠。
使用以下代码。我相信它可能是字符串之一或整数数组(变量 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;
}
I am marshaling via PInvoke to a native C dll which expects the following call.
private static extern int externalMethod(IntPtr Data, [MarshalAs(UnmanagedType.U4)] ref int dataLength);
The dataLength parameter is the length of the struct being passed via the IntPtr Data parameter. It throws an exception if the two do not match. The external method uses a C Union joining together four types.
I've managed to recreate unions in C# by using the FieldOffsetAttribute. I am then calculating the length of the C# union and calling the method with the following:
int len = Marshal.SizeOf(data);
IntPtr ptr = Marshal.AllocCoTaskMem(len);
externalMethod(ptr, len);
However, I get the error 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.
with the following code. I believe it is perhaps either one of the strings or the integer array (variable B7)? How would I go about changing this to make it work - do I have to break the integer array into multiple variables?
[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;
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
只需直接使用 A/B/C/D 结构并跳过联合即可。在您的 extern 调用中,只需在方法声明中替换正确的结构即可。
如果非托管方法实际上接受联合并且根据传递的类型表现不同,那么您可以声明不同的 extern 方法,这些方法最终都会调用相同的非托管入口点。
更新了“长度”参数。该逻辑仍然适用。只需创建一个“包装”方法并执行相同的操作即可。
Just use the A/B/C/D structs directly and skip the union. In your extern calls, simply substitute the correct struct in the method declaration.
If the unmanaged methods actually accept a union and behave differently based on the type passed, then you can declare different extern methods that all end up calling the same unmanaged entry point.
Updated for "length" parameter. The logic still applies. Just create a "wrapper" method and do the same thing.
如果不知道你想要实现什么目标,就很难回答这个问题。对于任何正常的用例来说,显式布局的结构都是一个非常糟糕的选择;仅当您在本机调用 (pinvoke) 中使用数据时,这才有意义,在这种情况下,您肯定不想使用托管类
string
。[MarshalAs]
属性仅在调用期间生效,而不是在每次由托管代码读取或写入该字段时始终生效。它不允许您将字符串指针与 int 重叠,因为这样做会允许您将指针设置为无意义的值,然后访问该字符串会使 CLR 崩溃。对于数组也是如此,所以你也不能使用char[]
。如果您是需要调用的本机代码的作者,那么我强烈建议编写四个单独的方法,而不是编写一个接受四种完全不同的数据结构的方法。
如果您无法更改本机代码,那么您始终可以声明四个结构体
A
、B
、C
和D
> 就像你现在所做的那样,直接使用它们,而不需要联合。只需为同一个本机函数声明四个不同的 pinvoke 声明(使用[DllImport]
属性上的EntryPoint
属性)。It is difficult to answer this question without knowing what you are trying to achieve. An explicitly-layouted struct is a very bad choice for any normal use-case; this only makes sense if you are using the data in native calls (pinvoke), and in those cases you definitely don’t want to use the managed class
string
. The[MarshalAs]
attribute only takes effect during the call invocations, not constantly every time the field is read from or written to by your managed code. It doesn’t allow you to overlap a string pointer with an int because doing so would allow you to set the pointer to a meaningless value and then accessing the string would crash the CLR. The same is true for arrays, so you can’t usechar[]
either.If you are the author of the native code that you need to call, then I strongly recommend to write four separate methods instead of a single one that accepts four completely different data structures.
If you cannot change the native code, then you could always declare your four structs
A
,B
,C
andD
the way you do now and just use them directly, without the union. Just declare four different pinvoke declarations for the same native function (use theEntryPoint
property on the[DllImport]
attribute).