wat*_*pet 6 .net c# marshalling unions
我遇到了一个奇怪的场景,它在C#/ .NET中包含了包含数组的联合.考虑以下程序:
namespace Marshal
{
class Program
{
[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct InnerType
{
byte Foo;
//[MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.Struct, SizeConst = 1)]
//byte[] Bar;
}
[StructLayout(LayoutKind.Explicit, Pack = 1)]
struct UnionType
{
[FieldOffset(0)]
InnerType UnionMember1;
[FieldOffset(0)]
[MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.Struct, SizeConst = 1)]
byte[] UnionMember2;
}
static void Main(string[] args)
{
Console.WriteLine(@"SizeOf UnionType: {0}", System.Runtime.InteropServices.Marshal.SizeOf(typeof(UnionType)));
}
}
}
Run Code Online (Sandbox Code Playgroud)
如果您运行此程序,您将获得以下异常:
Could not load type 'UnionType' from assembly 'Marshal, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' because it contains an object field at offset 0 that is incorrectly aligned or overlapped by a non-object field.
Run Code Online (Sandbox Code Playgroud)
现在,如果您取消注释两个注释掉的行,程序将运行正常.我想知道为什么会这样.为什么在InnerType中添加额外的数组可以解决问题?顺便说一下,你制作阵列的大小并不重要.如果没有数组,UnionMember1和UnionMember2应该在大小上相互匹配.对于数组,它们的大小不匹配,但不会抛出异常.
更新 将InnerType更改为以下内容也会导致异常(此时在InnerType上):
[StructLayout(LayoutKind.Explicit, Pack = 1)]
struct InnerType
{
[FieldOffset(0)]
byte Foo;
[FieldOffset(1)]
[MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.Struct, SizeConst = 1)]
byte[] Bar;
}
Run Code Online (Sandbox Code Playgroud)
在我看来,这应该等同于原始代码(with LayoutKind.Sequential)byte[] Bar取消注释.
我不相信这里的问题与单词边界有关 - 我使用Pack = 1.相反,我认为它是异常的第二部分,"...它包含一个偏移0处的对象字段,即......与非对象字段重叠." byte []是引用类型,而byte本身是值类型.我可以看到"byte Foo"最终会重叠"byte [] UnionMember2".但是,这仍然无法解释为什么在原始代码中取消注释"byte [] bar"会使异常消失.
我的假设是,顺序布局被否决,如以下答案中所述:当子结构具有 LayoutKind.Explicit 时,不遵循 LayoutKind.Sequential。
请注意,pack 设置为 1 并不会消除填充,即BarFieldOffset 不为 1。它与 4 字节对齐(在 x32 上),并且根据 Marshal.OffsetOf()预期,它应该为 4。
但是,.NET 运行时实际上可能会将引用类型放在托管内存中的Bar字节之前Foo,在这种情况下,它会在与 .NET 的联合中正确重叠UnionMember2。
有趣的是,对于Fooint 和 float 也会发生同样的情况,但是对于 long 和 double 又会出现异常。看起来它按大小对字段进行排序,但如果大小相等,则将引用类型放在第一位。
当我切换到 x64 时,它也可以与 一起使用long Foo,这将支持这个理论。最后,我打开了一个内存窗口(调试->Windows->内存)并输入位置&instance.UnionMember1.Foo并向上滚动一点以显示之前的字节Foo。然后使用直接窗口在 Foo 和 Bar 中设置一个值,该窗口Bar被证明为 0 和Foo4。(添加var instance = new UnionType()到Main)
请记住,这可能不是您想要的,它们Byte[]只是被视为引用类型。您可以将其替换为object. 您也许可以使用它fixed byte Bar[1],具体取决于您的目标。
| 归档时间: |
|
| 查看次数: |
816 次 |
| 最近记录: |