Какой самый чистый способ сделать манипуляции на уровне байтов? - PullRequest
1 голос
/ 02 июня 2010

У меня есть следующая структура 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 ]

6 голосов
/ 02 июня 2010

Я бы создал собственный класс 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 голос
/ 02 июня 2010

Согласен с ho1, я бы написал небольшой класс C ++ / CLI, который оборачивает эту структуру. Этот класс, возможно, нуждается в интерфейсе, который может заполнять структуру из байтового массива, и свойстве для каждого члена структуры. Клиент C # может создать этот экземпляр класса из байтового массива, полученного из сокета, и прочитать каждый элемент структуры из него как управляемое свойство. Вся нелепая работа может выполняться в неуправляемом коде.

1 голос
/ 02 июня 2010

Если существует много небезопасного кода, я бы, вероятно, вместо этого обратился к написанию его на C ++. Возможно, как C ++ COM DLL, которую затем можно легко вызвать из C # при необходимости, просто убедившись, что интерфейс COM легко сопоставим с типами .Net. Хотя, возможно, есть какой-то лучший способ использовать Managed C ++, который я никогда не использовал.

0 голосов
/ 02 июня 2010

Хорошо, вот что я придумала:

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.
    }
}
...