zoukankan      html  css  js  c++  java
  • 结构或者类中的string进行封送时长度缺失的原因及解决方案

    在数据通信或者调用C/C++的DLL时,会用到结构或类的封送(C#调用C++DLL传递结构体数组的终极解决方案),但是当结构或者类中用到string类型时,封送的数据会出现缺失。下面是以类的封送转换来举例。代码如下

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Runtime.InteropServices;
    using System.Text;
    
    namespace StringLayoutTest
    {
        class Program
        {
            static void Main(string[] args)
            {
                TestStructToBytes();    
            }
        
            private static void TestStructToBytes()
            {
                TestStruct testStruct = new TestStruct();
                testStruct.name = "ABC";
                Console.WriteLine("Input={0}",testStruct.name);
                int testStructLen = Marshal.SizeOf(typeof(TestStruct));
                Console.WriteLine("Struct Len={0}",testStructLen);
                byte[] testStructBytes = structToBytes(testStruct);
                Console.WriteLine("Data Len={0},Data={1}",
                                testStructBytes.Length,
                                Encoding.UTF8.GetString(testStructBytes));
                foreach (byte item in testStructBytes)
                {
                    Console.WriteLine("byte={0},char={1}", item, (char)item);
                }
                Console.ReadLine();
            }
    
            [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
            public sealed class TestStruct
            {
                [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 3)]
                public string name = "";
            }
          
            public static byte[] structToBytes(object obj)
            {
                int size = Marshal.SizeOf(obj);//Get size of struct or class.            
                byte[] bytes = new byte[size];
                IntPtr structPtr = Marshal.AllocHGlobal(size);//Allocate memory space of the struct or class.            
                Marshal.StructureToPtr(obj, structPtr, false);//Copy struct or class to the memory space.            
                Marshal.Copy(structPtr, bytes, 0, size);//Copy memory space to byte array.           
                Marshal.FreeHGlobal(structPtr);//Release memory space.           
                return bytes;
            }
        }
    }
    

    运行结果见图1

    图1


    我们输入的是ABC,但经过封送后却变成了AB。再看封送后展开的字节,会发现第一个字节是65(A),第二个字节是66(B),第三个字节为0(空),这里其实三个字节都已经封送了,只是最后一个字节变成了0,也就是结束符'',只是结束符输出时是空的。

    所以封送的字节数还是3个,只是因为最后一个字节会默认是结束符'',这样真正放数据的长度就相当于少了一位,也就是只有2位,自然数据也就只有前两个字节了。

    那如果从字节转换成结构或者类(相当于通过DLL调用得到了数据,然后要转换成所要的数据结构或者数据类),是不是也有同样的问题呢?为此,也作了测试,代码如下

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Runtime.InteropServices;
    using System.Text;
    
    namespace StringLayoutTest
    {
        class Program
        {
            static void Main(string[] args)
            {           
                TestBytesToStruct();          
            }
    
            private static void TestBytesToStruct()
            {
                byte[] testStructBytes = new byte[3];
                testStructBytes[0] = 65;//A
                testStructBytes[1] = 66;//B
                testStructBytes[2] = 67;//C
                Console.WriteLine("Input={0}", Encoding.UTF8.GetString(testStructBytes));
                TestStruct testStruct = (TestStruct)bytesToStruct(testStructBytes, typeof(TestStruct));
                Console.WriteLine("Len={0},Data={1}", testStruct.name.Length, testStruct.name);
                Console.ReadLine();
            } 
    
           
    
            [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
            public sealed class TestStruct
            {
                [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 3)]
                public string name = "";
            }
        
    
            public static object bytesToStruct(byte[] bytes, Type type, int startIndex = 0)
            {
    
                int size = Marshal.SizeOf(type);//Get size of the struct or class.          
                if (bytes.Length < size)
                {
                    return null;
                }
                IntPtr structPtr = Marshal.AllocHGlobal(size);//Allocate memory space of the struct or class. 
                Marshal.Copy(bytes, startIndex, structPtr, size);//Copy byte array to the memory space.
                object obj = Marshal.PtrToStructure(structPtr, type);//Convert memory space to destination struct or class.         
                Marshal.FreeHGlobal(structPtr);//Release memory space.    
                return obj;
            }
        }
    }
    

    运行结果见图2

    图2


    我们输入的是ABC(代码中已经将字节数组testStructBytes赋值为ABC),但转换成类之后,却只变成了AB,原因还是因为最后一个字节被处理成了结束符。

    那这个问题要如何解决呢?

    方案一:

    在结构或者中定义string的封送长度时多加1字节的长度(相当于在C/C++中定义char字符串时,需要多一个字节的结束位),然后进行封送。不过,这可能会引发另一 个问题。因为我实际字串长度就是3,而封送的时为了给结束符留1个字节就需要4字节的长度,这样一来,长度的控制上就有可能出问题。那有没有更好的方案呢?可以看方案二。

    方案二:

    不采用string来封送数据,而是使用byte数组。比如,上面的的问题,我们可以按下面的方式来解决。

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Runtime.InteropServices;
    using System.Text;
    
    namespace StringLayoutTest
    {
        class Program
        {
            static void Main(string[] args)
            {
                //TestStructToBytes();
                 //TestBytesToStruct();
               TestBytesToStruct2();
            }
    
            private static void TestBytesToStruct()
            {
                byte[] testStructBytes = new byte[3];
                testStructBytes[0] = 65;//A
                testStructBytes[1] = 66;//B
                testStructBytes[2] = 67;//C
                Console.WriteLine("Input={0}", Encoding.UTF8.GetString(testStructBytes));
                TestStruct testStruct = (TestStruct)bytesToStruct(testStructBytes, typeof(TestStruct));
                Console.WriteLine("Len={0},Data={1}", testStruct.name.Length, testStruct.name);
                Console.ReadLine();
            }
    
            private static void TestBytesToStruct2()
            {
                byte[] testStructBytes = new byte[3];
                testStructBytes[0] = 65;//A
                testStructBytes[1] = 66;//B
                testStructBytes[2] = 67;//C
                Console.WriteLine("Input={0}", Encoding.UTF8.GetString(testStructBytes));
                TestStruct2 testStruct = (TestStruct2)bytesToStruct(testStructBytes, typeof(TestStruct2));
                Console.WriteLine("Len={0},Data={1}", testStruct.name.Length, Encoding.UTF8.GetString(testStruct.name));
                Console.ReadLine();
            }
    
            private static void TestStructToBytes()
            {
                TestStruct testStruct = new TestStruct();
                testStruct.name = "ABC";
                Console.WriteLine("Input={0}",testStruct.name);
                int testStructLen = Marshal.SizeOf(typeof(TestStruct));
                Console.WriteLine("Struct Len={0}",testStructLen);
                byte[] testStructBytes = structToBytes(testStruct);
                Console.WriteLine("Data Len={0},Data={1}",
                                testStructBytes.Length,
                                Encoding.UTF8.GetString(testStructBytes));
                foreach (byte item in testStructBytes)
                {
                    Console.WriteLine("byte={0},char={1}", item, (char)item);
                }
                Console.ReadLine();
            }
    
            [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
            public sealed class TestStruct
            {
                [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 3)]
                public string name = "";
            }
    
            [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
            public sealed class TestStruct2
            {
                [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
                public byte[] name;
            }
    
            public static byte[] structToBytes(object obj)
            {
                int size = Marshal.SizeOf(obj);//Get size of struct or class.            
                byte[] bytes = new byte[size];
                IntPtr structPtr = Marshal.AllocHGlobal(size);//Allocate memory space of the struct or class.            
                Marshal.StructureToPtr(obj, structPtr, false);//Copy struct or class to the memory space.            
                Marshal.Copy(structPtr, bytes, 0, size);//Copy memory space to byte array.           
                Marshal.FreeHGlobal(structPtr);//Release memory space.           
                return bytes;
            }
    
            public static object bytesToStruct(byte[] bytes, Type type, int startIndex = 0)
            {
    
                int size = Marshal.SizeOf(type);//Get size of the struct or class.          
                if (bytes.Length < size)
                {
                    return null;
                }
                IntPtr structPtr = Marshal.AllocHGlobal(size);//Allocate memory space of the struct or class. 
                Marshal.Copy(bytes, startIndex, structPtr, size);//Copy byte array to the memory space.
                object obj = Marshal.PtrToStructure(structPtr, type);//Convert memory space to destination struct or class.         
                Marshal.FreeHGlobal(structPtr);//Release memory space.    
                return obj;
            }
        }
    }
    
    运行结果



    输入和转换后的结果都ABC,达到了预期。不过这里有一个问题,因为封送的是字节数组,所以必须要转换成字符串。而在转换成字符串时会有编码的问题,一般使用UTF-8是可以的,但也不排除一些其他的情况。

    结论:

    在.NET中对类或者结构进行封送时,要特别注意string的封送,具体要采用byte数组来替换还是增加1字节的结束符,要看具体应用而定。

  • 相关阅读:
    Android实战:手把手实现“捧腹网”APP(一)-----捧腹网网页分析、数据获取
    容器云平台使用体验:数人云Crane(续)
    [React Native]升级React Native版本
    [React Native]去掉WebStorm中黄色警告
    数据库--mysql介绍
    缓存数据库-redis(补充)
    缓存数据库-redis(订阅发布)
    缓存数据库-redis(管道)
    缓存数据库-redis数据类型和操作(sorted set)
    缓存数据库-redis数据类型和操作(set)
  • 原文地址:https://www.cnblogs.com/sparkleDai/p/7605064.html
Copyright © 2011-2022 走看看