什么是最简洁的字节级操作方法?

时间:2010-06-02 11:55:55

标签: c# marshalling bytearray byte

我从服务器的源代码中获得了以下C结构,并且有很多相似的结构:

// preprocessing magic: 1-byte alignment

typedef struct AUTH_LOGON_CHALLENGE_C
{
    // 4 byte header
    uint8   cmd;
    uint8   error;      
    uint16  size;       

    // 30 bytes
    uint8   gamename[4];
    uint8   version1;
    uint8   version2;
    uint8   version3;
    uint16  build;
    uint8   platform[4];
    uint8   os[4];
    uint8   country[4];
    uint32  timezone_bias;
    uint32  ip;
    uint8   I_len;

    // I_len bytes
    uint8   I[1];
} sAuthLogonChallenge_C;

// usage (the actual code that will read my packets): 
sAuthLogonChallenge_C *ch = (sAuthLogonChallenge_C*)&buf[0]; // where buf is a raw byte array

这些是TCP数据包,我需要实现一些在C#中发出和读取它们的东西。最干净的方法是什么?

我目前的方法涉及

[StructLayout(LayoutKind.Sequential, Pack = 1)]
unsafe struct foo { ... }

和许多fixed语句来读取和写入它,但它感觉非常笨重,而且由于数据包本身不是固定长度,我觉得使用它并不舒服。此外,它还有很多工作要做。

然而,它确实很好地描述了数据结构,并且协议可能会随着时间而改变,因此这可能是维护的理想选择。

我有什么选择?用C ++编写它并使用一些.NET魔法来使用它会更容易吗?

澄清:我还需要处理字节序问题和空填充字符串。

4 个答案:

答案 0 :(得分:6)

我会创建一个本机C#类来表示数据包及其数据(不是尝试匹配有线格式的数据),并在构造函数中传递BinaryReader。让它从数据流中以适当的块读取数据:

public class LogonChallenge
{
    public LogonChallenge(BinaryReader data)
    {
        // header
        this.Cmd = data.ReadByte();
        this.Error = data.ReadByte();
        this.Size = data.ReadUInt16();

        // etc
    }
}

如果您有多个共享公共标头或其他前导字段的数据包类型,则可以使用继承来避免重复。 BasePacket类可能会读取并填充标题字段,LogonChallenge类将继承BasePacket并在调用基础构造函数后开始读取质询字段。

答案 1 :(得分:1)

如果有很多不安全的代码,我可能会考虑用C ++编写代码。可能作为C ++ COM DLL,如果需要,可以很容易地从C#中调用,只需确保COM接口很容易与.Net类型匹配。虽然可能有一些更好的方法使用Managed C ++,我从未使用过。

答案 2 :(得分:1)

同意ho1,我会编写包含此结构的小型C ++ / CLI类。这个类可能需要一个接口,它可以从字节数组填充结构,并为每个结构成员填充属性。 C#客户端可以从从套接字接收的字节数组构造此类实例,并从中读取每个结构成员作为托管属性。所有durty工作都可以在非托管代码中完成。

答案 3 :(得分:0)

好的,这就是我想出的:

abstract class Packet
{
    protected enum T
    {
        Byte,
        UInt16,
        UInt32,
        NullPaddedAsciiString,
        Whatever
    }
    protected struct Offset
    {
        public int offset;
        public T type;                      // included only for readability
        public Offset(int i, T type)
        {
            this.type = type;
            offset = i;
        }
    }

    protected byte[] data;

    byte[] RawData { get { return data; } }

    // getters and setters will be implemented using something like this
    protected UInt16 GetUInt16(Offset o)
    {
        // magic
    }

    protected void Write(Offset o, string s)
    { 
        // magic
    }
}

class cAuthLogonChallenge : Packet
{
    // still not perfect, but at least communicates the intent
    static Offset cmd = new Offset(0, T.Byte);
    static Offset error = new Offset(1, T.Byte);
    static Offset size = new Offset(2, T.UInt16);
    // etc.

    public cAuthLogonChallenge(string username)
    {
        var size = 30 + username.Length
        data = new byte[size];
        Write(cmd, 0x00);
        // etc.
    }
}