• C#中结构体与字节流互相转换 [StructLayout(LayoutKind.Sequential)]


    一、c#结构体

     
    1、定义与C++对应的C#结构体

     
    在c#中的结构体不能定义指针,不能定义字符数组,只能在里面定义字符数组的引用。 
    C++的消息结构体如下: 
    //消息格式 4+16+4+4= 28个字节 
    struct cs_message

        u32_t        cmd_type; 
        char username[16]; 
        u32_t        dstID; 
        u32_t        srcID; 
    };
     
    C#定义的结构体如下:
     
    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct my_message 

        public UInt32  cmd_type;

        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 16)]
        public string username;    

        public UInt32  dstID;

        public UInt32  srcID;

        public my_message(string s)
        {
            cmd_type = 0;
            username = s;
            dstID = 0;
            srcID = 0;
        } 
    }
     
    在C++的头文件定义中,使用了 #pragma pack 1 字节按1对齐,所以C#的结构体也必须要加上对应的特
    性,LayoutKind.Sequential属性让结构体在导出到非托管内存时按出现的顺序依次布局,而对于C++的
    char数组类型,C#中可以直接使用string来对应,当然了,也要加上封送的特性和长度限制。

    托管代码指的是必须依靠.NET框架解释运行的代码,非托管代码一般指的是传统的不需要借助.NET框架解释的代码。在.NET出现之前,如VB,C++,DELPHI编写的程序都是非托管代码。
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 16)]
    MarshalAs属性指示如何在托管代码和非托管代码之间封送数据。
    很多时候我们想直接在.NET中调用我们以前写好的非托管程序或组件,这样就会出现托管代码与非托管代码之间互相调用,数据交换的问题,而MarshalAs语法就是定义非托管数据类型与大小的。

    2、结构体与byte[]的互相转换
     
    定义一个类,里面有2个方法去实现互转:
     
    public class Converter 

        public Byte[] StructToBytes(Object structure) 
        {

            Int32 size = Marshal.SizeOf(structure); 
            Console.WriteLine(size); 
            IntPtr buffer = Marshal.AllocHGlobal(size); 
            try 
            { 
                Marshal.StructureToPtr(structure, buffer, false); 
                Byte[] bytes = new Byte[size]; 
                Marshal.Copy(buffer, bytes, 0, size); 
                return bytes; 
            } 
            finally 
            { 
                Marshal.FreeHGlobal(buffer); 
            } 
        }

        public Object BytesToStruct(Byte[] bytes, Type strcutType) 
        { 
            Int32 size = Marshal.SizeOf(strcutType); 
            IntPtr buffer = Marshal.AllocHGlobal(size); 
            try 
            { 
                Marshal.Copy(bytes, 0, buffer, size); 
                return Marshal.PtrToStructure(buffer, strcutType); 
            } 
            finally 
            { 
                Marshal.FreeHGlobal(buffer); 
            } 
        } 
    }
     
    3、测试结果:
     
    static void Main(string[] args) 

        //定义转换类的一个对象并初始化 
        Converter Convert = new Converter();

        //定义消息结构体 
        my_message m;

        //初始化消息结构体 
        m = new my_message("yanlina"); 
        m.cmd_type = 1633837924; 
        m.srcID = 1633837924; 
        m.dstID = 1633837924;

        //使用转换类的对象的StructToBytes方法把m结构体转换成Byte 
        Byte[] message = Convert.StructToBytes(m); 
        //使用转换类的对象的BytesToStruct方法把Byte转换成m结构体 
        my_message n = (my_message)Convert.BytesToStruct(message, m.GetType()); 
        //输出测试 
        Console.WriteLine(Encoding.ASCII.GetString(message)); 
        Console.WriteLine(n.username); 
    }
     
    结构体的size是28个字节和c++的结构体一样,同时可以将结构体和字节数组互转,方便UDP的发送和接收。

    c#补充:

    [StructLayout(LayoutKind.Sequential)]

    结构体是由若干成员组成的.布局有两种
    1.Sequential,顺序布局,比如
    struct S1
    {
    int a;
    int b;
    }
    那么默认情况下在内存里是先排a,再排b
    也就是如果能取到a的地址,和b的地址,则相差一个int类型的长度,4字节
    [StructLayout(LayoutKind.Sequential)]
    struct S1
    {
    int a;
    int b;
    }
    这样和上一个是一样的.因为默认的内存排列就是Sequential,也就是按成员的先后顺序排列.
    2.Explicit,精确布局
    需要用FieldOffset()设置每个成员的位置
    这样就可以实现类似c的公用体的功能
    [StructLayout(LayoutKind.Explicit)]
    struct S1
    {
    [FieldOffset(0)]
    int a;
    [FieldOffset(0)]
    int b;
    }
    这样a和b在内存中地址相同


    StructLayout特性支持三种附加字段:CharSet、Pack、Size。
         

    ·   CharSet定义在结构中的字符串成员在结构被传给DLL时的排列方式。可以是Unicode、Ansi或Auto。     
      默认为Auto,在WIN   NT/2000/XP中表示字符串按照Unicode字符串进行排列,在WIN   95/98/Me中则表示按照ANSI字符串进行排列。     
    ·   Pack定义了结构的封装大小。可以是1、2、4、8、16、32、64、128或特殊值0。特殊值0表示当前操作平台默认的压缩大小。     
     

      [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
        public struct LIST_OPEN
        {
            public int dwServerId;
            public int dwListId;
            public System.UInt16 wRecordSize;
            public System.UInt16 wDummy;
            public int dwFileSize;
            public int dwTotalRecs;
            public NS_PREFETCHLIST sPrefetch;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 24)]
            public string szSrcMach;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 24)]
            public string szSrcComp;
        }

    此例中用到MashalAs特性,它用于描述字段、方法或参数的封送处理格式。用它作为参数前缀并指定目标需要的数据类型。
    例如,以下代码将两个参数作为数据类型长指针封送给 Windows API 函数的字符串 (LPStr): 
    [MarshalAs(UnmanagedType.LPStr)] 
    String existingfile; 
    [MarshalAs(UnmanagedType.LPStr)] 
    String newfile; 
    注意结构作为参数时候,一般前面要加上ref修饰符,否则会出现错误:对象的引用没有指定对象的实例。
    [ DllImport( "kernel32", EntryPoint="GetVersionEx" )] 
    public static extern bool GetVersionEx2( ref OSVersionInfo2 osvi );

    JAVA&NET技术QQ群号:456257217有问题的可以在群里面提问。
  • 相关阅读:
    Java 对象头
    JVM类加载过程
    final,static,this,super关键字汇总
    进程与线程通信
    数据库三范式
    接口与抽象类区别
    TF1.x + RTX 3090 训练PWCNet 踩坑
    spring boot jpa 配置多数据源 报错 datasource循环依赖
    springboot 配置 jasypt加密,应用于配置文件数据库密码加密形式展现
    Redis基础入门:五大数据类型有哪些?
  • 原文地址:https://www.cnblogs.com/shiyh/p/14791254.html
Copyright © 2020-2023  润新知