结构或者类中的string进行封送时长度缺失的原因及解决方案

原创 2013年12月05日 09:45:17

在数据通信或者调用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,也就是结束符'\0',只是结束符输出时是空的。

所以封送的字节数还是3个,只是因为最后一个字节会默认是结束符'\0',这样真正放数据的长度就相当于少了一位,也就是只有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字节的结束符,要看具体应用而定。

版权声明:本文为博主原创文章,未经博主允许不得转载。

C#——Marshal.StructureToPtr方法简介

本博客(http://blog.csdn.net/livelylittlefish)贴出作者(三二一、小鱼)相关研究、学习内容所做的笔记,欢迎广大朋友指正!                      ...
  • livelylittlefish
  • livelylittlefish
  • 2008年05月09日 16:54
  • 29927

C# - Marshal.StructureToPtr方法简介

本博客(http://blog.csdn.net/livelylittlefish)贴出作者(三二一、小鱼)相关研究、学习内容所做的笔记,欢 迎广大朋友指正!               ...
  • ybhjx
  • ybhjx
  • 2016年04月06日 12:28
  • 1175

.NET Compact Framework 1.0 下实现抓屏

.NET Compact Framework 1.x中实现屏幕抓取有些难度,其实还是.net cf 1.x的支持库不够强大,微软在.net cf2.0中已经弥补了此处的不足。但是为什么还非要实现.ne...
  • wellwelcome
  • wellwelcome
  • 2006年11月01日 11:19
  • 1519

String类型数学表达式直接进行运算

今天遇到了一个需要将数据库中保存的表达式,替换其中的字符,并计算出值,java是不能直接计算的例如:  Java代码   double d = (3+5-(2-4)*2)/24...
  • xiaemperor
  • xiaemperor
  • 2014年03月03日 10:45
  • 3557

redis数据丢失及解决

Redis的数据回写机制 Redis的数据回写机制分同步和异步两种, 同步回写即SAVE命令,主进程直接向磁盘回写数据。在数据大的情况下会导致系统假死很长时间,所以一般不是推荐的。异步回写...
  • gzh0222
  • gzh0222
  • 2013年01月08日 18:27
  • 42615

iOS - Json解析数据精度丢失处理

原文 http://blog.sina.com.cn/s/blog_92aba1430102wakk.html 开发中处理处理数字、价格金额等问题时, 后台经常返回float类型, 打印或转成NSS...
  • qq_23292307
  • qq_23292307
  • 2016年10月09日 18:06
  • 1138

拓宽数值类型会造成精度丢失吗?

Java语言的8种基本数据类型中7种都可以看作是数值类型,我们知道对于数值类型的转换有一个规律:从窄范围转化成宽范围能够自动类型转换,反之则必须强制转换。请看下图: byte-->short-->in...
  • u014730159
  • u014730159
  • 2014年04月18日 13:32
  • 305

Java中不同数值类型间转换与计算精度丢失问题

在Java编程过程中,经常会涉及到不同数值类型之间的转换以及计算精度丢失的问题,例如:int m=6; float n=3.5f; double p=2.75d; System.out.pri...
  • gulingfengze
  • gulingfengze
  • 2017年01月05日 16:06
  • 3192

理解int转float为何会可能精度丢失的问题

在看Java核心技术卷I的时候,看到个基础类型转换,图片就不附上了,上面写到int转float有可能会精度丢失,看到此处的时候我一直在疑惑,明明float能够表示的数比int要大得多,怎么可能int转...
  • m1n_love
  • m1n_love
  • 2017年02月15日 22:53
  • 2601

java Date类型插入orcale数据库是出现时分秒丢失现象

做一些java项目是和数据库打交道是不可避免的,本人在一次将数据插入orcale数据库时Date类型字段插入时时分秒莫名其妙的就丢失了,在debug过程中发现在Date类型是时分秒是存在的,但是在这里...
  • summer_star
  • summer_star
  • 2016年01月07日 09:23
  • 1934
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:结构或者类中的string进行封送时长度缺失的原因及解决方案
举报原因:
原因补充:

(最多只允许输入30个字)