简体   繁体   English

使用C#,如何将二进制数据的字节数组转换为对数据建模的自定义类型对象?

[英]With C#, how can I convert a byte array of binary data into a custom-typed object that models the data?

Scenario: I have received raw binary data via HTTP and stored the data into a byte array. 场景:我通过HTTP接收了原始二进制数据,并将数据存储到字节数组中。 I have documentation that describes various fields that the binary data can represent, but the actual meaning of the data must be determined at run-time. 我有描述二进制数据可以表示的各种字段的文档,但必须在运行时确定数据的实际含义。 For example, if the byte that represents the occurrence of an error = 1, then the meaning of the next byte changes. 例如,如果表示错误发生的字节= 1,则下一个字节的含义会发生变化。

Using C# with .NET 4, I want to create one or more classes that mirror the fields described in the documentation and then somehow initialize the classes using the byte array of binary data. 使用C#和.NET 4,我想创建一个或多个镜像文档中描述的字段的类,然后以某种方式使用二进制数据的字节数组初始化类。 I would like the solution to minimize code duplication and also to be modular and elegant. 我希望该解决方案能够最大限度地减少代码重复,并且模块化和优雅。

I have looked into creating Serializable classes, but I don't see how that can work since I am starting with a byte array that was not created (and therefore, not serialized) by me. 我已经研究过创建Serializable类,但是我没有看到它是如何工作的,因为我开始使用的是一个未创建(因此,没有序列化)的字节数组。

I have also attempted to use generics and reflection in order to retrieve the sizes and types of the fields contained in custom classes. 我还尝试使用泛型和反射来检索自定义类中包含的字段的大小和类型。 I then used that information to dynamically slice out data from the byte array and assign it to the corresponding fields. 然后,我使用该信息动态地从字节数组中切出数据并将其分配给相应的字段。 However, this method resulted in a lot of ugly, unmanageable code. 但是,这种方法导致了许多丑陋,无法管理的代码。

Any advice or pointers on designing an extensible, decoupled solution for this problem would be much appreciated. 任何有关为此问题设计可扩展的解耦解决方案的建议或指示都将非常受欢迎。

Edit: Example of classes containing fields that mirror the fields in the specification 编辑:包含镜像规范中字段的字段的类的示例

public class PriceHistoryResponse : BinaryResponse
{
    public List<Quote> quotes { get; set; }
    private CountData countData { get; set; }
    private EndingDelimiterSection endingDelimiterSection { get; set; }

    /* This code performs the logic needed to check for optional fields
    and to find the number of times that certain fields are repeated */
    public PriceHistoryResponse(byte[] responseBytes) : base(responseBytes)
    {
        countData = new CountData();
        ParseResponseSection(countData);

        quotes = new List<Quote>();
        for (int i = 0; i < countData.quoteCount; i++)
        {
            quotes.Add(new Quote());

            quotes[i].symbolData = new SymbolData();
            ParseResponseSection(quotes[i].symbolData);

            if (quotes[i].symbolData.errorCode == 1)
            {
                quotes[i].errorData = new ErrorData();
                ParseResponseSection(quotes[i].errorData);
                break;
            }

            quotes[i].chartBarData = new ChartBarData();
            ParseResponseSection(quotes[i].chartBarData);

            quotes[i].chartBars = new List<ChartBar>();
            for (int j = 0; j < quotes[i].chartBarData.chartBarCount; j++)
            {
                quotes[i].chartBars.Add(new ChartBar());
                ParseResponseSection(quotes[i].chartBars[j]);
            }
        }

        endingDelimiterSection = new EndingDelimiterSection();
        ParseResponseSection(endingDelimiterSection);
    }
}

class CountData : IResponseSection
{
    public int quoteCount { get; set; }
}

public class Quote
{
    public SymbolData symbolData { get; set; }
    public ErrorData errorData { get; set; }
    public ChartBarData chartBarData { get; set; }
    public List<ChartBar> chartBars { get; set; }
}

public class SymbolData : IResponseSection
{
   public string symbol { get; set; }
   public byte errorCode { get; set; }
}

public class ErrorData : IResponseSection
{
    public string errorText { get; set; }
}

public class ChartBarData : IResponseSection
{
    public int chartBarCount { get; set; }
}

public class ChartBar : IResponseSection
{
    public float close { get; set; }
    public float high { get; set; }
    public float low { get; set; }
    public float open { get; set; }
    public float volume { get; set; }
    public long timestamp { get; set; }
}

You can try to do something like this: 您可以尝试执行以下操作:

public object ByteArrayToObject(byte[] byteArray)
{
    try
    {
        // convert byte array to memory stream
        System.IO.MemoryStream memoryStream = new System.IO.MemoryStream(byteArray);

        // create new BinaryFormatter
        System.Runtime.Serialization.Formatters.Binary.BinaryFormatter binaryFormatter
                        = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();

        // set memory stream position to starting point
        memoryStream.Position = 0;

        // Deserializes a stream into an object graph and return as a object.
        return binaryFormatter.Deserialize(memoryStream);
    }
    catch (Exception ex)
    {
        Console.WriteLine("Exception caught in process: {0}", ex.ToString());
    }

    return null;
}

EDIT 编辑

Take a look at protobuf-net. 看看protobuf-net。 I think this is what you need: 我想这就是你需要的:

http://code.google.com/p/protobuf-net/ http://code.google.com/p/protobuf-net/

protected bool EvaluateBuffer(byte[] buffer, int length)
{
    if (length < 8)
    {
        return false;
    }

    MessageType messageType = (MessageType)BitConverter.ToInt32(buffer, 0);
    int size = BitConverter.ToInt32(buffer, 4);
    if (length < size + 8)
    {
        return false;
    }

    using (MemoryStream memoryStream = new MemoryStream(buffer))
    {
        memoryStream.Seek(8, SeekOrigin.Begin);
        if (messageType == MessageType.MyMessage)
        {
            MyMessage message = 
                ProtoBuf.Serializer.Deserialize<MyMessage>(memoryStream);
        }
    }
}

There's a lot of project that will let you (de)serialize classes, but if the format is set and it's not to some standard like protocol buffers then I guess they won't help you much. 有很多项目可以让你(de)序列化类,但是如果设置了格式并且它不像协议缓冲区这样的标准,那么我想它们对你没什么帮助。 Are you sure it's not some standard format? 你确定它不是一些标准格式吗?

If you need to read a byte to find out what the next bytes mean, my guess is you need to read them byte by byte using a BinaryReader and then pass the remainder of the byte array to a different function based on the result. 如果你需要读取一个字节来找出下一个字节的意思,我猜你需要使用BinaryReader逐字节读取它们,然后根据结果将字节数组的其余部分传递给另一个函数。

If you want to avoid having to handcode these 'parsers' then you could make a little DSL or something that would generate the dataclasses and accompanying code to read one from the byte array. 如果你想避免手工编码这些'解析器'那么你可以制作一个小的DSL或者生成数据类和附带代码的东西来从字节数组中读取一个。 But this sounds like doing a lot of work to save a little. 但这听起来像做了很多工作来节省一点。

How many different classes do you need to be able to read from these byte arrays? 您需要从这些字节数组中读取多少个不同的类? My first guess would be to just write the code by hand. 我的第一个猜测就是手工编写代码。 You'd need to have a lot of different classes or complicated logic to recuperate your development efforts. 您需要有许多不同的类或复杂的逻辑来恢复您的开发工作。

Some more random ideas: look at the sourcecode of protocol buffer implementations, maybe you can pick ideas from those. 一些更随意的想法:查看协议缓冲区实现的源代码,也许你可以从中选择想法。 http://code.google.com/p/protobuf-net/ or http://code.google.com/p/protobuf-csharp-port/ http://code.google.com/p/protobuf-net/http://code.google.com/p/protobuf-csharp-port/

Rgds Gert-Jan Rgds Gert-Jan

I pasted your code into VS, clicked 'Generate Method Stub' a few times and moved it around some. 我将你的代码粘贴到VS中,单击“生成方法存根”几次并将其移动到某些位置。 I guess this would do the trick. 我想这可以解决问题。

The code you provide is pretty clever, it's a bit like a visitor pattern where overloading switches to the right method. 您提供的代码非常聪明,它有点像访问者模式,其中重载切换到正确的方法。

 public class BinaryResponse {

        private BinaryReader _rdr;
        public BinaryResponse(byte[] responseBytes) {
            _rdr = new BinaryReader(new MemoryStream(responseBytes)); // wrap the byte[] in a BinaryReader to be able to pop the bytes off the top
        }

        protected void ParseResponseSection(CountData countData) {
            countData.quoteCount = _rdr.ReadInt16(); // guessing 64.000 quotes should be enough in one response, the documentation will have the type      
        }

        protected void ParseResponseSection(SymbolData symbolData) {
            symbolData.errorCode = _rdr.ReadByte(); // depending on your format, where is the ErrorCOde in the byte[]? the symbol might be first

            int symbolLength = _rdr.ReadInt16(); // if it's not written by a .Net WriteString on the other end better to read this count yourelf
            symbolData.symbol = new string(_rdr.ReadChars(symbolLength)); // read the chars and put into string
        }

        protected void ParseResponseSection(ErrorData errorData) {
            int errorLenth = _rdr.ReadInt16();
            errorData.errorText = new string(_rdr.ReadChars(errorLenth));
        }

        protected void ParseResponseSection(ChartBarData chartBarData) {
            chartBarData.chartBarCount = _rdr.ReadInt16();
        }

        protected void ParseResponseSection(ChartBar chartBar) {
            // check the order with the documentation, also maybe some casting is needed because other types are in the byte[]
            chartBar.close = _rdr.ReadSingle();
            chartBar.high = _rdr.ReadSingle();
            chartBar.low = _rdr.ReadSingle();
            chartBar.open = _rdr.ReadSingle();
            chartBar.timestamp = _rdr.ReadInt64();
        }

        protected void ParseResponseSection(EndingDelimiterSection endingDelimiterSection) {
            int checkValue = _rdr.ReadInt16();
            if (checkValue != 12345) throw new InvalidDataException("Corrupt Response! Expecting End Delimiter"); // assert that the end delimiter is some value
        }
    }

Is this what you're looking for? 这是你在找什么? You didn't say anything about encoding, you may need to take that into account when reading bytes etc. 你没有说任何关于编码的信息,你可能需要在读取字节时考虑到这一点。

Regards Gert-Jan 关心Gert-Jan

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM