Tout d'abord bonjour tout le monde et bienvenue dans mon premier tutoriel pour le site du zéro !
Je vous apprendrai ici à utiliser l'API MySQL dans vos programmes pour les rendre encore plus complets et intéressants, à condition pour cela que vous compreniez bien ce que je vais vous apprendre. ;) A la fin de ce cours, vous devriez être capable de vous connecter à une base de données (BDD) et de faire des requêtes SQL depuis vos programmes C/C++.
Si vous êtes toujours partant, attachez vos ceintures, et préparez-vous à partir à l'aventure ! :pirate:
Pour pouvoir comprendre au mieux ce tutoriel, quelques connaissances sont requises.
MySQL
Vous devez savoir ce qu'est une BDD (base de données), ainsi que tout le vocabulaire comme : table, champ, ligne, requête SQL, ... Il vous est aussi conseillé de connaître les bases du langage SQL pour pouvoir comprendre les requêtes de base.
S'il vous manque certaines connaissances à ce sujet je vous conseille de lire le tuto de M@teo sur le PHP, de cette manière vous serez plus en mesure de comprendre ce tutoriel. ;)
C/C++
Il vous faudra avoir des bases en C (pointeurs, fonctions, conditions, boucles, installation d'une bibliothèque, etc.) pour comprendre ce tutoriel. Dans le cas échéant, je vous conseille encore une fois un cours de M@teo21 (jusqu'à la partie 3 minimum).
API MySQL, mais pour faire quoi?
Ben oui, à quoi ça nous sert d'apprendre à utiliser MySQL dans nos programmes?
Je vais répondre à cette question par un exemple simple.
Imaginons que vous vouliez faire un jeu dans lequel le joueur à un score final, alors vous allez faire un fichier score et y stocker le score. De cette manière, le joueur et ses amis pourront se concurrencer pour voir lequel est le meilleur. Mais, dans ce cas-là, pourquoi ne pas le faire à une plus grande échelle ?
En effet, vous pourriez enregistrer le score du joueur dans une BDD en utilisant l'API MySQL. Ainsi, ce n'est plus qu'avec ses amis que le joueur va concurrencer, mais avec tout le monde puisque tous les scores seront répertoriés dans cette BDD. Il vous suffira ensuite de faire une requête pour récupérer ces scores, pour que le joueur puisse voir s'il est le meilleur... ou pas.
Bien sûr, ce n'est qu'un exemple parmi tant d'autres. ;)
Portabilité
Utilisant les sockets de Windows, les exemples qui seront proposés dans ce tutoriel ne pourront être compatibles qu'avec Windows. Pour les utilisateurs UNIX, il vous faudra vous renseigner davantage (via ce cours sur les sockets par exemple).
le fichier libmysql.dll, que vous mettrez dans le dossier de tous vos programmes utilisant l'API MySQL (comme vous le faite habituellement pour tous vos fichiers .dll) ;
le fichier libmysqlclient.a, que vous mettrez dans le dossier lib du dossier mingw32 de Code::Blocks ;
un dossier MYSQL où se trouvent tous les fichiers d'en-têtes dont vous pourrez avoir besoin. Copiez ce dossier et collez-le dans le dossier include du dossier mingw32 de Code::Blocks.
Maintenant vous n'avez plus qu'à créer un nouveau projet, à ajouter le fichier .dll dans le répertoire de celui-ci et à lier le fichier libmysqlclient.a. Voilà, la bibliothèque est installée ; vous allez enfin pouvoir commencer à coder !
Linux
Pour Linux, voici la commande pour installer l'API MySQL (adaptez-la à votre distribution).
Nous arrivons maintenant à la partie « théorique » du tutoriel. Nous allons étudier les principales fonctions de l'API MySQL pour pouvoir ensuite créer un petit jeu utilisant tout ce qu'on aura appris.
Connexion/déconnexion
Tout d'abord, créez et configurez un nouveau projet en console, en utilisant les informations de la sous-partie précédente.
Pour pouvoir utiliser l'API MySQL, il faut commencer par inclure deux fichiers d'en-têtes :
#include <winsock.h>
#include <MYSQL/mysql.h>
Maintenant nous allons faire quelque chose d'indispensable pour pouvoir faire des requêtes dans votre programme : l'initialisation, la connexion et la déconnexion. Tout d'abord, il va falloir que nous déclarions un objet de type MYSQL. Ce dernier nous servira tout au long du tutoriel, afin de pouvoir utiliser la plupart des fonctions.
MYSQL mysql;
Puis, initialisons MySQL, grâce à la fonction :
mysql_init(MYSQL *mysql);
.
Une fois l'initialisation réalisée, il faut se connecte. Dans cette optique, nous allons utiliser deux fonctions, que voici :
Le premier argument est un pointeur de structure, que nous avons vu juste avant.
Le deuxième argument est l'option que nous voulons configurer, nous utiliserons toujours MYSQL_READ_DEFAULT_GROUP, qui lie les options spécifiées dans le fichier my.cnf.
Enfin, le troisième argument est la valeur de cette option ; vous pouvez mettre le nom que vous souhaitez.
mysql_real_connect :
Le premier argument est toujours un pointeur vers votre structure.
Le deuxième argument est le nom de domaine ou l'adresse de votre hébergeur (dans mon cas, c'est www.goldzoneweb.info). S'il est marqué « localhost », je vous conseille de faire comme moi, et de mettre l'adresse de votre hébergeur car l'utilisation de « localhost » peut engendrer des erreurs.
Le troisième argument est votre identifiant de connexion.
Le quatrième argument est votre mot de passe.
Le cinquième argument est le nom de votre base de données.
Le sixième argument est le port, je vous conseille de mettre 0 pour éviter les erreurs.
Le septième argument est le socket à utiliser, je vous conseille de mettre NULL ici.
Et le huitième argument est le flag, je vous conseille de mettre 0.
Comme vous l'aurez sûrement compris, mysql_options sert à spécifier des options de connexion, et mysql_real_connect sert à se connecter à une base de données. Pour terminer, il ne vous reste plus qu'à fermer la connexion MySQL à l'aide de la fonction :
mysql_close(MYSQL *mysql);
.
#include <stdio.h>
#include <stdlib.h>
#include <winsock.h>
#include <MYSQL/mysql.h>
int main(void)
{
MYSQL mysql;
mysql_init(&mysql);
mysql_options(&mysql,MYSQL_READ_DEFAULT_GROUP,"option");
if(mysql_real_connect(&mysql,"www.goldzoneweb.info","mon_pseudo","******","ma_base",0,NULL,0))
{
mysql_close(&mysql);
}
else
{
printf("Une erreur s'est produite lors de la connexion à la BDD!");
}
return 0;
}
Voilà je ne crois pas avoir besoin d'expliquer ce code, si ce n'est que, lorsque la connexion plante, mysql_real_connect renvoie NULL, ce qui nous permet de gérer les erreurs.
Les requêtes n'attendant aucun jeu de résultats
Hein, c'est quoi que ça, un nouveau type de requêtes ?
Non non, rassurez-vous ! En fait, vous les connaissez déjà : INSERT, DELETE, CREATE, UPDATE par exemple n'attendent aucune réponse. Au contraire, des requêtes comme SELECT ou SHOW attendent une réponse. Voyons maintenant la fonction permettant de faire des requêtes MySQL :
mysql_query(MYSQL *mysql, const char *query);
Il vous faudra spécifier pour cette fonction le pointeur de structures ainsi que la requête. Voici un exemple de son utilisation:
mysql_query(&mysql, "INSERT INTO ma_table VALUES('valeur 1', 'valeur 2', 'etc')");
Comme vous pouvez le voir, cette requête va enregistrer dans la table ma_table les valeurs que j'ai mises dans l'exemple. Maintenant, si vous voulez savoir combien de lignes ont été affectées par votre requête, il suffit d'appeler la fonction :
mysql_affected_rows(MYSQL *mysql);
Les requêtes attendant un jeu de résultats
Ce type de requête n'est pas plus compliqué que l'autre, si ce n'est que, comme on attend un résultat, il faut faire des opérations en plus. ;) Pour la requête en elle même ça ne change pas, il faut toujours utiliser la fonction mysql_query. Cependant, pour récupérer le résultat il y a deux façons de faire, que nous allons étudier ensemble.
mysql_use_result(MYSQL *mysql)
Cette fonction récupère le jeu de résultat, mais ne l'enregistre pas dans le client. Son principal avantage réside dans sa rapidité et dans sa faible utilisation de la mémoire. Toutefois, vous ne pourrez pas faire d'opérations comme revenir au résultat n°2, etc. Vous devrez stocker la valeur de retour de cette fonction dans un pointeur de structure de type MYSQL_RES, qui est fait spécialement pour stocker le jeu de résultats.
Stocker un jeu de résultat ne suffit pas pour le lire ; pour cela, vous devrez appeler une autre fonction :
mysql_fetch_row(MYSQL_RES *result);
La valeur de retour de cette fonction devra quant à elle être stockée dans un objet de type MYSQL_ROW (correspondant généralement à un tableau de chaînes de caractères) mysql_fetch_row stocke les valeurs de la ligne suivante dans le jeu de résultats. Il est nécessaire, de libérer la mémoire allouée par cette fonction.
mysql_free_result(MYSQL_RES *result);
Voici un exemple récapitulatif.
#include <stdio.h>
#include <stdlib.h>
#include <winsock.h>
#include <MYSQL/mysql.h>
int main(void)
{
//Déclaration du pointeur de structure de type MYSQL
MYSQL mysql;
//Initialisation de MySQL
mysql_init(&mysql);
//Options de connexion
mysql_options(&mysql,MYSQL_READ_DEFAULT_GROUP,"option");
//Si la connexion réussie...
if(mysql_real_connect(&mysql,"www.goldzoneweb.info","mon_pseudo","*****","ma_base",0,NULL,0))
{
//Requête qui sélectionne tout dans ma table scores
mysql_query(&mysql, "SELECT * FROM scores");
//Déclaration des objets
MYSQL_RES *result = NULL;
MYSQL_ROW row;
int i = 1;
//On met le jeu de résultat dans le pointeur result
result = mysql_use_result(&mysql);
//Tant qu'il y a encore un résultat ...
while ((row = mysql_fetch_row(result)))
{
printf("Resultat %ld\n", i);
i++;
}
//Libération du jeu de résultat
mysql_free_result(result);
//Fermeture de MySQL
mysql_close(&mysql);
}
else //Sinon ...
{
printf("Une erreur s'est produite lors de la connexion à la BDD!");
}
return 0;
}
Resultat 1
Resultat 2
Resultat 3
Resultat 4
Resultat 5
Resultat 6
Press any key to continue.
Le jeu de résultats est mis dans result, puis on fait une boucle pour lister toutes les lignes. Mais ça n'indique toujours pas la valeur de la ligne : deux nouvelles fonctions vont nous le permettre.
mysql_num_fields(MYSQL_RES *result);
Elle retourne un entier non signé qui correspond au nombre de champs de la table sélectionnée. Donc, pour pouvoir avoir les valeurs de notre requête, il va falloir stocker dans une variable le nombre de champs, puis faire une boucle pour avoir chaque valeur, une par une.
mysql_fetch_lengths(MYSQL_RES *result);
Elle retourne un tableau d'entiers non signés qui correspondent à la taille de la valeur de chaque champ du résultat. Avec ces informations, voici un exemple de requête complète ::
#include <stdio.h>
#include <stdlib.h>
#include <winsock.h>
#include <MYSQL/mysql.h>
int main(void)
{
//Déclaration du pointeur de structure de type MYSQL
MYSQL mysql;
//Initialisation de MySQL
mysql_init(&mysql);
//Options de connexion
mysql_options(&mysql,MYSQL_READ_DEFAULT_GROUP,"option");
//Si la connexion réussie...
if(mysql_real_connect(&mysql,"www.goldzoneweb.info","mon_pseudo","*****","ma_base",0,NULL,0))
{
//Requête qui sélectionne tout dans ma table scores
mysql_query(&mysql, "SELECT * FROM scores");
//Déclaration des objets
MYSQL_RES *result = NULL;
MYSQL_ROW row;
unsigned int i = 0;
unsigned int num_champs = 0;
//On met le jeu de résultat dans le pointeur result
result = mysql_use_result(&mysql);
//On récupère le nombre de champs
num_champs = mysql_num_fields(result);
//Tant qu'il y a encore un résultat ...
while ((row = mysql_fetch_row(result)))
{
//On déclare un pointeur long non signé pour y stocker la taille des valeurs
unsigned long *lengths;
//On stocke ces tailles dans le pointeur
lengths = mysql_fetch_lengths(result);
//On fait une boucle pour avoir la valeur de chaque champs
for(i = 0; i < num_champs; i++)
{
//On ecrit toutes les valeurs
printf("[%.*s] ", (int) lengths[i], row[i] ? row[i] : "NULL");
}
printf("\n");
}
//Libération du jeu de résultat
mysql_free_result(result);
//Fermeture de MySQL
mysql_close(&mysql);
}
else //Sinon ...
{
printf("Une erreur s'est produite lors de la connexion à la BDD!");
}
return 0;
}
D'abord, la chaîne de conversion de printf, %.*s, est assez austère, il faut l'avouer. Cependant, en prenant les éléments un par un, on arrive à la comprendre sans problème. Tout d'abord, le % indique le début d'un format. Ensuite vient le point, qui sert à séparer un nombre éventuel indiquant la largeur du champ et la précision désirés. Par exemple, avec %10.20s, on aura une largeur de champ de 10 et on écrira au maximum 20 caractères. Enfin, on trouve l'étoile, qui permet, en réalité, de spécifier la largeur ou la précision à l'aide d'une variable du programme. Celle-ci doit être de type int (d'où le transtypage).
Ainsi, l'appel à printf suivant (tiré du K&R) signifiera : « imprimer sur stdout au plus max caractères de la chaîne s ».
printf("%.*s", max, s);
Ensuite, la condition ternaire. Voici son équivalent sous forme classique :
if (row[i])
printf("%.*s", (int) lengths[i], row[i]);
else
printf("NULL");
Le principe est le même, à la différence que cette fonction stocke le jeu de résultat dans une mémoire tampon, ce qui rend possible des éventuelles opérations à partir de ce jeu. Notez néanmoins que la fonction peut se révéler plus lente que mysql_use_result.
Pour réaliser une requête similaire à celle de l'exemple précédent il suffit juste de changer mysql_use_result par mysql_store_result.
#include <stdio.h>
#include <stdlib.h>
#include <winsock.h>
#include <MYSQL/mysql.h>
int main(void)
{
//Déclaration du pointeur de structure de type MYSQL
MYSQL mysql;
//Initialisation de MySQL
mysql_init(&mysql);
//Options de connexion
mysql_options(&mysql,MYSQL_READ_DEFAULT_GROUP,"option");
//Si la connexion réussie...
if(mysql_real_connect(&mysql,"www.goldzoneweb.info","mon_pseudo","*****","ma_base",0,NULL,0))
{
//Requête qui sélectionne tout dans ma table scores
mysql_query(&mysql, "SELECT * FROM scores");
//Déclaration des objets
MYSQL_RES *result = NULL;
MYSQL_ROW row;
unsigned int i = 0;
unsigned int num_champs = 0;
//On met le jeu de résultat dans le pointeur result (maintenant on utilise mysql_store_result
result = mysql_store_result(&mysql);
//On récupère le nombre de champs
num_champs = mysql_num_fields(result);
//Tant qu'il y a encore un résultat ...
while ((row = mysql_fetch_row(result)))
{
//On déclare un pointeur long non signé pour y stocker la taille des valeurs
unsigned long *lengths;
//On stocke cette taille dans le pointeur
lengths = mysql_fetch_lengths(result);
//On fait une boucle pour avoir la valeur de chaque champs
for(i = 0; i < num_champs; i++)
{
//On ecrit toutes les valeurs
printf("[%.*s] ", (int) lengths[i], row[i] ? row[i] : "NULL");
}
printf("\n");
}
//Libération du jeu de résultat
mysql_free_result(result);
//Fermeture de MySQL
mysql_close(&mysql);
}
else //Sinon ...
{
printf("Une erreur s'est produite lors de la connexion à la BDD!");
}
return 0;
}
Mais alors pourquoi utiliser cette fonction si elle fait la même chose, en plus de temps ? o_O
Eh bien, c'est vrai qu'à première vue ça ne sert pas à grand chose, mais en fait l'intérêt de cette fonction est qu'on peut « voyager » dans le jeu de résultat, c'est-à-dire qu'on peut demander à avoir seulement le résultat n°2, ou n°3, etc. Pour cela, il existe une fonction que voici :
Elle prend en argument le jeu de résultat ainsi que le numéro de la ligne qu'on veut obtenir.
//Requête qui sélectionne tout dans ma table scores
mysql_query(&mysql, "SELECT * FROM livreor");
//Déclaration des variables
MYSQL_RES *result = NULL;
MYSQL_ROW row;
unsigned int i = 0;
unsigned int num_champs = 0;
//On met le jeu de résultats dans le pointeur result.
result = mysql_store_result(&mysql);
//On choisit une ligne.
mysql_data_seek(result, 4);
//On récupère le nombre de champs
num_champs = mysql_num_fields(result);
//On stocke les valeurs de la ligne choisie.
row = mysql_fetch_row(result);
//On déclare un pointeur long non signé pour y stocker la taille des valeurs.
unsigned long *lengths;
//On stocke cette taille dans le pointeur.
lengths = mysql_fetch_lengths(result);
//On fait une boucle pour avoir la valeur de chaque champ.
for(i = 0; i < num_champs; i++)
{
//On ecrit toutes les valeurs
printf("[%.*s] ", (int) lengths[i], row[i] ? row[i] : "NULL");
}
[4] [Joe] [78491]
Voilà, nous en avons donc terminé avec la théorie, nous allons maintenant passer à la pratique dans un petit TP, que de réjouissances en perspective. :ange:
Nous y voilà enfin ! Après toute cette attente nous allons enfin pouvoir pratiquer ! :D Ce petit TP ne sera pas bien compliqué et utilisera ce que vous venez d'apprendre.
Concept
Le concept de ce jeu est assez simple, nous allons reprendre le jeu du plus ou moins en console, et y apporter quelques modifications. Tout d'abord, il va nous falloir une table pour stocker les scores, dont voici le code :
CREATE TABLE `scores` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`pseudo` varchar(20) NOT NULL,
`score` int(11) NOT NULL,
KEY `id` (`id`)
);
Au début, il y aura un menu demandant si on jouer ou voir les scores.
Jouer
Si le joueur choisit de jouer, alors on entre dans le jeu du plus ou moins : un nombre est généré aléatoirement, l'utilisateur doit essayer de le trouver. Une fois cela fait, on indique au joueur le nombre de coups qu'il a fait et on lui demande son pseudo. Il suffit ensuite d'enregistrer le tout dans la table « scores ».
Scores
Pour afficher les scores, nous utiliserons le format suivant, avec les scores dans l'ordre croissant :
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <winsock.h>
#include <MYSQL/mysql.h>
int main(void)
{
long nombreMystere = 0, nombreEntre = 0, choix = 0;
const long MAX = 100, MIN = 1;
char pseudo[20] = "";
// Génération du nombre aléatoire
srand(time(NULL));
//Choix de voir les scores ou de jouer
printf("Bienvenue dans mon jeu de plus ou moins!\n\n");
printf("1- Jouer\n");
printf("2- Scores\n\n");
scanf("%ld", &choix);
switch (choix)
{
//Si on veut jouer
case 1:
//On génére le nombre aléatoire
nombreMystere = (rand() % (MAX - MIN + 1)) + MIN;
int i = 1;
do
{
// On demande le nombre
printf("Quel est le nombre ? ");
scanf("%ld", &nombreEntre);
// On compare le nombre entré avec le nombre mystère
if (nombreMystere > nombreEntre)
printf("C'est plus !\n\n");
else if (nombreMystere < nombreEntre)
printf("C'est moins !\n\n");
else
printf ("Bravo, vous avez trouve le nombre mystere !!!\n\n");
i++;
} while (nombreEntre != nombreMystere);
printf("Votre score est de: %ld\n\n", i);
printf("Veuillez entrer votre pseudo\n");
scanf("%s", pseudo);
//Déclaration de l'objet de type MYSQL
MYSQL mysql;
//Initialisation de MySQL
mysql_init(&mysql);
//Options de connexion
mysql_options(&mysql,MYSQL_READ_DEFAULT_GROUP,"option");
//Si la connexion réussie...
if(mysql_real_connect(&mysql,"www.votrehebergeur.com","mon_pseudo","*****","ma_base",0,NULL,0))
{
//On déclare un tableau de char pour y stocker la requete
char requete[150] = "";
//On stock la requete dans notre tableau de char
sprintf(requete, "INSERT INTO scores VALUES('', '%s', '%ld')", pseudo, i);
//On execute la requete
mysql_query(&mysql, requete);
//Fermeture de MySQL
mysql_close(&mysql);
}
else
{
printf("Une erreur s'est produite lors de la connexion a la BDD!\n");
}
break;
//On veut voir les scores
case 2:
printf("Liste des scores du jeu: \n\n");
//Déclaration de l'objet de type MYSQL
MYSQL mysql2;
//Initialisation de MySQL
mysql_init(&mysql2);
//Options de connexion
mysql_options(&mysql2,MYSQL_READ_DEFAULT_GROUP,"option2");
//Si la connexion réussie...
if(mysql_real_connect(&mysql2,"www.votrehebergeur.com","mon_pseudo","*****","ma_base",0,NULL,0))
{
//Requête qui sélectionne tout dans ma table scores
mysql_query(&mysql2, "SELECT * FROM scores ORDER BY score");
//Déclaration des pointeurs de structure
MYSQL_RES *result = NULL;
MYSQL_ROW row;
unsigned int i = 0;
unsigned int num_champs = 0;
int j =1;
//On met le jeu de résultat dans le pointeur result
result = mysql_use_result(&mysql2);
//On récupère le nombre de champs
num_champs = mysql_num_fields(result);
//on stock les valeurs de la ligne choisie
while ((row = mysql_fetch_row(result)))
{
//On déclare un pointeur long non signé pour y stocker la taille des valeurs
unsigned long *lengths;
//On stocke ces tailles dans le pointeur
lengths = mysql_fetch_lengths(result);
//On affiche la position du joueur
printf("%ld- ", j);
//On fait une boucle pour avoir la valeur de chaque champs
for(i = 1; i < num_champs; i++)
{
//On ecrit toutes les valeurs
printf("%.*s ", (int) lengths[i], row[i] ? row[i] : "NULL");
}
printf("pts\n");
//On incrémente la position du joueur
j++;
}
//Libération du jeu de résultat
mysql_free_result(result);
//Fermeture de MySQL
mysql_close(&mysql2);
}
else //Sinon ...
{
printf("Une erreur s'est produite lors de la connexion a la BDD!");
}
break;
//Sinon
default:
printf("Le choix que vous avez entre n'est pas valide!");
break;
}
return 0;
}
Voilà, ce cours sur l'API MySQL est maintenant fini, j'espère qu'il a été assez clair et que vous avez bien tout compris.
Voici quelques idées d'amélioration du mini-jeu que l'on vient de faire, que l'on réaliser au niveau de MySQL :
limiter le nombre de scores à afficher à 10 pour éviter que ça prenne trop de place ;
prévenir le joueur que l'enregistrement de son score s'est bien déroulé.
De plus, vous pouvez laisser libre cours à votre imagination. ;)
Nous pouvons donc nous quitter maintenant (j'en vois qui se réjouissent dans le fond ! :p ). Si vous voulez en apprendre encore plus sur cette bibliothèque, je vous conseille cette page : c'est la liste de toutes les fonctions de l'API MySQL avec leur description, en français !
Sur ce, au revoir tout le monde, et à bientôt pour de nouvelles aventures !