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