QDataStream e strutture

QDataStream e strutture

Messaggioda Marco Trapanese » 23 mag , 2012 1:08 pm

Ciao,

come da manuale utilizzo QDataStream per serializzare delle mie strutture eseguendo l'overload degli operatori >> / << e gestendo a mano ciascun elemento della struttura.
Il tutto ovviamente funziona ma è scomodo perché costringe a creare un overload per ogni struttura da serializzare, con conseguente difficoltosa manutenzione in caso di modifiche.

Dal momento che gli elementi "foglia" di ciascuna struttura sono direttamente digeribili da QDataStream, mi chiedo se c'è un modo furbo per processare automaticamente le strutture (anche annidate). In pratica le difficoltà sono quelle di esplorare la struttura e individuare i tipi di dato.

Che io sappia non si può fare in C++ perché a run-time non si è a conoscenza del tipo di dato presente in memoria. Una soluzione ugualmente poco pratica (ma più facilmente manutenibile) è associare a una struttura la sua descrizione in termini di tipo di dato (enum) e offset rispetto alla prima locazione. La funzione di serializzazione seguendo l'elenco di questa descrizione è quindi in grado di puntare correttamente a ciascun elemento.

E' tempo perso o si può tirar fuori qualcosa di decente?
L'idea finale è di scrivere del codice leggibile, tipo questo:

Codice: Seleziona tutto
struct myStruct1 {
    int a;
    QString b;
    float[3] c;
}

struct myStruct2 {
    bool a;
    int[5] b;
    QString c;
}

myStruct1 s1;
myStruct2 s2;
QByteArray msg;
QDataStream stream(&msg, QIODevice::WriteOnly);
stream << s1 << s2;


Ciao!
Marco Trapanese
Troll competente
 
Messaggi: 141
Iscritto il: 19 giu , 2010 10:31 am
Programmo in: C,C++,.NET,Python,QT

Re: QDataStream e strutture

Messaggioda MauroTec » 24 mag , 2012 12:18 am

Dal momento che gli elementi "foglia" di ciascuna struttura sono direttamente digeribili da QDataStream, mi chiedo se c'è un modo furbo per processare automaticamente le strutture (anche annidate). In pratica le difficoltà sono quelle di esplorare la struttura e individuare i tipi di dato.


Ammetto che non ho ben chiaro l'uso che fai di datastream, penso che per come serializzi poi vuoi anche deserializzare o no?

Forse potresti usare QVariant() come tipo di dato generico per ogni elemento della struttura, ma questo mi sembra uno spreco, ma forse neanche così ottieni quello che cerchi.

Comunque potresti provare a tirare in ballo i template e lasciare al compilatore il compito di creare una funzione template per il tipo mancante.

Che io sappia non si può fare in C++ perché a run-time non si è a conoscenza del tipo di dato presente in memoria.

Dipende, ad esempio con type(obj) ti ritorna il tipo, oppure prova a stampare un oggetto con qdebug e vedi che visualizza il tipo e l'indirizzo.

Mi puoi far vedere un pezzo dei metodi sovraccaricati?

Ciao.
MauroTec
Trollino in fasce
 
Messaggi: 21
Iscritto il: 11 feb , 2011 3:01 pm
Località: Palermo
Programmo in: C/C++ python

Re: QDataStream e strutture

Messaggioda Marco Trapanese » 24 mag , 2012 8:06 am

MauroTec ha scritto:Ammetto che non ho ben chiaro l'uso che fai di datastream, penso che per come serializzi poi vuoi anche deserializzare o no?


Certamente. Mi serve per inviare su un socket (zmq) dei dati. Ma sarebbe la stessa cosa se volessi salvare su file.

Forse potresti usare QVariant() come tipo di dato generico per ogni elemento della struttura, ma questo mi sembra uno spreco, ma forse neanche così ottieni quello che cerchi.


Non credo che funzionerebbe. Per quanto i tipi di dato supportati dalla serializzazione in Qt4 comprendono anche il QVariant (vedi http://doc.qt.nokia.com/4.7-snapshot/datastreamformat.html) si riferiscono letteralmente al tipo di dato nudo, non purtroppo a una combinazione di essi.


Comunque potresti provare a tirare in ballo i template e lasciare al compilatore il compito di creare una funzione template per il tipo mancante.


Provo a cercare con google perché non ho idea di come si faccia.


Dipende, ad esempio con type(obj) ti ritorna il tipo, oppure prova a stampare un oggetto con qdebug e vedi che visualizza il tipo e l'indirizzo.



type a quale classe/namespace appartiene?
qDebug() mi stampa il tipo se si tratta di tipi Qt. Ma se sono int, float o long non so se si possono distinguere.

Mi puoi far vedere un pezzo dei metodi sovraccaricati?


Certo, eccone uno molto semplice:

Codice: Seleziona tutto
QDataStream &operator <<(QDataStream &out, const tower_t &tower) {
    out << tower.config.enabled;
    out << tower.config.invertEnabled;
    out << tower.config.showMin;
    out << tower.config.showMax;
    out << tower.config.hideMin;
    out << tower.config.hideMax;
    out << tower.config.invertMin;
    out << tower.config.invertMax;
    out << tower.config.points[0];
    out << tower.config.points[1];
    out << tower.config.points[2];
    return out;
}

QDataStream &operator >>(QDataStream &in, tower_t &tower) {
    in >> tower.config.enabled;
    in >> tower.config.invertEnabled;
    in >> tower.config.showMin;
    in >> tower.config.showMax;
    in >> tower.config.hideMin;
    in >> tower.config.hideMax;
    in >> tower.config.invertMin;
    in >> tower.config.invertMax;
    in >> tower.config.points[0];
    in >> tower.config.points[1];
    in >> tower.config.points[2];
    return in;
}


In realtà avrò molte strutture anche più complesse. E per ognuna dovrò sovraccaricare gli operatori.
Come fate voi a inviare dati su socket o su file?
Marco Trapanese
Troll competente
 
Messaggi: 141
Iscritto il: 19 giu , 2010 10:31 am
Programmo in: C,C++,.NET,Python,QT

Re: QDataStream e strutture

Messaggioda MauroTec » 25 mag , 2012 6:35 pm

Ho capito, credo. Lo stesso problema c'è l'ho io e non credo che ci sia soluzione.
Io scrivo su un dispositivo usb, con precisione scrivo su un endpoint che accetta solo scrittura, il dispositivo usb mi risponde nell'altro endpoint a cui posso accedere solo in lettura, i dati sono serializzati dal sottosistema usb e in origine i dati sono grandi 13 byte i primi tre byte devo metterli in variabili separate e l'altro di 10 byte è una stringa no zero terminated.

Se c'è analogia con il tuo problema posso dirti che non c'è soluzione per ricostruire il tipo dalla serializzazione. Però si risolve usando un tipo standard che funge da contenitore come appunto QVariant, ma non riesco a pensare ad un modo diverso di una struttura dove ogni campo è un QVariant e quindi dovrai sempre ricavare il valore del dato tramite i metodi di Qvariant. Se hai molto traffico di questo tipo potresti pensare di usare come dici tu un descrittore di tipo e magari usare QScript per avere variabili dinamiche ma non credo che ne valga la pena.

ps. sempre che sia possibile serializzare un tipo come QVariant, cioè il tipo stesso e non il valore.

Ciao.
MauroTec
Trollino in fasce
 
Messaggi: 21
Iscritto il: 11 feb , 2011 3:01 pm
Località: Palermo
Programmo in: C/C++ python

Re: QDataStream e strutture

Messaggioda Marco Trapanese » 25 mag , 2012 11:21 pm

MauroTec ha scritto:Se c'è analogia con il tuo problema posso dirti che non c'è soluzione per ricostruire il tipo dalla serializzazione.


Non pretendo tanto! Anche perché sarebbe quasi inutile.
Mi basterebbe che la serializzazione fosse "autonoma" per qualsiasi tipo (leggi: struttura).
Chiaramente dall'altro lato so già cosa mi aspetto, e darò quindi alla funzione di deserializzazione il tipo giusto.

Tutto questo "casino" solo per evitare di scrivere una funzione di (de)serializzazione per ciascun tipo di dato da trasmettere.
.NET lo fa tranquillamente, probabilmente inserisce qualche overhead in memoria per cui è in grado di conoscere l'esatta struttura di ogni...struttura.
Marco Trapanese
Troll competente
 
Messaggi: 141
Iscritto il: 19 giu , 2010 10:31 am
Programmo in: C,C++,.NET,Python,QT

Re: QDataStream e strutture

Messaggioda MauroTec » 26 mag , 2012 11:03 am

Occhio che quanto dirò potrà farti strabbuzzare gli occhi in quanto non ho la soluzione in tasca.

Al di la di QDataStreeam, se fornisci la struttura con i campi di dimensione corretta per ospitare la deserializzazione, si potrebbe risalire al tipo alla dimensione del tipo con sizeof(), ma come si fà a scanzionare tutti i campi di una struttura? Non lo so.
Una struttura si può serializzare, quello che ci vorrebbe è una struttura ospite ed una array di puntatori ad ogni byte della struttura ospite, poi si scrive nell'array di puntatori il valore ricavato dal dato serializzato ed il risultato è la compilazione della struttura senza fare assegnazione di ogni dato a membro di struttura.

Comunque, qui sotto c'è un pezzo di openusb, che fa la deserializzazione, ma questo vuole una costante letterale di formato tipo "2b4dw", che descrive la sequenza della struttura.

Codice: Seleziona tutto
*
* Unpack arbitrary little endian data (ie. descriptors)

*  openusb_parse_data()
*
*   Arguments:
*   format          - String indicating the format in b, w, d, eg. "2b4dw"
*                        which describes 2 bytes, 4 dwords, one word.
*                        A byte (b) is 8-bits, word (w) is 16-bits,
*                        dword (w) is 32-bits. The character '.' skips one
*                        byte in the source. The number prefix indicates
*                        the number of items of the subsequent type.
*   data            - Pointer to the LE data buffer
*   datalen         - Length of the data
*   structure       - Pointer to return structure where the unpacked data
*                        will be written
*   structlen       - Length of the return structure
*   count           - Number of bytes parsed
*
*   Return Values:
*   OPENUSB_SUCCESS
*   OPENUSB_PARSE_ERROR      - Data could not be parsed successfully
*
*   Notes:
*   for example to parse a descriptor such as:
*   struct {
*              uint8_t         a;
*              uint16_t        b;
*              uint8_t         c;
*              uint32_t        d;
*   };
*
* the application would call:
*
*   rv = openusb_parse_data("bwbd", buffer, sizeof (buffer),
*              (void *)my_descr, sizeof (my_descr), &count);
*
* this would result in inserting some padding to align structure
* members on natural boundaries (this is necessary on some processors such
* as SPARC). If you would dump memory of this structure on SPARC, you would
* see this:
*              uint8_t         a;
*              uint8_t         unused;
*              uint16_t        b;
*              uint8_t         c;
*              uint8_t         unused[3];
*              uint32_t        d;
*/
int32_t openusb_parse_data(const char *format, uint8_t *data, uint32_t datalen,
   void *structure, uint32_t structlen, uint32_t *count);


/* FIXME: Should we return OPENUSB_NO_RESOURCES on buffer overflow? */
int openusb_parse_data(const char *format, uint8_t *source, uint32_t sourcelen,
   void *dest, uint32_t destlen, uint32_t *count)
{
   unsigned char *sp = source, *dp = dest;
   uint16_t w;
   uint32_t d;
   const char *cp;

   if (!format || !source || !dest || !count) {
      return OPENUSB_BADARG;
   }
   for (cp = format; *cp; cp++) {
      switch (*cp) {
      case '.':   /* Skip 8-bit byte */
         sp++; sourcelen--;
         break;
      case 'b':   /* 8-bit byte */
         if (sourcelen < 1 || destlen < 1)
            return OPENUSB_NO_RESOURCES;

         *dp++ = *sp++;
         destlen--; sourcelen--;
         break;
      case 'w': /* 16-bit word, convert from little endian to CPU */
         if (sourcelen < 2 || destlen < 2)
            return OPENUSB_NO_RESOURCES;

         w = (sp[1] << 8) | sp[0]; sp += 2;

         /* Align to word boundary */
         dp += ((unsigned long)dp & 1);
         *((uint16_t *)dp) = w; dp += 2;
         destlen -= 2; sourcelen -= 2;
         break;
      case 'd': /* 32-bit dword, convert from little endian to CPU */
         if (sourcelen < 4 || destlen < 4)
            return OPENUSB_NO_RESOURCES;

         d = (sp[3] << 24) | (sp[2] << 16) |
            (sp[1] << 8) | sp[0]; sp += 4;

         /* Align to dword boundary */
         dp += ((unsigned long)dp & 2);
         *((uint32_t *)dp) = d; dp += 4;
         destlen -= 4; sourcelen -= 4;
         break;
         /*
          * These two characters are undocumented and just a
          * hack for Linux
          */
      case 'W':   /* 16-bit word, keep CPU endianess */
         if (sourcelen < 2 || destlen < 2)
         return OPENUSB_NO_RESOURCES;

         /* Align to word boundary */
         dp += ((unsigned long)dp & 1);
         memcpy(dp, sp, 2); sp += 2; dp += 2;
         destlen -= 2; sourcelen -= 2;
         break;
      case 'D':  /* 32-bit dword, keep CPU endianess */
         if (sourcelen < 4 || destlen < 4)
            return OPENUSB_NO_RESOURCES;

         /* Align to dword boundary */
         dp += ((unsigned long)dp & 2);
         memcpy(dp, sp, 4); sp += 4; dp += 4;
         destlen -= 4; sourcelen -= 4;
         break;
      }
   }

   *count = sp - source;
   return OPENUSB_SUCCESS;
}


Fammi sapere se trovi una possibile soluzione, che la cosa come vedi interessa anche me.

Ciao.
MauroTec
Trollino in fasce
 
Messaggi: 21
Iscritto il: 11 feb , 2011 3:01 pm
Località: Palermo
Programmo in: C/C++ python

Re: QDataStream e strutture

Messaggioda Marco Trapanese » 26 mag , 2012 12:26 pm

Penso di aver intuito quello che intendi. Ma se non sbaglio è proprio quello che avevo accennato nel primo post. Cioè una struttura parallela che contiene la descrizione (secondo determinate convenzioni) di come sono organizzati i dati nella struttura target.

Abbiamo solo spostato il problema. Ci ritroviamo comunque a dover definire per ogni struttura il suo descrittore... allora tanto vale fare l'overloading degli operatori :-(
Marco Trapanese
Troll competente
 
Messaggi: 141
Iscritto il: 19 giu , 2010 10:31 am
Programmo in: C,C++,.NET,Python,QT

Re: QDataStream e strutture

Messaggioda MauroTec » 28 mag , 2012 12:43 pm

.Net riesce a fare queste cose perchè non è un linguaggio puro compilato, la cosa è anche facile in java a quanto ho letto ma in C/C++ sembra più complesso, anche se ho trovato dei link dove si vede come serializzare degli oggetti per salvarli su file binario e poi ricrearli in ram e l'esempio è davvero semplice.

Ma serializzare tutti gli oggetti di una struttura specie se la magior parte non è di tipo predefinito non è un lavoro pesante, forse sarebbe il caso di usare CORBA o altro IPC per comunicare tra processi o in rete.

Ciao.
MauroTec
Trollino in fasce
 
Messaggi: 21
Iscritto il: 11 feb , 2011 3:01 pm
Località: Palermo
Programmo in: C/C++ python

Re: QDataStream e strutture

Messaggioda TrueNeo » 29 mag , 2012 12:10 pm

.Net e tutti i linguaggi interpretati fanno dei check a runtime per il tipo di dato quindi diventa tutto più facile, in C++, essendo compilato, devi necessariamente predisporre a priori i meccanismi di identificazione e gestione del tipo di dato. Qt ha un dato "variabile" QVariant che incapsula tutte le funzioni di gestione dei dati principali e anche di quelli complessi come Liste e array, nasce proprio per alleggerire il programmatore da il dover riscrivere, fare overloading, di tutta una serie di operatori necesari alla gestione di dati complessi. Fatta questa premessa si intuisce che una scorciatoia, al problema da te posto non c'è, nel senso che overloading lo devi fare per forza, dipende però da "dove" intendi farlo e cioè dove da meno fastidio e risulta più menutenibile? Il mio consiglio è quello di registrare il tipo di dato in QVariant tramite QMetaType , quelle postate sembrano strutture dati basate su tipi standard non dovresti avere problemi. Al momento dello streaming riconosci il tipo di dato dall'Id che hai registrato interno al QVariant, quindi non devi preoccuparti di avere hash table o cose simili, riconosciuto il dato lo assegni al tipo di struttura opportuna.
Codice: Seleziona tutto
QFile file("file.dat");
file.open(QIODevice::ReadOnly);
QDataStream in(&file);    // read the data serialized from the file
QVariant data;
in >> data;
manageData(data);

ManageData( const QVariant & data )
{
controlla il tipo di dato con data.type() e lo assegni alla struttura adeguata con data.value();

}

La fatica sta nel registrare i tipi di dato e nell'aggiungere i controlli in ManageData( ).
Daniele

Prima di porre domande leggere il Regolamento e FAQ, poi usare la funzione "Cerca".
Avatar utente
TrueNeo
Admin
 
Messaggi: 453
Iscritto il: 29 dic , 2009 7:42 pm
Località: Bari
Programmo in: C++

Re: QDataStream e strutture

Messaggioda Marco Trapanese » 01 giu , 2012 10:44 am

@TrueNeo
Grazie, ho capito il suggerimento. Come dicevi tu non era quello che intendevo ma è un'altra via da considerare.

@MauroTec
Utilizzo anche un IPC (zmq) proprio per comunicare tra processi e in rete ma è proprio da lì che nasce il problema. Devi in ogni caso serializzare le strutture per poterle trasferire...
Marco Trapanese
Troll competente
 
Messaggi: 141
Iscritto il: 19 giu , 2010 10:31 am
Programmo in: C,C++,.NET,Python,QT


Torna a Qt & C++

Chi c’è in linea

Visitano il forum: Nessuno e 2 ospiti