Programmation Windows : e/s 1

Transcription

Programmation Windows : e/s 1
Programmation C++
TP 1 de gestion des entrées/sorties avec les MFC
Gestion de fichiers
2 méthodes sont disponibles : la sérialisation des données (à partir d'un menu Fichier) ou l'accès direct en
utilisant la classe CFile.
La classe document d'un programme MFC est utilisé pour sauvegarder et pour charger sur une unité de disque
les données utilisées par l'application.
Ces données sont accessibles aux autres classes de l'application et en particulier à la classe CView qui permet
de les afficher et de les modifier.
La classe document assure également l'interfaçage avec les commandes de menu Fichier destinées à la lecture et
à la sauvegarde des données.
La sérialisation
Cette méthode utilise la fonction Serialize(), prédéfinie par l'assistant AppWizard lors de la création du squelette
de l'application.
Lire et écrire des données membres de la classe document depuis la classe CView
Les variables membres de la classe document sont accessibles à la classe CView en définissant un pointeur :
CMonApplicationDoc * pDoc = GetDocument();
m_MaVariableView = pDoc->m_MaVariableDoc;//accès aux variables
Initialisation des variables de la classe document
Pour initialiser les variables de la classe document au démarrage de l'application et/ou après l'exécution de la
commande New du menu File(ou Nouveau du menu Fichier), on utilise la fonction OnNewDocument().
La fonction Serialize()
Cette fonction est automatiquement appelée lorsque l'utilisateur sélectionne la commande Open, Save ou Save
as dans le menu File (ou Ouvrir, Enregistrer ou Enregistrer sous …dans le menu Fichier)
void CMonApplicationDoc::Serialize(CArchive &ar)
{
if(ar.IsStoring())
{
//on insère ici le code pour le stockage
ar << m_MaVariableDoc;
}
else
{
//on insère ici le code pour le chargement
ar >> m_MaVariableDoc;
}
}
La variable ar représente l'archive, c'est-à-dire le fichier accessible en lecture/écriture. On utilise les opérateurs
surchargés >> et <<.
Remarque :
Le type est incompatible avec les opérateurs >> et <<. Il faut donc faire un casting vers le type WORD, soit :
ar << (WORD)m_MaVariableDoc;
Enregistrer les modifications avant de quitter
Les données sont affichées et modifiées à partir de la classe CView. Voici un exemple pour une variable
m_MaVariableView associée à un contrôle d'une zone de saisie. Toute modification dans cette zone provoque
l'émission d'un message EN_UPDATE. Dans la fonction de traitement de ce message, on peut écrire :
#njc Lycée « la Briquerie »
Programmation C++ gestion des entrées/sorties
& page 1/8
void CMonApplicationView::OnUpdate()
{
//récupère un pointeur sur la classe document
CMonApplicationDoc * pDoc = GetDocument();
//mise à jour de la variable associée au contrôle
//d'une zone de saisie
UpdateData(TRUE);
//accès aux variables
pDoc->m_MaVariableDoc = m_MaVariableView;
//indique que la donnée de la classe document a été modifiée
//et demande une sauvegarde
pDoc->SetModifiedFlag();
}
Si l'utilisateur tente de quitter l'application sans effectuer une sauvegarde, un message d'avertissement sera
affiché.
Définition du type des documents par défaut
Pour définir par défaut les extensions et les filtres des fichiers (document) de l'application, on va modifier le
champ Caption de l'entrée IDR_MAINFRAME de la String Table. Exemple :
MonApplication\n\nMonApplication\n\n\nMonApplication.document\nMonApplication Document
Les caractères \n délimitent les 7 paramètres. Soit :
MonApplication
-> titre de la fenêtre de l'application
\n (null)
MonApplication
\n (null)
-> type par défaut des documents
\n (null)
-> filtre dans la boîte de dialogue pour Ouvrir et Enregistrer Sous
MonApplication.document
MonApplication Document
Donc :
MonApplication\n\nMonApplication\nMonApplication (*.app)\n.app\n etc …
Remarque : le type de document par défaut peut être configuré au moment de la création du projet Workspace
en sélectionnant le bouton Advanced.
La classe CFile
La classe CFile contient de nombreuses méthodes dédiées à la gestion des fichiers sur disque : Open, Close,
Read, Write et aussi des fonctions pour l'accès direct comme Seek, SeekToBegin et SeekToEnd. Consultez
l'aide de Visual C++ pour obtenir la liste et la description des fonctions de la classe CFile (notamment pour
connaître les modes d'ouverture du fichier).
Exemple :
CFile MonFichier;
CString m_MaVariable("Hello world !!!");
MonFichier.Open("C:\\MonFic.txt",
CFile::modeWrite);
CFile::modeCreate |
MonFichier.Write(m_MaVariable, m_MaVariable.GetLength());
MonFichier.Close();
Choix entre les 2 méthodes
La sérialisation fournit l'interface de type Windows pour la gestion de fichiers.
#njc Lycée « la Briquerie »
Programmation C++ gestion des entrées/sorties
& page 2/8
Par contre, on préférera l'utilisation de la classe CFile pour manipuler des données internes à l'application
(fichier de configuration, etc …) et qui ne nécessite pas une interaction avec l'utilisateur.
Le menu de l'application
Un menu par défaut est créé automatiquement par AppWizard pour une application SDI ou MDI. Ce menu, qui
comprend les éléments Fichier, Edition et ?, peut être modifié à partir de l'entrée IDR_MAINFRAME de
l'éditeur de ressource Menu.
Une action utilisateur sur une entrée du menu génère 2 messages : COMMAND et UPDATE_COMMAND_UI.
Le message COMMAND est généré lorsque l'utilisateur clique sur un élément du menu et donc permet de
définir ce qui doit être exécuté dans ce cas. Le message UPDATE_COMMAND_UI permet de mettre à jour
l'apparence de l'élément de menu sélectionné (notamment : ü checked ou grayed). La fonction de traitement du
message UPDATE_COMMAND_UI reçoit un pointeur sur un objet CCmdUI dont les méthodes membres
sont :
Operations
Enable
Enables or disables the user-interface item for this command.
SetCheck
Sets the check state of the user-interface item for this command.
SetRadio
Like the SetCheck member function, but operates on radio groups.
SetText
Sets the text for the user-interface item for this command.
ContinueRouting
Tells the command-routing mechanism to continue routing the current message down the
chain of handlers.
Voici 2 exemples de menu utilisant les messages précédents :
Exemple 1 :
Voici le menu que l'on veut gérer :
La variable Niveau est déclarée dans la classe EATMEView et elle est gérée comme ceci :
void CEATMEView::OnUpdateDebutant(CCmdUI* pCmdUI) //message UPDATE_COMMAND_UI
{
//on met à jour le menu :
if(Niveau != DEBUTANT)
#njc Lycée « la Briquerie »
Programmation C++ gestion des entrées/sorties
& page 3/8
else
pCmdUI->SetCheck(FALSE);//on enlève le ü
pCmdUI->SetCheck(TRUE); //met un ü devant l'élément de menu
}
void CEATMEView::OnUpdateExpert(CCmdUI* pCmdUI)
{
if(Niveau != EXPERT)
pCmdUI->SetCheck(FALSE);
else
pCmdUI->SetCheck(TRUE);
}
void CEATMEView::OnUpdateNormal(CCmdUI* pCmdUI)
{
if(Niveau != NORMAL)
pCmdUI->SetCheck(FALSE);
else
pCmdUI->SetCheck(TRUE);
}
void CEATMEView::OnDebutant()
//message COMMAND
{
//on met à jour la variable associée
Niveau = DEBUTANT;
//on recommence la partie
PartieFinie = VRAI;
OnInitialUpdate();
}
void CEATMEView::OnExpert()
{
//on met à jour la variable associée
Niveau = EXPERT;
//on recommence la partie
PartieFinie = VRAI;
OnInitialUpdate();
}
void CEATMEView::OnNormal()
{
//on met à jour la variable associée
Niveau = NORMAL;
//on recommence la partie
PartieFinie = VRAI;
OnInitialUpdate();
}
Exemple 2 :
Voici le menu que l'on veut gérer :
L'élément de menu "Lancer l'affichage" ne sera actif seulement si un message a été saisi (par "Saisir Message")
et que la liaison a été configurée (par "Configurer Liaison"). Donc la gestion de ce menu est la suivante :
void CPanneauView::OnUpdateGesLance(CCmdUI* pCmdUI) //message UPDATE_COMMAND_UI
{
//on met à jour l'élément de menu "Lancer Affichage"
//suivant la condition suivante :
if((PortConfigure == FAUX) || (SaisieOk == FAUX))
pCmdUI->Enable(FALSE);//on le désactive
else
pCmdUI->Enable(TRUE);//on l'active
}
void CPanneauView::OnGesLance()//message COMMAND
{
AffichageEnCours = VRAI;
#njc Lycée « la Briquerie »
Programmation C++ gestion des entrées/sorties
& page 4/8
//on lance l'affichage
… ETC …
}
void CPanneauView::OnUpdateGesStop(CCmdUI* pCmdUI)
{
if(AffichageEnCours == FAUX)
pCmdUI->Enable(FALSE);
else
pCmdUI->Enable(TRUE);
}
Les DLL (Dynamic Link Library)
Les DLL sont utilisés par les programmeurs pour concevoir des applications modulaires au niveau de
l'exécution (run-time). On placera dans les DLL des bibliothèques de fonctions et de ressources accessibles à
l'application cliente.
Contrairement aux bibliothèques classiques, les DLL sont chargées pendant l'exécution de l'application.
Les avantages sont :
la taille de l'application EXE est réduite, puisque certaines fonctions et ressources sont stockées dans
des fichiers séparés (.DLL);
si 2 applications utilisent la même DLL, une seule version de cette DLL est nécessaire, donc un gain de
place sur le disque et partage des ressources;
on peut faire évoluer la DLL sans reprogrammer et recompiler l'application (si l'interface d'accès aux
fonctions de la DLL ne change pas);
l'utilisation des DLL facilitent la conception modulaire et le développement en équipe.
Les inconvénients sont :
il faut distribuer les DLL utilisées par l'application cliente;
Lorsque l'application s'exécute, elle doit, suivant les besoins, charger la DLL en mémoire (si elle n'est
pas encore chargée), puis se rattacher aux fonctions. Même si l'accès est tout de même rapide,
l'utilisation entraîne un ralentissement.
Théorie fondamentale sur les DLL
A la base une DLL est un fichier (doté à priori de l'extension .DLL) stocké sur le disque dur. Le DLL comprend
des variables globales (très peu utilisées en pratique), des fonctions compilées et des ressources. Cette DLL va
devenir partie intégrante du processus client. La DLL est compilée pour se charger à une adresse de base
préférentielle. Si il n'y a pas de conflit avec d'autres DLL, elle va être mappée à une même adresse virtuelle
dans le processus. La DLL comporte diverses fonctions exportées et l'application cliente, celle qui a chargée la
DLL, importe ces fonctions. Windows fait correspondre les importations et les exportations quand il charge la
DLL.
Une DLL contient une table des fonctions exportées. Ces fonctions sont identifiées par leurs noms symboliques
et par des entiers appelés nombres ordinaux. La table des fonctions contient aussi les adresses des fonctions
dans la DLL. Quand l'application cliente charge la DLL, elle ne connaît pas les adresses des fonctions qu'elle
doit appeler. Mais elle connaît les noms symboliques ou les ordinaux. Le processus de liaison dynamique crée
alors une table, qui relie les appels du client aux adresses des fonctions dans la DLL. Si vous modifiez et
recompilez la DLL, il n'est pas nécessaire de reconstruire l'application cliente (sauf si vous modifiez les noms
et/ou les paramètres des fonctions de la DLL).
Déclaration des fonctions :
Côté DLL :
Côté client :
#njc Lycée « la Briquerie »
extern "C" __declspec(dllexport) int MaFonction(int n);
extern "C" __declspec(dllimport) int MaFonction(int n);
Programmation C++ gestion des entrées/sorties
& page 5/8
Ces déclarations ne suffisent pas à relier le client à la DLL. Lorsque vous compilez la DLL, Visual crée la DLL
et la librairie d'importation .LIB. Cette librairie doit être spécifier à l'éditeur de liens (Projects -> Settings ->
Link).
Lien implicite et lien explicite :
Le lien implicite utilise la librairie d'importation .LIB pour connaître les noms symboliques des fonctions
exportées de la DLL (la librairie ne contient pas le code). Cette librairie contient aussi le nom de fichier de la
DLL (mais pas le chemin) qui sera écrit dans l'application .EXE. Quand l'application est chargée, Windows
recherche et charge la DLL et la lie dynamiquement grâce aux noms symboliques.
Le lien explicite (souvent utilisé dans les langages interprétés comme Visual Basic) n'utilise pas la librairie
d'importation .LIB. On utilise la fonction LoadLibrary de WIN32 en spécifiant en paramètre le nom et le
chemin de la DLL. La fonction LoadLibrary renvoie une valeur de type HINSTANCE qu'on utilise alors avec
la fonction GetProcAddress qui permet de transformer le nom symbolique en une adresse dans la DLL.
Avec un lien implicite, toutes les DLL utilisées sont chargées lorsque le client est chargé.
Avec le lien explicite, on contrôle le moment où les DLL sont chargées et déchargées. Dans ce cas on peut par
exemple choisir la DLL à charger en fonction de l'évolution de l'application (exemple avec une DLL avec des
chaînes de caractères en français et une DLL identique mais en anglais, le choix se faisant après avoir choisi la
langue).
Avec un lien explicite, vous précisez directement dans l'appel à LoadLibrary le chemin et le nom du fichier
DLL.
Avec un lien implicite, Windows essaie de localiser la DLL en examinant successivement : le répertoire
contenant l'application EXE, le répertoire courant du processus, le répertoire system de Windows, le répertoire
de Windows, puis ceux précisés dans la variable d'environnement PATH. Il faut faire attention à l'emplacement
lorsqu'on doit réaliser une mise à jour de la DLL.
Différents types de DLL
Une DLL WIN32 comporte une fonction d'entrée principale DllMain appelée automatiquement par Windows
lors du chargement et du déchargement de la DLL.
Vous n'êtes pas obligé d'écrire la fonction DllMain, dans ce cas une version vide sera incorporée.
Dans les applications MFC, AppWizard rajoute sa propre couche logicielle au dessus de WIN32. Il est alors
possible de créer 2 types de DLL: les DLL d'extension et les DLL conventionnelles.
Une DLL d'extension se lie dynamiquement au code de la version DLL de la bibliothèque MFC. Elle exige
donc une liaison dynamique (shared) avec les MFC mais aussi de partager les mêmes versions MFC (par
exemple MFC40.DLL). Vous pouvez alors utiliser et exporter des classes C++.
Si vous voulez une librairie susceptible d'être chargée par n'importe quel environnement de développement
WIN32 (par exemple Visual Basic) vous devez utiliser une DLL conventionnelle. La seule restriction est
d'utiliser exclusivement des fonctions C et non C++. Vous pouvez utiliser des classes C++ dans votre DLL
conventionnelle mais vous ne pouvez pas les exporter. Une DLL conventionnelle peut être liées
dynamiquement ou statiquement à la bibliothèque MFC. Dans le cas d'un lien statique (static), le code MFC
requis est introduit dans la DLL et la taille de celle-ci prend au moins 110 KO contre 20 KO pour une liaison
dynamique (shared).
Création d'une DLL avec AppWizard
AppWizard permet de créer 2 types de DLL (avec ou sans MFC) :
#njc Lycée « la Briquerie »
Programmation C++ gestion des entrées/sorties
& page 6/8
Exemple
On va créer une DLL pour gérer des entrées/sorties physiques. Pour simplifier le code, on utilisera les E/S du
port parallèle (sinon on préférera une carte PIA insérée dans le PC, voir TP).
Pour accéder aux entrées/sorties physiques, on utilise les fonctions _outp et _inp de la bibliothèque LIBC.LIB.
Ces fonctions nécessitent l'inclusion du fichier conio.h. Si on veut utiliser directement ces fonctions dans une
applications MFC, il faut alors compiler l'application en mode statique (on choisit l'option "Use MFC in a static
library) ce qui augmente la taille du fichier EXE (et ne permet pas alors l'utilisation des fonctions
d'entrées/sorties par d'autres applications).
On va donc écrire une DLL pour piloter les E/S physiques.
Création de la DLL :
On commence par créer un projet Workspace de type Dynamic-Link Library. Notre DLL ne nécessite pas
l'utilisation de MFC et contient seulement des fonctions en C pour gérer les E/S.
On crée un nouveau fichier xxx.cpp qui va contenir les fonctions Ecrire et Lire à exporter. Une fois le fichier
créé, on l'insère dans le projet Workspace.
# include <afx.h>
# include "conio.h"
extern "C" __declspec(dllexport) void Ecrire(unsigned short AdrPort, int Valeur)
{
_outp(AdrPort, Valeur);
}
extern "C" __declspec(dllexport) int Lire(unsigned short AdrPort)
{
int Valeur;
Valeur = _inp(AdrPort);
return Valeur;
}
On crée un deuxième fichier xxx.def qui va contenir la définition où on précise le nom de la librairie et les
noms des fonctions exportées de la DLL :
LIBRARY Paralel_es;
CODE PRELOAD OVEABLE DISCARDABLE
DATA PRELOAD SINGLE
EXPORTS
Ecrire;
Lire;
On insère le fichier xxx.def dans le projet Workspace.
On construit la bibliothèque DLL (et la librairie .LIB associée) avec la commande Build (Maj + F8).
Création de l'application cliente :
On crée un projet Workspace de type MFC AppWizard (exe) de type SDI.
On modifie le menu de l'application comme ci-dessous :
On donne ici les 2 versions d'utilisation de la DLL : avec lien explicite ou lien implicite.
Dans la déclaration de la classe, on rajoute les lignes de codes suivantes :
#njc Lycée « la Briquerie »
Programmation C++ gestion des entrées/sorties
& page 7/8
//Liens explicites :
//typedef void (*DLLECRIRE)(unsigned short Port, int Valeur);
//typedef int
(*DLLLIRE)(unsigned short Port);
//Liens implicites :
extern "C" __declspec(dllimport) void Ecrire(unsigned short AdrPort, int Valeur);
extern "C" __declspec(dllimport) int Lire(unsigned short AdrPort);
class CTest_paralel_esView : public CView
{
public:
//pour les liens explicites :
//HINSTANCE glibDLLParalel_es;
//DLLECRIRE Ecrire;
//DLLLIRE
Lire;
… ETC …
};
Dans le fichier .cpp associé, on édite les lignes suivantes, après avoir utilisé ClassWizard pour la gestion des
messages COMMAND associés au menu "Commandes" :
void CTest_paralel_esView::OnEcrire()
{
Ecrire(0x378, 0xAA);
}
void CTest_paralel_esView::OnLire()
{
CString Data;
int Entrees;
Entrees = Lire(0x379);
Data.Format("Entrees = %x\n", Entrees);
AfxMessageBox(Data, MB_OK);
}
void CTest_paralel_esView::OnInitialUpdate()
{
CView::OnInitialUpdate();
//utilisation des liens explicites :
//glibDLLParalel_es = LoadLibrary("paralel_es.DLL");
//Ecrire = (DLLECRIRE)GetProcAddress(glibDLLParalel_es, "Ecrire");
//Lire = (DLLLIRE)GetProcAddress(glibDLLParalel_es, "Lire");
}
Dernier point avant de créer et d'exécuter l'application cliente :
Dans le cas d'une utilisation d'un lien implicite, il faut rajouter la prise en compte de la librairie .LIB associée à
la DLL pour l'édition des liens. Pour cela, on va dans Projects -> Settings, dans l'onglet Link, on rajoute le nom
de la librairie dans le champ Object/Library modules.
Travail à réaliser.
Tester l'application avec le module LEDs/BP à relier sur le port parallèle.
#njc Lycée « la Briquerie »
Programmation C++ gestion des entrées/sorties
& page 8/8