Search code examples
cmetaprogrammingpreprocessorx-macros

Convert endianness of integer fields in struct using macros


Consider the following struct and functions

typedef struct __attribute__((__packed__)) req_file {
  uint32_t start_pos;
  uint32_t byte_count;
  uint16_t name_len;
} req_file;

void req_file_hton(req_file *d){
  d->name_len = htons(d->name_len);
  d->start_pos = htonl(d->start_pos);
  d->byte_count = htonl(d->byte_count);
}

void req_file_ntoh(req_file *d){
  d->name_len = ntohs(d->name_len);
  d->start_pos = ntohl(d->start_pos);
  d->byte_count = ntohl(d->byte_count);
}

The above code is tedious to write for a lot of structs with many fields. I would like to configure the name and the fields of the struct once, and have the functions struct_name_hton and struct_name_ntoh generated for me. I have tried to play with x macros a little but had bad luck. A portable C preprocessor solution will be highly appreciated (not C++).


Solution

  • Well, that's easy.

    #include <stdint.h>
    #include <arpa/inet.h>
    
    /* the NETSTRUCT library ------------------------------- */
    
    // for uint32_t
    #define NETSTRUCT_dec_uint32_t(n)  uint32_t n;
    #define NETSTRUCT_hton_uint32_t(n)  t->n = htonl(t->n);
    #define NETSTRUCT_ntoh_uint32_t(n)  t->n = ntohl(t->n);
    
    // for uint16_t
    #define NETSTRUCT_dec_uint16_t(n)  uint16_t n;
    #define NETSTRUCT_hton_uint16_t(n)  t->n = htons(t->n);
    #define NETSTRUCT_ntoh_uint16_t(n)  t->n = ntohs(t->n);
    
    // dec hton ntoh switch
    #define NETSTRUCT_dec(type, name)  NETSTRUCT_dec_##type(name)
    #define NETSTRUCT_hton(type, name) NETSTRUCT_hton_##type(name)
    #define NETSTRUCT_ntoh(type, name) NETSTRUCT_ntoh_##type(name)
    
    // calls NETSTRUCT_mod
    #define NETSTRUCT1(mod, a)       NETSTRUCT_##mod a
    #define NETSTRUCT2(mod, a, ...)  NETSTRUCT1(mod, a) NETSTRUCT1(mod, __VA_ARGS__)
    #define NETSTRUCT3(mod, a, ...)  NETSTRUCT1(mod, a) NETSTRUCT2(mod, __VA_ARGS__)
    #define NETSTRUCT4(mod, a, ...)  NETSTRUCT1(mod, a) NETSTRUCT3(mod, __VA_ARGS__)
    // TO DO: all up to NETSTRUCT64
    
    // variadic macro overload
    #define NETSTRUCT_GET(_1,_2,_3,_4,NAME,...) NAME
    // Overlads VA_ARGS with specified mod
    #define NETSTRUCT_IN(mod, ...) \
            NETSTRUCT_GET(__VA_ARGS__, NETSTRUCT4, NETSTRUCT3, NETSTRUCT2, NETSTRUCT1) \
                (mod, __VA_ARGS__)
    
    // entrypoint of out library
    #define NETSTRUCT(name, ...)  \
        \
        struct name { \
            NETSTRUCT_IN(dec, __VA_ARGS__) \
        } __attribute__((__packed__)); \
        \
        void name##_hton(struct name *t) { \
            NETSTRUCT_IN(hton, __VA_ARGS__) \
        } \
        \
        void name##_ntoh(struct name *t) { \
            NETSTRUCT_IN(ntoh, __VA_ARGS__) \
        }
    
    /* -------------------------------------------------------- */
    
    // adding custom type
    #define NETSTRUCT_dec_uint8_t_arr_8(n) uint8_t n[8];
    #define NETSTRUCT_hton_uint8_t_arr_8(n) do{}while(0);
    #define NETSTRUCT_ntoh_uint8_t_arr_8(n) do{}while(0);
    
    NETSTRUCT(reg_file, 
        (uint32_t, start_pos),
        (uint32_t, byte_count),
        (uint16_t, name_len),
        (uint8_t_arr_8, example_custom_array)
    );
    
    int main() {
        struct reg_file t;
        reg_file_hton(&t);
        reg_file_ntoh(&t);
    }
    

    I have written the mactos so it's easy to add another function, most probably void name##serialize(char *in) and void name##deserialize(const char *out). The design can be slightly refactored so that type callbacks NETSTRUCT_dec_* take two or even unknown number of arguments with ex. NETSTRUCT(name, (type_callback_suffix, (arguments, arguments2))).

    @edit added custom array type example and some lines order changing.