2016-06-27 40 views
6

Sto effettuando una chiamata ioctl da C++ a un driver che non possiedo/gestisco, e sto cercando di risolvere se c'è un meccanismo pulito, "sicuro" occuparsi di alcune delle brutte allocazioni strutturali richieste.Moderno modello C++ per la brutta allocazione di struct C

versione snellita di alcune strutture coinvolte

// IOCTL expects an instance of this structure "first" 
typedef struct { 
    int param1; 
    int param2; 
} s_ioctl_request; 

//... followed by an instance of this. If attr_length 
// is > sizeof(s_attr_header), more data is allowed to follow. 
typedef struct { 
    uint32_t attr_length; 
    uint32_t attr_type; 
} s_attr_header; 

// Example that uses more data than just the header. 
typedef struct { 
    s_attr_header hdr; 
    uint32_t attr_param; 
} s_attr_type1; 

// Another example. 
typedef struct { 
    s_attr_header hdr; 
    uint32_t attr_param1; 
    uint32_t attr_param2; 
} s_attr_type2; 

ioctl richiede che s_ioctl_request essere immediatamente seguito da un s_attr_header, o altra struttura che contiene esso, dove attr_length è impostato alla dimensione della struttura esterna in byte.

In C, di scrivere un wrapper per il ioctl sarebbe stato fatto attraverso qualcosa in queste righe:

int do_ugly_ioctl(int fd, int p1, int p2, s_attr_header * attr) 
{ 
    int res;  
    // Allocate enough memory for both structures. 
    s_ioctl_request *req = malloc(sizeof(*req) + attr->hdr.attr_length); 

    // Copy the 2nd, (variable length) structure after the first. 
    memcpy(((char*)req) + sizeof(*req), attr, attr->hdr.attr_length); 

    // Modify params as necessary 
    req->param1 = p1; 
    req->param2 = p2; 

    // Make the driver call, free mem, and return result. 
    res = ioctl(fd, SOME_IOCTL_ID, req); 
    free(req); 
    return res; 
} 

// Example invocation. 
s_attr_type1 a1; 
a1.hdr.attr_length = sizeof(a1); 
a1.hdr.attr_type = 1; 
do_ugly_ioctl(fd, 10, 20, &a1); 

A opzioni paio sto pensando di, sono:

  1. tiro C++ moderno: gli ismi fuori dalla finestra e fanno esattamente quello che ho mostrato sopra.

  2. destinare l'archiviazione con uno std :: vector, poi fare brutti calchi con lo std :: vector :: risultante dei dati() puntatore così almeno non sto facendo new[]/delete[] o malloc/free.

  3. Creare un metodo di wrapper univoco per ogni s_attr_type* che utilizza la propria struttura "speciale". Questo sembra "più sicuro", cioè meno probabile per l'utente del metodo wrapper di rovinarlo. E punti bonus, consente il pass-by-ref.

Metodo # 3 Esempio:

int do_ugly_ioctl(fd, int param1, int param2, s_attr_type2& attr){ 
    struct RequestData { 
     s_ioctl_request ioreq; 
     s_attr_type2 attr; 
    }; 
    RequestData r; 
    r.ioreq.param1 = param1; 
    r.ioreq.param2 = param2; 
    r.attr   = attr; 
    r.attr.hdr.attr_length = sizeof(attr); // Might as well enforce this here. 
    ioctl(fd, SOME_IOCTL_ID, (void*) &r); 
} 

Quindi credo che alcune domande qui sono:

  • È "vale la pena" di C++ - izzare una soluzione a questo problema? (al contrario di fare affidamento sul C impl più incline agli errori).

  • Se vado con il metodo n. 3 o simile, c'è qualcosa che posso fare con <type_traits> per creare un modello di questa funzione e accettare solo le strutture con un s_attr_header come primo membro?

  • Altre idee brillanti?

+0

Il wrapper C richiede 8 righe di codice. Il wrapper C++ richiede 10. Non riesco a vedere molti miglioramenti. –

+0

L'opzione 2 nasconde semplicemente le allocazioni all'interno di std :: vector, quindi non c'è alcun miglioramento. In effetti, l'opzione 2 è una soluzione terribile. –

+0

@CareyGregory Non direi che è nascosto perché ogni programmatore C++ sa cosa fa un vettore. Lo chiamerebbe incapsulato. – Jens

risposta

3

Ne vale la pena, e la soluzione è molto bella. Si potrebbe voler dichiarare le strutture come packed (ci sono estensioni del compilatore per ottenere ciò) per evitare di avere un riempimento extra quando si combinano più strutture.

È inoltre possibile impostare la dimensione della struttura all'interno del costruttore.

struct RequestData 
{ 
     RequestData() : ioreq{}, attr{} 
     { 
     attr.hdr.attr_length = sizeof(attr); 
     } 
     s_ioctl_request ioreq; 
     s_attr_type2 attr; 
}; 

riguardante la seconda domanda, si potrebbe dividere l'assegnazione in due, non è troppo bello, ma è facile e se si passa qualcosa senza intestazione corretta, porterà ad un errore di compilazione:

template<typename Attr> 
int do_ugly_ioctl(fd, int param1, int param2, Attr& attr){ 
    struct RequestData { 
     s_ioctl_request ioreq; 
     Attr attr; 
    }; 
    RequestData r; 
    r.ioreq.param1 = param1; 
    r.ioreq.param2 = param2; 
    s_attr_header hdr = Attr.hdr; //this will lead to compilation error if the type is not what we expect 
    (void) hdr; 
    r.attr  = attr; 
    r.attr.hdr.attr_length = sizeof(attr); // Might as well enforce this here. 
    ioctl(fd, SOME_IOCTL_ID, (void*) &r); 
} 
+0

Il fatto che l'accesso al membro dell'intestazione causi un errore quando non c'è il mio "collegamento mancante" mentale nel mio metodo # 3. –