C# 结构到字节数组封送产生 19 个元素而不是 20 个

Posted

技术标签:

【中文标题】C# 结构到字节数组封送产生 19 个元素而不是 20 个【英文标题】:C# struct to byte array marshal resulting in 19 elements instead of 20 【发布时间】:2018-12-05 00:29:19 【问题描述】:

一个 UDP 服务器和客户端正在运行,并且在编译或运行时没有显示任何错误,除非给它超过 20 个字符。然后突然我在服务器端只返回了 19 个字符。

看起来字符串的SizeConst 具有实际维度-1

当我发送字符串时,它会一直工作,直到我得到一个超过 20 个字符的字符串。即使SizeConst = 20,它也只保留 19 个字符。 (当 3 时只有 2 个字符,等等。)

谁能解释一下为什么我突然丢失了一些数据?

/******************** STRUCT *****************************/
[StructLayout(LayoutKind.Sequential)]
public struct TEST

    public string Buffer; 
    public int     number;

    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 20)]
    public string  aString;

    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)]
    public byte[] innerTestArray;



/****************** CLIENT ********************************//
static void Main(string[] args)
    
        //Start ServerSide acknowledge
        byte[] resp = Encoding.ASCII.GetBytes("INIT");
        sendData(resp);

        while (true)
        
            TEST test = new TEST();
            Console.WriteLine("");
            Console.WriteLine("ReadLine : ");
            test.aString = Console.ReadLine();
            test.number = 10;
            test.innerTestArray = null;
            test.Buffer = null;
            byte[] arr = structToBytes(test);
            sendData(arr);
        
    

/**************** MARSHAL FUNCTIONS *************************/
static byte[] structToBytes(object str)

        byte[] arr = new byte[Marshal.SizeOf(str)];
        IntPtr pnt = Marshal.AllocHGlobal(Marshal.SizeOf(str));
        Marshal.StructureToPtr(str, pnt, false);
        Marshal.Copy(pnt, arr, 0, Marshal.SizeOf(str));
        Marshal.FreeHGlobal(pnt);
        return arr;


static TEST structFromBytes(byte[] arr)

        TEST str = new TEST();
        int size = Marshal.SizeOf(str);
        IntPtr ptr = Marshal.AllocHGlobal(size);
        Marshal.Copy(arr, 0, ptr, size);
        str = (TEST)Marshal.PtrToStructure(ptr, str.GetType());
        Marshal.FreeHGlobal(ptr);
        return str;



[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 20)]

在这个例子中,我为字符串分配了20 作为SizeConstant。但是当我收到字符串时,它有一个SizeOf(string) = 19

此外,最好为字符串和其他数组提供更灵活的方法,以便具有更大的灵活性。 IE。不要在结构中使用SizeConst。在这个例子中,我只有一些小数据,但后来我要发送的数据有很大差异。结构和不同数据类型的列表和数组。

任何提示、技巧、想法?

PS:https://github.com/ritskes/C-UDP-struct-to-byte-DEMO-with-server-and-client的演示代码

【问题讨论】:

那个仓库是空的... 就我个人而言,我尽量避免任何涉及Marshal 的事情——它几乎总是没有必要或有助于理解正在发生的事情; Test 这里是什么?我们能看到Test吗? 您没有显示任何发送/接收代码;如果接收到的有效负载与您认为正在发送的有效负载不完全匹配,则其他任何操作都不起作用 - 所以首先要检查的是发送的内容与接收的内容。最简单的显示方法是 Console.WriteLine(BitConverter.ToString(yourByteArray)) - 并比较十六进制转储 是的,尝试将两个存储库都上传到 github,但失败了。客户端已上传,可以找到。而且服务器现在似乎在另一个仓库中。 Github 对我来说是新的。它的行为与我预期的不同。但是现在有代码,但是在 2 个新的 repos 中。 进行编组的原因是,当 UDP 通过 byte[] 传递消息时,我可以将 STRUCT 直接发送到 byte[]。 【参考方案1】:

发生这种情况是因为the interop assumes that the string will be null-terminated, hence the marshalling is adding a null terminator。这意味着字符串最长可以是 19 个字符,允许使用空终止符。

(请注意,文档实际上并未说明 ByValTStr 将添加空终止符 - 但确实如此!)

以下可编译的控制台应用程序演示了该问题:

using System;
using System.Runtime.InteropServices;

namespace Demo

    [StructLayout(LayoutKind.Sequential)]
    public struct TEST
    
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 20)]
        public string aString;
    

    class Program
    
        static void Main()
        
            TEST test = new TEST();

            test.aString = "1234567890123456789012345";

            int    size = Marshal.SizeOf(test);
            byte[] arr  = new byte[size];

            IntPtr ptr = Marshal.AllocHGlobal(size);
            Marshal.StructureToPtr(test, ptr, true);
            Marshal.Copy(ptr, arr, 0, size);

            Console.WriteLine("Bytes: " + string.Join(", ", arr));

            test = Marshal.PtrToStructure<TEST>(ptr);
            Marshal.FreeHGlobal(ptr);

            Console.WriteLine(test.aString); // Prints "1234567890123456789" - only 19 characters.
        
    

解决方案可能是使用 char 数组:

using System;
using System.Runtime.InteropServices;

namespace Demo

    [StructLayout(LayoutKind.Sequential)]
    public struct TEST
    
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)]
        public char[] aString;
    

    class Program
    
        static void Main()
        
            TEST test = new TEST();

            test.aString = "1234567890123456789012345".ToCharArray();

            int    size = Marshal.SizeOf(test);
            byte[] arr  = new byte[size];

            IntPtr ptr = Marshal.AllocHGlobal(size);
            Marshal.StructureToPtr(test, ptr, true);
            Marshal.Copy(ptr, arr, 0, size);

            Console.WriteLine("Bytes: " + string.Join(", ", arr));

            test = Marshal.PtrToStructure<TEST>(ptr);
            Marshal.FreeHGlobal(ptr);

            Console.WriteLine(new string(test.aString)); // Prints "12345678901234567890" - all 20 characters.
        
    

【讨论】:

这是我的预期,所以这证实了我的测试结果。所以要么有 21 个 MAX(如果我想要 20 个),要么使用你的 CHAR 示例。非常感谢! 是否有可能注意到 char[] 有一个 MAX 分配,所以没有限制(不使用 SizeConst。(当然,我自己也会尝试一些不同的数据。因为这是只是看看UDP是否可以处理结构 @Ritskes 不知道你对最大分配的意思是什么......我认为该数组的大小必须与SizeConst 规范相同。如果您想从返回的 char 数组中创建一个较小的字符串,您可以在数组中搜索 NUL 值,然后您就会知道该字符串需要多长时间。 我的意思是:以灵活的方式将原始字符串的长度(由于结构中的严格分配而长于最大大小)在结构中被分配为完整长度。是否可以将结构的编组代码分配给结构内的 SizeOf 变量? (我的意思是 Size Const) 这是我在处理此问题时的另一个观察结果。在使用带有由 SizeConst 注释的任何字符串属性的结构时,我注意到您可以使用超过定义长度的字符串来初始化结构。但是当您转换为字节然后返回时,如果字符串超过长度(包括空终止符),则会根据 SizeConst 修剪该字符串。这使我在 char[] 阵营中的可读性和可维护性。或者,您可以按照说明设置 SizeConst = DesiredLength + 1,然后在构造函数中针对 DesiredLength 进行验证或修剪

以上是关于C# 结构到字节数组封送产生 19 个元素而不是 20 个的主要内容,如果未能解决你的问题,请参考以下文章

高难度问题,C#结构体的封送 的使用经验总结

封送包含 char* 成员的非托管结构

c#拷贝数组指针中的元素到变量中

来自特定索引的字节数组作为 c# 中的结构而无需复制

C# 记录麦克风输入并将其存储在字节数组中,而不是本地存储

在 C# 中尽可能快地将数组复制到结构数组