C# 中的联合 - 与非对象字段不正确对齐或重叠

发布于 2024-10-11 16:30:12 字数 2157 浏览 2 评论 0原文

我通过 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

喵星人汪星人 2024-10-18 16:30:12

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

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.

extern void UnionMethodExpectingA( A a );

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.

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

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

Updated for "length" parameter. The logic still applies. Just create a "wrapper" method and do the same thing.

void CallScaryMethodExpectingA( A a )
{
  ScaryMethodExpectingA( a, Marshal.SizeOf( a ) );
} 
空城仅有旧梦在 2024-10-18 16:30:12

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

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

如果您无法更改本机代码,那么您始终可以声明四个结构体 ABCD > 就像你现在所做的那样,直接使用它们,而不需要联合。只需为同一个本机函数声明四个不同的 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 use char[] 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 and D 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 the EntryPoint property on the [DllImport] attribute).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文