V. Les ressources▲
V-A. Introduction▲
Le développement d'applications est un art qui fait intervenir plusieurs techniques, moyens et objets. Il ne suffit pas d'avoir un compilateur et les librairies nécessaires, il s'agit également d'intégrer des images, du son, de la vidéo, etc. Enfin, cela dépend du type d'applications qu'on veut faire et de la manière dont on veut s'y prendre, mais bref…
Sous Windows, ces éléments « externes » qui viennent s'ajouter au programme s'appellent les ressources (cette définition n'est pas complète). Vous pouvez évidemment les séparer du programme (c'est-à-dire en tant que fichiers à part), mais vous pouvez également les intégrer à l'intérieur même de votre exécutable ! Et ce n'est pas tout ! Il n'y a pas que des fichiers qu'on peut mettre en ressources, on peut aussi y placer des chaînes ou des informations de version (version du fichier, copyright, nom de l'auteur, etc.) par exemple. Nous n'allons pas tout dire des ressources dans ce tutoriel, car il y a encore plein de choses que nous n'avons pas encore vu (de même nous n'allons pas encore voir comment afficher une image ou lire un mp3 …), mais nous allons au moins voir comment créer un fichier de ressources, le lier à l'exécutable et charger une ressource pendant l'exécution.
V-B. Exemple de fichier de ressources▲
Un fichier de ressources est un bête fichier texte qui (grossièrement) contient la liste des objets à embarquer. L'extension .rc est utilisée pour indiquer qu'il s'agit d'un fichier de ressources, mais évidemment on peut utiliser n'importe quelle extension. Le listing contenu dans un fichier de ressources s'appelle un script de ressources. La syntaxe est simplissime, en voici un exemple :
Logo ICON "
mylogo.ico
"
User ICON "
user.ico
"
Ajoutez donc ce fichier à votre projet. Lorsque vous compilerez ce dernier, tous les fichiers sources ainsi que le fichier de ressources seront compilés puis liés pour produire l'exécutable (avec une icône (mylogo.ico) cette fois-ci !). À noter que le compilateur de ressources est un programme à part (RC) et non le compilateur C lui-même. Il génère un fichier .res qui contient enfin effectivement les données à intégrer dans l'exécutable contrairement au fichier .rc qui n'est qu'un simple fichier texte.
Dans l'exemple donné plus haut, Logo sera donc le nom qui nous servira à identifier la ressource mylogo.ico dans le programme. De même, le nom User servira à identifier la ressource user.ico. Le mot-clé ICON sert à indiquer qu'il s'agit d'un fichier d'icône (le répertoire Common \ Graphics \ Icons de Visual Studio en contient plein au cas où …). Pour charger une icône, on utilisera la fonction LoadIcon. Cette fonction attend en premier paramètre le handle du module qui contient l'icône à charger, en second son nom, puis retourne un handle de l'icône ainsi chargée. Par exemple :
HICON hLogo =
LoadIcon
(
hInstance, "
Logo
"
);
Où hInstance est évidemment le handle de l'instance de notre application.
On peut maintenant utiliser hLogo partout où le handle d'une icône est requis, comme dans le membre hIcon de la structure WNDCLASS par exemple. En fait, la fonction LoadIcon ne charge pas vraiment une icône, mais retourne tout simplement le handle d'une icône appartenant à un module qui lui a déjà été chargé (donc avec ses ressources également !). En conséquence, il ne faut surtout pas chercher à libérer la mémoire utilisée par cette icône puisqu'elle sera automatiquement déchargée de la mémoire en même temps que le programme lorsque celui-ci se sera terminé.
On peut également utiliser un nombre à la place d'un nom pour identifier une ressource. La macro MAKEINTRESOURCE permet de convertir un nombre en chaîne de caractères que l'on pourra alors passer à une fonction comme LoadIcon par exemple.
Le principe est le même pour les autres types de fichiers. Pour les curseurs et les bitmaps par exemple, on utilisera respectivement CURSOR, HCURSOR, LoadCusror et BITMAP, HBITMAP et LoadBitmap.
V-C. Les icônes▲
Vous ne le savez peut-être pas encore, mais une fenêtre utilise en fait deux icônes : une « petite » et une « grande ». Petite icône est le nom donné à celle qui sera utilisée dans la barre de titre de ladite fenêtre et grande icône est celui de celle qui apparaîtra dans la boîte de basculement rapide entre fenêtres (celle qui apparaît quand on appuie sur ALT + TAB). Lorsqu'on ne spécifie pas d'icône, Windows utilisera alors l'icône par défaut (IDI_APPLICATION). La structure WNDCLASS permet de spécifier une icône qui sera à la fois utilisée comme petite et grande icône. Utilisez la structure WNDCLASSEX pour spécifier individuellement les petite et grande icônes. Elle ressemble à la structure WNDCLASS, mais possède deux champs supplémentaires : cbSize (mettre sizeof (WNDCLASSEX)) et hIconSm (permet de spécifier une petite icône tandis que hIcon sera utilisée pour comme grande icône).
On peut également changer dynamiquement l'icône d'une fenêtre en lui envoyant le message WM_SETICON. On mettra dans wParam ICON_SMALL ou ICON_LARGE suivant l'icône que l'on veut modifier et le handle de la nouvelle icône dans lParam.
V-D. Charger une image▲
Nous avons vu que les fonctions LoadIcon, LoadCursor et LoadBitmap permettent de récupérer respectivement le handle d'une icône, d'un curseur ou d'une image bitmap. La fonction LoadImage est beaucoup plus souple et générique (voir MSDN). Elle permet de charger aussi bien une icône qu'un curseur ou une image bitmap et ce depuis une ressource ou bien depuis un fichier. Si l'image a été chargée depuis un fichier, alors il faudra libérer la mémoire lorsqu'on n'en aura plus besoin : DestroyIcon pour une icône, DestroyCursor pour un curseur et DeleteObject pour un bitmap.
V-E. Mettre des données brutes en ressources▲
Le mot-clé RCDATA permet d'embarquer des données brutes en ressource. Par exemple :
MyData RCDATA
{
"
Bonjour.
\0
"
}
Qu'on aurait également pu écrire :
MyData RCDATA
{
"
B
"
, "
o
"
, "
n
"
, "
j
"
, "
o
"
, "
u
"
, "
r
"
, "
.
"
, "
\0
"
}
Ou encore plus tordu… Attention ! RC n'ajoute pas automatiquement le caractère de fin de chaîne c'est pourquoi il faut explicitement l'ajouter si on veut obtenir une chaîne terminée par zéro.
On peut également utiliser des entiers (éventuellement séparés par des virgules). Le type par défaut des constantes entières est WORD (unsigned short). Pour utiliser des entiers long (DWORD), il suffit d'ajouter le suffixe L. De même, on peut utiliser des chaînes UNICODE en préfixant la chaîne par L.
Pour accéder à la ressource, il faut suivre les étapes suivantes :
- localiser la ressource (FindResource) ;
- charger la ressource (LoadResource) ;
- obtenir un pointeur sur la mémoire utilisée par la ressource (LockResource).
Pour obtenir la taille de la ressource, on a la fonction SizeofResource. Voici un exemple d'utilisation de la ressource MyData :
#include <stdio.h>
#include <windows.h>
int
main
(
)
{
HINSTANCE hInstance;
HRSRC hrcMyData;
HGLOBAL hMyData;
LPVOID pMyData;
DWORD dwSizeofMyData;
hInstance =
GetModuleHandle
(
NULL
);
hrcMyData =
FindResource
(
hInstance, "
MyData
"
, RT_RCDATA);
hMyData =
LoadResource
(
hInstance, hrcMyData);
pMyData =
LockResource
(
hMyData);
dwSizeofMyData =
SizeofResource
(
hInstance, hrcMyData);
printf
(
"
%s
\n
"
, pMyData);
return
0
;
}
Dans la réalité on ajoutera évidemment des tests d'erreur. GetModuleHandle(NULL) retourne toujours le handle (HINSTANCE) de l'application courante. Dans une application GUI cette valeur nous est déjà fournie par le paramètre hInstance de la fonction WinMain. On notera également que certes, SizeofResource ne nous a été d'aucune utilité dans cet exemple, mais au moins ça nous a permis de voir comment l'utiliser. D'ailleurs ici, on aurait pu simplement la calculer avec strlen(pMyData) + 1.
V-F. Les ressources personnalisées▲
On peut évidemment définir ses propres types de ressource. Cela permet d'embarquer n'importe quoi (absolument n'importe quoi !) dans notre exécutable. Par exemple :
#define GENERIC_RESOURCE 0x100
Il est impératif d'utiliser une valeur supérieure ou égale à 0x100, car les valeurs comprises entre 0x00 et 0xFF sont déjà réservées. Voici un exemple :
MyFile GENERIC_RESOURCE "
myfile.dat
"
MyData GENERIC_RESOURCE
{
L"
Bonjour
"
, 0x00
}
Pour localiser MyFile pendant l'exécution, il suffit de faire :
FindResource
(
hInstance, "
MyFile
"
, MAKEINTRESOURCE
(
GENERIC_RESOURCE));