2013-01-21 18 views
12

Ho il seguente codice C++ a scopo di test in combinazione con SQLite3. È una classe chiamata customer con una funzione di callback dichiarata. Questa funzione di callback viene richiamata ogni volta che sqlite3_exec() restituisce risultati (record) dal database SQLite.Uso corretto della funzione di callback di sqlite3 in C++

Quello che non mi piace di questa costruzione è che il codice sorgente per elaborare i risultati si trova in una funzione di richiamata al di fuori della classe piuttosto che i risultati elaborati dal metodo di classe da cui viene chiamato sqlite3_exec().

Potrei utilizzare le variabili globali che verranno utilizzate nel metodo di classe dopo che la funzione di callback ha completato l'estrazione dei valori dai risultati della query SQL. Ma cosa succede se c'è più di un record e la funzione di richiamata viene chiamata più volte. Quindi ho bisogno di lavorare con gli array a meno che non mi assicuri che avrò solo risultati singoli.

Devo dimenticare la funzione di callback e andare in chiamate più profonde dell'API SQLite?

Oppure ho bisogno di andare in un wrapper C++, suppongo che non ci sia un meccanismo di richiamata e che i risultati siano passati al metodo di classe stesso?

// customer 
#include "Customer\customer.h" 
//## begin module%50E6CCB50119.additionalDeclarations preserve=yes 
static int callback(void *NotUsed, int argc, char **argv, char **azColName) 
{ 
    int i; 
    char* columnName; 
    char* columnValueString; 
    short int columnValueShortInt = 0; 
    int columnValueInt = 0; 

    cout << "begin of callback function\n"; 

    for(i=0; i<argc; i++) 
    { 
    columnName = azColName[i]; 
    if (strcmp(columnName, "FirstName")==0 || strcmp(columnName, "LastName")==0) 
    { 
     columnValueString = argv[i]; 
     cout << "columnName = " << columnName << "; value = " << columnValueString <<"\n"; 
    } 
    else 
    { 
     if(strcmp(columnName, "Age")==0) 
     { 
     stringstream(argv[i]) >> columnValueShortInt; 
     cout << "columnName = " << columnName << "; value = " << columnValueShortInt <<"\n"; 
     } 
     else // strcmp(columnName, "Id")==0) 
     { 
     stringstream(argv[i]) >> columnValueInt; 
     cout << "columnName = " << columnName << "; value = " << columnValueInt <<"\n"; 
     } 
    } 
    } 
    cout << "end of call back function \n"; 
    return 0; 
} 

//## end module%50E6CCB50119.additionalDeclarations 


// Class customer 

customer::customer() 
    //## begin customer::customer%50F969EE01E4.hasinit preserve=no 
    //## end customer::customer%50F969EE01E4.hasinit 
    //## begin customer::customer%50F969EE01E4.initialization preserve=yes 
    //## end customer::customer%50F969EE01E4.initialization 
{ 
    //## begin customer::customer%50F969EE01E4.body preserve=yes 
    customerId = 0; 
    zErrMsg = 0; 

    customerDataBaseRc = sqlite3_open("customerdb", &customerDataBase); 
    if(customerDataBaseRc) 
    { 
    fprintf(stderr, "Can't open database %s\n", sqlite3_errmsg(customerDataBase)); 
    sqlite3_close(customerDataBase); 
    } 

    const char * pSQL[6]; 
    const char * sqlStatement; 

    pSQL[0] = "create table customerTable (Id int, FirstName varchar(30), LastName varchar(30), Age smallint)"; 

    // execute all the sql statements 
    for(int i = 0; i < 1; i++) 
    { 
    customerDataBaseRc = sqlite3_exec(customerDataBase, pSQL[i], callback, 0, &zErrMsg); 

    if(customerDataBaseRc !=SQLITE_OK) 
    { 
     fprintf(stderr, "SQL error: %s\n", zErrMsg); 
     sqlite3_free(zErrMsg); 
     break; // break the loop if error occur 
    } 
    } 
    //## end customer::customer%50F969EE01E4.body 
} 


customer::~customer() 
{ 
    //## begin customer::~customer%50F93279003E.body preserve=yes 
    const char *pSQL[6]; 

    // Remove all data in customerTable 
    pSQL[0] = "delete from customerTable"; 

    // Drop the table from database 
    pSQL[1] = "drop table customerTable"; 

    // execute all the sql statements 
    for(int i = 0; i < 2; i++) 
    { 
    customerDataBaseRc = sqlite3_exec(customerDataBase, pSQL[i], callback, 0, &zErrMsg); 
    if(customerDataBaseRc !=SQLITE_OK) 
    { 
     fprintf(stderr, "SQL error: %s\n", zErrMsg); 
     sqlite3_free(zErrMsg); 
     break; // break the loop if error occur 
    } 
    } 
    cout << "destructor"; 
    //## end customer::~customer%50F93279003E.body 
} 



//## Other Operations (implementation) 
unsigned int customer::createCustomer (char iCustomerFirstName[20], char iCustomerLastName[20], unsigned short iCustomerAge) 
{ 
    //## begin customer::createCustomer%50EBFFA3036B.body preserve=yes 
    const char *sqlStatement; 

    string result;   // string which will contain the result 

    ostringstream convert; // stream used for the conversion 

    convert << "insert into customerTable (Id, FirstName, LastName, Age) values (" << customerId << ", '" << iCustomerFirstName << "', '" << iCustomerLastName << "', " << iCustomerAge << ")"; 
    result = convert.str(); // set 'Result' to the contents of the stream 

    sqlStatement = result.c_str(); 

    // Execute sql statement 
    customerDataBaseRc = sqlite3_exec(customerDataBase, sqlStatement, callback, 0, &zErrMsg); 
    // Check for errors 
    if(customerDataBaseRc !=SQLITE_OK) 
    { 
    fprintf(stderr, "SQL error: %s\n", zErrMsg); 
    sqlite3_free(zErrMsg); 
    } 

    return customerId++; 
    //## end customer::createCustomer%50EBFFA3036B.body 
} 

char * customer::getCustomer (unsigned int iCustomerId) 
{ 
    //## begin customer::getCustomer%50ED3D700186.body preserve=yes 
    const char *sqlStatement; 

    char *tmp ="blabla"; 

    string result;   // string which will contain the result 

    ostringstream convert; // stream used for the conversion 

    convert << "select * from customerTable where Id = " << iCustomerId; 
    result = convert.str(); // set 'Result' to the contents of the stream 

    sqlStatement = result.c_str(); 

    // Execute the sql statement 
    customerDataBaseRc = sqlite3_exec(customerDataBase, sqlStatement, callback, 0, &zErrMsg); 
    // Check for errors 
    if(customerDataBaseRc !=SQLITE_OK) 
    { 
    fprintf(stderr, "SQL error: %s\n", zErrMsg); 
    sqlite3_free(zErrMsg); 
    } 

    return tmp; 
    //## end customer::getCustomer%50ED3D700186.body 
} 

// Additional Declarations 
    //## begin customer%50E6CCB50119.declarations preserve=yes 
    //## end customer%50E6CCB50119.declarations 

//## begin module%50E6CCB50119.epilog preserve=yes 
//## end module%50E6CCB50119.epilog 
+0

è vero esempio di codice davvero minimale? –

+0

In realtà non credo, sono nuovo in questo forum, proverò a minimizzare il codice le volte successive, grazie per il suggerimento. –

risposta

20

ciò che si fa di solito in questo caso è sfruttare la void * (che si chiama NotUsed) parametro della callback - un parametro si definire quando si installa il callback. Per C++, in genere si imposta tale parametro sul puntatore this sull'oggetto interessato e si effettua la richiamata (una funzione extern "C" in un file sorgente C++) un metodo friend per la classe (se necessario).

Questo sarebbe simile a questa:

class customer 
{ 
    ... 
public: 
    int callback(int argc, char **argv, char **azColName); 
}; 

static int c_callback(void *param, int argc, char **argv, char **azColName) 
{ 
    customer* cust = reinterpret_cast<customer*>(param); 
    return cust->callback(argc, argv, azColName); 
} 

char* customer::getCustomer(int id) 
{ 
    ... 
    rc = sqlite3_exec(db, sql, c_callback, this, &errMsg); 
    ... 
} 

int customer::callback(int argc, char **argv, char **azColName) 
{ 
    ... 
} 
+0

Questo va oltre la mia attuale conoscenza del C++, per non dire esperienza, devo lavorare su questo. Vuoi dire, se passassi il puntatore, la funzione di richiamo sarebbe quindi in grado di riempire gli attributi dell'istanza di classe (oggetto) e quindi elaborare i valori all'interno del metodo stesso dopo che la chiamata sqlite3_exec è stata completata? –

+0

Sì. Dovresti lanciare il 'void *' in arrivo sul tuo tipo di puntatore oggetto - magari con qualcosa come 'MyClass * object = (MyClass *) NotUsed;'. Da quel punto puoi accedere a tutto ciò che è pubblico sul tuo oggetto; se vuoi accedere a cose private (o protette), devi dichiarare (nella tua classe) che 'callback()' è una funzione amico. Puoi facilmente trovare esempi di funzioni di amici in una ricerca su google. – mah

+0

Grazie, proverò ad attuare i tuoi suggerimenti, –

16

Utilizzando sqlite3_exec ha gli svantaggi che è necessario convertire alcuni valori di ritorno da una stringa in un numero, e che ha bisogno di allocare memoria per tutti i record di risultato (che può portare a problemi durante la lettura di tabelle di grandi dimensioni). Inoltre, il callback è sempre una funzione separata (anche se si trova nella stessa classe).

Per la vostra query di esempio, utilizzando l'API sqlite3_prepare/sqlite3_step/sqlite3_finalize sarebbe simile a questa:

void one_customer::readFromDB(sqlite3* db, int id) 
{ 
    sqlite3_stmt *stmt; 
    int rc = sqlite3_prepare_v2(db, "SELECT FirstName, LastName, Age" 
            " FROM customerTable" 
            " WHERE Id = ?", -1, &stmt, NULL); 
    if (rc != SQLITE_OK) 
     throw string(sqlite3_errmsg(db)); 

    rc = sqlite3_bind_int(stmt, 1, id); // Using parameters ("?") is not 
    if (rc != SQLITE_OK) {     // really necessary, but recommended 
     string errmsg(sqlite3_errmsg(db)); // (especially for strings) to avoid 
     sqlite3_finalize(stmt);   // formatting problems and SQL 
     throw errmsg;      // injection attacks. 
    } 

    rc = sqlite3_step(stmt); 
    if (rc != SQLITE_ROW && rc != SQLITE_DONE) { 
     string errmsg(sqlite3_errmsg(db)); 
     sqlite3_finalize(stmt); 
     throw errmsg; 
    } 
    if (rc == SQLITE_DONE) { 
     sqlite3_finalize(stmt); 
     throw string("customer not found"); 
    } 

    this->id   = id; 
    this->first_name = string(sqlite3_column_text(stmt, 0)); 
    this->last_name = string(sqlite3_column_text(stmt, 1)); 
    this->age  =  sqlite3_column_int(stmt, 2); 

    sqlite3_finalize(stmt); 
} 

(. Questo codice gestisce gli errori, semplicemente lanciando un string con il messaggio di errore)

+1

Sono davvero impressionato dalla velocità delle persone che offrono soluzioni così utili e dettagliate, molto apprezzate. –