zoukankan      html  css  js  c++  java
  • C# Struct结构体里数组长度的指定

    转自:C# Struct结构体里数组长度的指定

     
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    typedef struct Point{
        unsigned short x;
        unsigned short y;
    }mPoint;//点坐标
     
    typedef struct Line{
          mPoint p[2];
          unsigned char name[20];
          unsigned int  mark[5];
    }mLine; //线坐标

      

    如上一个C++的结构体Line,分别有3个数组

    • 结构体数组
    • 字节数组
    • int数组

    简单翻译成C#如下:

    1
    2
    3
    4
    5
    6
    public  struct Point{
         public ushort x;
         public ushort y;
     };//点坐标
     
    public  struct Line{
    1
    Point[] p; <br>            byte[] name; <br>            uint[] mark;
    1
    }; //线坐标

     

    但这样无法使用

    C# byte[]、struct、intptr等的相互转换

    这篇里的StructToBytes  BytesToStruct等函数快捷转换字节用来作为和C++程序的通信。

    MessageBox.Show(Marshal.SizeOf(typeof(Line)).ToString());

    也是无法计算结构体长度的。

    要解决这个问题,首先要看下字节对齐的概念

    现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,
    但实际情况是在访问特定类型变量的时候经常在特 定的内存地址访问,这就需要各种类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。
        对齐的作用和原因:各个硬件平台对存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取。
    比如有些架构的CPU在访问 一个没有进行对齐的变量的时候会发生错误,那么在这种架构下编程必须保证字节对齐.
    其他平台可能没有这种情况,但是最常见的是如果不按照适合其平台要求对 数据存放进行对齐,会在存取效率上带来损失。
    比如有些平台每次读都是从偶地址开始,如果一个int型(假设为32位系统)如果存放在偶地址开始的地方,那 么一个读周期就可以读出这32bit,
    而如果存放在奇地址开始的地方,就需要2个读周期,并对两次读出的结果的高低字节进行拼凑才能得到该32bit数 据。显然在读取效率上下降很多。
     
    现在已知32位机器上各种数据类型的长度如下:
    char:1(有符号无符号同)   
    short:2(有符号无符号同)   
    int:4(有符号无符号同)   
    long:4(有符号无符号同)   
    float:4     double:8
     
    编译器是按照什么样的原则进行对齐的?
     
        先让我们看四个重要的基本概念:
    1.数据类型自身的对齐值:
    对于char型数据,其自身对齐值为1,对于short型为2,对于int,float,double类型,其自身对齐值为4,单位字节。
    2.结构体或者类的自身对齐值:其成员中自身对齐值最大的那个值。
    3.指定对齐值:#pragma pack (value)时的指定对齐值value。
    4.数据成员、结构体和类的有效对齐值:自身对齐值和指定对齐值中小的那个值。

    一般强制1字节对齐就好了,实现上很简单,在结构体上面加入

    1
    [StructLayout(LayoutKind.Sequential, Pack = 1)]

      即可。

    代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public  struct Point{
                public ushort x;
                public ushort y;
            };//点坐标
     
    [StructLayout(LayoutKind.Sequential, Pack = 1)]
           public  struct Line{
                Point[] p;
                byte[] name;
                uint[] mark;
            }; //线坐标

      

    但这样明显还不够,依然没指定数组的长度。

    通过搜索资料最终尝试出的解决办法如下:

    • 结构体数组
    1
    [MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst = 2, ArraySubType = UnmanagedType.Struct)]
    1
    Point[] p;

      注意:

         MarshalAs属性指示如何在托管代码和非托管代码之间封送数据。

         ByValArray 

    当 MarshalAsAttribute.Value 设置为 ByValArray 时,必须设置 SizeConst 以指示数组中的元素数。当需要区分字符串类型时,ArraySubType 字段可以选择包含数组元素的 UnmanagedType。此 UnmanagedType 只可用于作为结构中的字段的数组。 

         SizeConst = 2表示数组长度为2

         ArraySubType = UnmanagedType.Struct 表示这个数组是Struct结构体数组

    • 字节数组
    1
    2
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)]
    public byte[] name; //20

      字节数组最简单了,同理

    1
    SizeConst = 20表示长度20字节
    • int数组

    int数组的解决办法网上似乎并没有,想了想上面 结构体数组  的解决办法后决定试一下。

    1
    2
    [MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst = 5, ArraySubType = UnmanagedType.U4)]
                public uint[] mark;

      果然解决了

    1
    SizeConst = 5表示数组长度为5
    1
    ArraySubType = UnmanagedType.U4 表示数组内容是无符号4字节的整数,=uint类型<br><br><br>至此,几种数组全部搞定了,看下效果
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    using System.Runtime.InteropServices;    <br><br>[StructLayout(LayoutKind.Sequential, Pack = 1)]
        public  struct Point{
            public ushort x;
            public ushort y;
        };//点坐标
     
       [StructLayout(LayoutKind.Sequential, Pack = 1)]
       public  struct Line{
            [MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst = 2, ArraySubType = UnmanagedType.Struct)]
            public Point[] p;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)]
            public byte[] name;
            [MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst = 5, ArraySubType = UnmanagedType.U4)]
            public uint[] mark;
        }; //线坐标

      编译运行正常

    MessageBox.Show(Marshal.SizeOf(typeof(Line)).ToString());

    口算下长度  

    点:2 * 2(short) = 4字节

    line里2个点即8字节

    line里name长度20字节

    line里5个uint数字 5* 4 = 20字节

    20+20+8 = 48字节,长度正确

    使用

    C# byte[]、struct、intptr等的相互转换

    这篇里的StructToBytes  BytesToStruct等函数快捷转换字节测试,正常!

    测试代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    Point p;
    p.x = 1; p.y = 2;
    Point p2;
    p2.x = 3; p2.y = 4;<br>
    uint[] uss = new uint[5];
    uss[0] = 111;
    uss[1] = 222;
    uss[2] = 333;
    uss[3] = 444;
    uss[4] = 555;
     
    Line ll;
    ll.mark = uss;
    byte[] bb = new byte[20];
    byte[] bb2 = Encoding.UTF8.GetBytes("测试");
    Array.Copy(bb2, bb, bb2.Length);
    ll.name = bb;
    ll.p = new NetProtocol.Point1[] { p , p2 };
    byte[] b = Common.StructToBytes(ll);
    object oo = Common.BytesToStruct(b, typeof(Line));
    NetProtocol.Line1 test2 = (Line)oo;
     
    string s = Encoding.UTF8.GetString(test2.name).Replace("""");

      

    两次转换后的字节和值都是正确的。

    UnmanagedType 枚举

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
        成员名称    说明
    由 .NET Compact Framework 支持 AnsiBStr    长度前缀为单字节的 ANSI 字符串。可以在 String 数据类型上使用此成员。
    由 .NET Compact Framework 支持 AsAny   一个动态类型,将在运行时确定对象的类型,并将该对象作为所确定的类型进行封送处理。仅对平台调用方法有效。
    由 .NET Compact Framework 支持 Bool    4 字节布尔值(true != 0、false = 0)。这是 Win32 BOOL 类型。
    由 .NET Compact Framework 支持 BStr    长度前缀为双字节的 Unicode 字符串。可以在 String 数据类型上使用此成员(它是 COM 中的默认字符串)。
    由 .NET Compact Framework 支持 ByValArray  当 MarshalAsAttribute.Value 设置为 ByValArray 时,必须设置 SizeConst 以指示数组中的元素数。当需要区分字符串类型时,ArraySubType 字段可以选择包含数组元素的 UnmanagedType。此 UnmanagedType 只可用于作为结构中的字段的数组。
    由 .NET Compact Framework 支持 ByValTStr   用于在结构中出现的内联定长字符数组。与 ByValTStr 一起使用的字符类型由应用于包含结构的 System.Runtime.InteropServices.StructLayoutAttribute 的 System.Runtime.InteropServices.CharSet 参数确定。应始终使用 MarshalAsAttribute.SizeConst 字段来指示数组的大小。
    .NET Framework 的 ByValTStr 类型的行为类似于结构中的 C 样式、固定大小的字符串(例如,char s[5])。托管代码中的行为与 Microsoft Visual Basic 6.0 中的行为不同,后者不是空终止(例如,MyString As String * 5)。
     
    由 .NET Compact Framework 支持 Currency    在 System.Decimal 上使用,以将十进制数值作为 COM 货币类型而不是 Decimal 封送。
    由 .NET Compact Framework 支持 CustomMarshaler 当与 MarshalAsAttribute.MarshalType 或 MarshalAsAttribute.MarshalTypeRef 一起使用时,指定自定义封送拆收器类。MarshalAsAttribute.MarshalCookie 字段可用于将附加信息传递给自定义封送拆收器。可以在任何引用类型上使用此成员。
    由 .NET Compact Framework 支持 Error   此与 I4 或 U4 关联的本机类型将导致参数作为导出类型库中的 HRESULT 导出。
    由 .NET Compact Framework 支持 FunctionPtr 一个可用作 C 样式函数指针的整数。可将此成员用于 Delegate 数据类型或从 Delegate 继承的类型。
    由 .NET Compact Framework 支持 I1  1 字节有符号整数。可使用此成员将布尔值转换为 1 字节、C 样式的 booltrue = 1、false = 0)。
    由 .NET Compact Framework 支持 I2  2 字节有符号整数。
    由 .NET Compact Framework 支持 I4  4 字节有符号整数。
    由 .NET Compact Framework 支持 I8  8 字节有符号整数。
    由 .NET Compact Framework 支持 IDispatch   一个 COM IDispatch 指针(在 Microsoft Visual Basic 6.0 中为 Object)。
    由 .NET Compact Framework 支持 Interface   COM 接口指针。从类元数据获得接口的 Guid。如果将此成员应用于类,则可以使用该成员指定确切的接口类型或默认的接口类型。当应用于 Object 数据类型时,此成员将产生 UnmanagedType.IUnknown 行为。
    由 .NET Compact Framework 支持 IUnknown    COMIUnknown 指针。可以在 Object 数据类型上使用此成员。
    由 .NET Compact Framework 支持 LPArray 指向 C 样式数组的第一个元素的指针。当从托管到非托管进行封送处理时,该数组的长度由托管数组的长度确定。当从非托管到托管进行封送处理时,将根据 MarshalAsAttribute.SizeConst 和 MarshalAsAttribute.SizeParamIndex 字段确定该数组的长度,当需要区分字符串类型时,还可以后跟数组中元素的非托管类型。
    由 .NET Compact Framework 支持 LPStr   单字节、空终止的 ANSI 字符串。可在 System.String 或 System.Text.StringBuilder 数据类型上使用此成员。
    由 .NET Compact Framework 支持 LPStruct    一个指针,它指向用于封送托管格式化类的 C 样式结构。仅对平台调用方法有效。
    由 .NET Compact Framework 支持 LPTStr  与平台相关的字符串:在 Windows 98 上为 ANSI,在 Windows NT 和 Windows XP 上为 Unicode。该值仅对平台调用受支持,而对 COM Interop 则不受支持,原因是不支持导出 LPTStr 类型的字符串。
    由 .NET Compact Framework 支持 LPWStr  一个 2 字节、空终止的 Unicode 字符串。
    请注意,如果非托管字符串不是使用非托管的 CoTaskMemAlloc 函数创建的,则不能在此非托管字符串中使用 LPWStr 值。
     
    由 .NET Compact Framework 支持 R4  4 字节浮点数。
    由 .NET Compact Framework 支持 R8  8 字节浮点数。
    由 .NET Compact Framework 支持 SafeArray   SafeArray 是自我描述的数组,它带有关联数组数据的类型、秩和界限。可将此成员与 MarshalAsAttribute.SafeArraySubType 字段一起使用,以重写默认元素类型。
    由 .NET Compact Framework 支持 Struct  一个用于封送托管格式化类和值类型的 VARIANT。
    由 .NET Compact Framework 支持 SysInt  与平台相关的有符号整数。在 32 位 Windows 上为 4 字节,在 64 位 Windows 上为 8 字节。
    由 .NET Compact Framework 支持 SysUInt 与平台相关的无符号整数。在 32 位 Windows 上为 4 字节,在 64 位 Windows 上为 8 字节。
    由 .NET Compact Framework 支持 TBStr   一个有长度前缀的与平台相关的 char 字符串。在 Windows 98 上为 ANSI,在 Windows NT 上为 Unicode。很少用到这个类似于 BSTR 的成员。
    由 .NET Compact Framework 支持 U1  1 字节无符号整数。
    由 .NET Compact Framework 支持 U2  2 字节无符号整数。
    由 .NET Compact Framework 支持 U4  4 字节无符号整数。
    由 .NET Compact Framework 支持 U8  8 字节无符号整数。
    由 .NET Compact Framework 支持 VariantBool 2 字节、OLE 定义的 VARIANT_BOOL 类型(true = -1、false = 0)。
    由 .NET Compact Framework 支持 VBByRefStr  允许 Visual Basic 2005 在非托管代码中更改字符串,并将结果在托管代码中反映出来。该值仅对平台调用受支持。

      

     参考:

    https://msdn.microsoft.com/zh-cn/magazine/system.runtime.interopservices.unmanagedtype(v=vs.80).aspx

    http://blog.chinaunix.net/uid-14802518-id-2784907.html

  • 相关阅读:
    thinkphp引入phpmailer发送邮件
    让火狐的DIV被内容自动撑开
    mysql 日期操作 增减天数、时间转换、时间戳
    [MySQL] 几句MySQL时间筛选SQL语句[进入查看]
    公钥和私钥
    SSI整合搭建Struts2+Spring+Ibatis框架
    目前 NORTON SEP 及各类产品 离线升级包下载及升级方法
    Spring 3.1.1 + Struts 2.3.1.2 + Hibernate 4.1 整合(SSH)
    IIS与asp.net3.5的问题
    SSI框架整合
  • 原文地址:https://www.cnblogs.com/zjoch/p/5999420.html
Copyright © 2011-2022 走看看