2010-06-02 20 views
1

J'ai la struct C suivant du code source d'un serveur, et beaucoup similaires:Quelle est la manière la plus propre de faire des manipulations au niveau des octets?

// 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 

Ce sont des paquets TCP, et je dois mettre en œuvre quelque chose qui émet et les lit en C#. Quelle est la manière la plus propre de faire ceci?

Mon approche actuelle implique

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

et beaucoup de déclarations fixed à lire et à écrire, mais il se sent vraiment maladroit, et puisque la longueur paquet lui-même est pas fixe, je ne me sens pas à l'aise avec il. Aussi, c'est beaucoup de travail. Cependant, il décrit bien la structure de données, et le protocole peut changer au fil du temps, ce qui peut être idéal pour la maintenance.

Quelles sont mes options? Serait-il plus facile de simplement l'écrire en C++ et d'utiliser un peu de magie .NET pour l'utiliser?

Clarification: J'ai aussi besoin de gérer les problèmes d'endian et les chaînes de caractères null-padded.

Répondre

6

Je créerais une classe C# native pour représenter le paquet et ses données (pas une qui essaye de correspondre au format de fil, nécessairement), et lui passerais un BinaryReader dans le constructeur. Faites-le lire ses données en morceaux appropriés à partir du flux de données:

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

     // etc 
    } 
} 

Si vous avez plusieurs types de paquets qui partagent un en-tête commun ou d'autres domaines de premier plan, vous pouvez utiliser l'héritage pour éviter les répétitions. La classe BasePacket peut lire et remplir les champs d'en-tête et la classe LogonChallenge héritera de BasePacket et commencera à lire les champs de défi après avoir appelé le constructeur de base.

1

S'il y a beaucoup de code dangereux, je chercherais probablement à l'écrire en C++ à la place. Peut-être en tant que DLL COM C++ qui peut ensuite être appelée assez facilement à partir de C# si nécessaire, juste en s'assurant que l'interface COM est facile à faire correspondre aux types .Net. Bien qu'il existe peut-être un meilleur moyen d'utiliser Managed C++, que je n'ai jamais utilisé.

1

D'accord avec ho1, j'écrirais une petite classe C++/CLI qui enveloppe cette structure. Cette classe a peut-être besoin d'une interface, qui peut remplir la structure à partir du tableau d'octets, et de la propriété pour chaque membre de la structure. Le client C# peut construire cette instance de classe à partir d'un tableau d'octets reçu d'un socket, et en lire chaque membre de structure en tant que propriété gérée. Tout le travail de durty peut être fait dans le code non managé.

0

Ok, voici ce que je suis venu avec:

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