C# StructLayout.Explicit 问题

Posted

技术标签:

【中文标题】C# StructLayout.Explicit 问题【英文标题】:C# StructLayout.Explicit Question 【发布时间】:2010-11-14 01:03:16 【问题描述】:

我试图理解为什么下面的第二个示例没有问题,但第一个示例给了我下面的异常。在我看来,这两个例子都应该根据描述给出一个例外。谁能赐教?

未处理的异常: System.TypeLoadException:不能 从加载类型“StructTest.OuterType” 程序集'StructTest,版本= 1.0.0.0, 文化=中立,PublicKeyToken=null' 因为它包含一个对象字段 未正确对齐的偏移量 0 或被非对象字段重叠。 在 StructTest.Program.Main(字符串 [] args) 按任意键继续。 . .

示例 1

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;

namespace StructTest

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    struct InnerType
    
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 100)]
        char[] buffer;
    

    [StructLayout(LayoutKind.Explicit)]
    struct OuterType
    
        [FieldOffset(0)]
        int someValue;

        [FieldOffset(0)]
        InnerType someOtherValue;
    

    class Program
    
        static void Main(string[] args)
        
            OuterType t = new OuterType();
            System.Console.WriteLine(t);
        
    


示例 2

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;

namespace StructTest

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    struct InnerType
    
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 100)]
        char[] buffer;
    

    [StructLayout(LayoutKind.Explicit)]
    struct OuterType
    
        [FieldOffset(4)]
        private int someValue;

        [FieldOffset(0)]
        InnerType someOtherValue;

    

    class Program
    
        static void Main(string[] args)
        
            OuterType t = new OuterType();
            System.Console.WriteLine(t);
        
    

【问题讨论】:

【参考方案1】:

公共语言运行时包含一个验证程序,可确保正在运行的代码(可验证的 IL)不会损坏托管环境中的内存。这可以防止您声明这样一个字段重叠的结构。基本上,您的结构包含两个数据成员。一个整数(4 个字节)和一个本机整数(指针大小)。在您可能正在运行代码的 32 位 CLR 上,char[] 将占用 4 个字节,因此如果您将整数放在距离结构开头不到 4 个字节的位置,您将有重叠的字段。有趣的是,您的两个代码 sn-ps 在 64 位运行时均失败,因为指针大小为 8 个字节。

【讨论】:

我明白了,所以如果我想正确执行此操作,以便在 32 或 64 位机器上正常工作,我需要分别使用 8 和 0 的偏移量,对吗?问题是我真正想要创建的是一个联合,如果我不能使用相同的偏移量,它看起来最终不会是一个。 是的。它适用于偏移量为 8 和 0 的 x64 CLR。​​请注意,虽然代码 sn-p 有效,但如果您在现实世界的应用程序中执行此操作,您可能会与一些非托管的东西交互,这些东西会期望精确的偏移量。如果你将它设置为 8,它可能会在 32 位机器上失败(不是这个 sn-p 本身,而是你正在与之交互的非托管代码)。 顺便说一下,如果它们是 非托管类型,你可以有重叠的字段,但是 .NET 数组是一个引用类型(它不能根据 C# 规范被视为非托管类型)。因此,您不能将引用类型作为 C# 中的联合成员。如果你真的需要这个,你应该考虑使用指针类型之类的东西作为结构成员而不是数组。 谢谢。我正在研究直接在 OuterType 中使用大小为 100 的固定字符的不安全结构,而不是使用 InnerType。听起来这可能是更好的方法。 规则更加晦涩难懂。引用类型的两个字段(甚至完全不相关)可以重叠,在这种情况下,程序集是有效的,但不可验证(因此它将在 FullTrust 中运行)。所以你仍然可以通过重叠对不兼容类型的引用来搞砸事情。我认为“部分重叠”限制的原因是因为它绝对保证将GC吹出水面。而使用完全引用重叠 GC 就可以了,而且当您有一个鉴别器字段来跟踪类型时,它实际上会很有用(节省强制转换运行时类型检查)。【参考方案2】:

我想我会用我用来创建工会的解决方案来回应——这是我的初衷。我使用了一个不安全的结构和一个固定数组,然后使用一个属性与固定数组进行交互。我相信这应该做我想做的。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;

namespace StructTest


    [StructLayout(LayoutKind.Explicit)]
    unsafe struct OuterType
    
        private const int BUFFER_SIZE = 100;

        [FieldOffset(0)]
        private int transactionType;

        [FieldOffset(0)]
        private fixed byte writeBuffer[BUFFER_SIZE];

        public int TransactionType
        
            get  return transactionType; 
            set  transactionType = value; 
        

        public char[] WriteBuffer
        
            set
            
                char[] newBuffer = value;

                fixed (byte* b = writeBuffer)
                
                    byte* bptr = b;
                    for (int i = 0; i < newBuffer.Length; i++)
                    
                         *bptr++ = (byte) newBuffer[i];
                    
                
            

            get
            
                char[] newBuffer = new char[BUFFER_SIZE];

                fixed (byte* b = writeBuffer)
                
                    byte* bptr = b;
                    for (int i = 0; i < newBuffer.Length; i++)
                    
                        newBuffer[i] = (char) *bptr++;
                    
                

                return newBuffer;
            
        
    

    class Program
    
        static void Main(string[] args)
        
            OuterType t = new OuterType();
            System.Console.WriteLine(t);
        
    

【讨论】:

以上是关于C# StructLayout.Explicit 问题的主要内容,如果未能解决你的问题,请参考以下文章

译《C# 小技巧 -- 编写更优雅的 C#》原书名《C# Tips -- Write Better C#》

C#进阶C# 泛型

C#进阶C# 匿名方法

C#进阶C# 多线程

C# 教程

[C#教程01]C# 简介