Qu’est-ce que la récupération d’application ?
La récupération d’application (ou application recovery) est une fonctionnalité de Windows qui permet aux applications de redémarrer lorsqu’un évènement particulier survient. Cela peut être un crash ou bien un blocage de l’application lorsqu’elle ne répond plus. Ou bien, plus normalement, lors de l’installation d’une mise à jour. Dans ce dernier cas, la version courante doit être fermée avant de pouvoir exécuter la nouvelle version. Ne pas perdre ses données lorsqu’une application plante, ou conserver son contexte de travail lors d’une mise à jour, c’est agréable pour l’utilisateur.
Cette fonctionnalité est disponible depuis Windows Vista. Elle est donc disponible sous Windows 7, mais pas sous Windows XP.
Visual Studio 2010 a apporté son lot de nouveautés concernant les MFC, le support de la récupération en fait partie. C’est une fonctionnalité puissante et très facile à utiliser avec les MFC.
Créer une application MFC avec récupération
Pour mettre en oeuvre la récupération d’application, il suffit de créer un projet MFC avec l’AppWizard de Visual Studio 2010. Le menu Fichier / Nouveau projet ouvre l’AppWizard, et permet de sélectionner un projet C++ de type Application MFC.
Appelons-le par exemple TestRecover. En appuyant successivement sur les boutons Suivant, on utilise les options par défaut. Une étape importante est celle des options avancées, où le support de la récupération d’application est activée par défaut.
L’étape finale de l’assistant de création d’application MFC permet de choisir le type de document et de vue utilisé. C’est important car la sauvegarde et le chargement des documents est utilisée par la récupération automatique. Pour notre exemple, choisissons CEditView comme type de vue. Ainsi, l’application TestRecover permettra de gérer des documents de type texte. Nous pourrons saisir du texte, et vérifier que le texte non enregistré est bien conservé si un problème survient.
Le bouton Terminer génère notre application TestRecover. Pour la compiler et la lancer, il suffit d’appuyer sur F5.
L’application générée supporte déjà la récupération. Si un problème lui arrive, elle se relance et recharge ses documents ouverts. Pour le vérifier, il faut provoquer un problème, et voir comment l’application se comporte.
Comment provoquer un problème ?
Un problème, cela peut être un crash, ou bien un blocage de l’application par exemple. Pour provoquer cela, ajoutons quelques lignes toutes simples, dans la méthode CTestRecoverApp::OnAppAbout() du fichier TestRecover.cpp. Cette méthode est appelée lorsque l’on clique sur le menu “A propos” de l’application.
Quelle torture appliquer à notre application ? Un blocage avec une boucle infinie ? Un crash avec un accès mémoire interdit ? Nous avons le choix :
// Commande App pour exécuter la boîte de dialogue void CTestRecoverApp::OnAppAbout() { // Boucle infinie while (true) { } // Exception d'accès mémoire char *p = nullptr; *p = 5; //CAboutDlg aboutDlg; //aboutDlg.DoModal(); }
Par défaut, la sauvegarde automatique s’effectue toutes les 5 minutes. Dans le cadre de notre exemple, et pour éviter d’attendre trop longtemps, nous allons spécifier un délai d’une minute. Pour cela, il faut modifier la valeur CWinApp::m_nAutosaveInterval dans la fonction CTestRecoverApp::InitInstance() de notre projet, par exemple juste après la prise en charge du gestionnaire de démarrage :
// prend en charge le Gestionnaire de redémarrage m_dwRestartManagerSupportFlags = AFX_RESTART_MANAGER_SUPPORT_ALL_ASPECTS; m_nAutosaveInterval = 60000; // 60 sec entre chaque sauvegarde
Le fonctionnement de la récupération d’application
Pour éviter qu’un programme ayant un dysfonctionnement au démarrage se relance sans arrêt, Windows attend 60 secondes après son lancement avant d’activer la restauration. A noter également que la restauration d’application ne fonctionne pas si le programme est lancé depuis Visual Studio !
Il faut donc lancer TestRecover depuis l’explorateur Windows, puis attendre une minute avant de cliquer sur “A propos” pour que l’on puisse tester le redémarrage.
En cliquant sur le bouton “A Propos”, le programme va se bloquer et ne plus répondre. En affichant le menu de l’application dans la barre des tâches, on peut forcer sa fermeture.
Mais, plus forte que Jack Bauer face à la torture, TestRecover ne s’en laisse pas compter et Windows propose avec tact de redémarrer l’application :
Et Windows relance l’application !
A noter que ce comportement est similaire, quel que soit le type de problème rencontré par le programme. Un blocage ou bien une exception provoqueront le même traitement par Windows. Vous pouvez essayer en modifiant le code de la méthode OnAppAbout() pour provoquer différents problèmes.
Au redémarrage, TestRecover vérifie si des sauvegardes automatiques ont été effectuées. Si oui, il propose de les charger :
La restauration des documents s’effectue sans problème :
Pour résumer, grâce aux MFC, notre application :
- s’est enregistrée auprès de Windows comme application résiliente aux crashs : en cas de problème, elle sait redémarrer.
- a sauvegardé automatiquement les documents ouverts dans un dossier temporaire grâce à l’architecture Document/Vue des MFC
- a identifié le redémarrage comme survenant suite à un crash, et a activé le processus de restauration
- a trouvé des documents temporaires sauvegardés automatiquement, et a demandé à l’utilisateur s’il souhaitait les récupérer
- a réouvert la liste des documents précédemment ouverts.
Petite parenthèse, les MFC de Visual Studio 2010 gèrent automatiquement les interactions de l’application avec la barre des tâches de Windows 7. Ainsi, notre programme permet d’afficher les miniatures des documents ouverts dans la barre des tâches de Windows 7, et donne la possibilité de les fermer un par un, comme illustré ci-dessous :
Comment fonctionne la récupération ?
Tout ce processus est pris en charge par les MFC et par son modèle Document/Vue. Ici, nous l’avons utilisé sur une nouvelle application, mais il est très facile de l’intégrer à une application existante.
Si vous avec une application MFC utilisant le modèle Document/Vue, vous pouvez intégrer ce fonctionnement dans votre programme en affectant simplement une valeur à la variable CWinApp::m_dwRestartManagerSupportFlags lors de l’initialisation de l’application, et en recompilant l’application avec Visual Studio 2010.
Pour ajouter la récupération automatique à une application MFC existante, ajoutez la ligne suivante dans la fonction CWinApp::OnInitInstance :
// prend en charge le Gestionnaire de redémarrage
m_dwRestartManagerSupportFlags = AFX_RESTART_MANAGER_SUPPORT_ALL_ASPECTS;
Et c’est tout !
La sauvegarde automatique est prise en charge par les MFC, qui déclenchent la sérialisation de tous les documents modifiés à intervalle régulier (5 minutes par défaut), lorsque l’application est inactive, dans CWinApp::OnIdle().
L’application doit s’enregistrer auprès de Windows comme voulant être redémarrée si un problème survient. Par défaut, les applications qui plantent ne redémarrent pas ! Il faut un comportement spécial de l’application pour cela. Les MFC implémentent ce code dans CWinApp::InitInstance, et il n’est exécuté que si m_dwRestartManagerSupportFlags a la bonne valeur.
Lorsque l’application est redémarrée, elle est simplement relancée par Windows avec un paramètre spécial sur la ligne de commande. Ainsi le programme distingue un lancement normal d’un redémarrage. En cas de redémarrage, les MFC rechargent automatiquement la liste des documents ouverts.
Les niveaux de récupération
Les MFC proposent trois niveaux de récupération automatique :
- 1 – L’application est redémarrée si elle rencontre un problème. Les documents ouverts ne sont pas rechargés, et aucun contenu n’est restauré.
- 2 – L’application est redémarrée si elle rencontre un problème, et les documents ouverts sont rechargés. Mais s’ils n’ont pas été sauvegardés, les modifications sont perdues. Il n’y a pas de sauvegarde automatique des documents.
- 3 – L’application est redémarrée si elle rencontre un problème, et les documents ouverts sont rechargés. La sauvegarde automatique permet de récupérer des documents non enregistrés.
Ces trois niveaux correspondent aux trois cases à cocher des options de redémarrage automatique de l’étape avancée de l’assistant de création d’application MFC présenté plus haut. On peut spécifier à ce moment quel niveau de récupération on souhaite.
Il est possible de spécifier ce niveau de récupération par code, en affectant une constante à la variable m_dwRestartManagerSupportFlags, dans la fonction CTestRecoverApp::InitInstance() :
Niveau 1 : redémarrage simple :
m_dwRestartManagerSupportFlags = AFX_RESTART_MANAGER_SUPPORT_RESTART;
Niveau 2 : redémarrage et réouverture des fichiers ouverts :
m_dwRestartManagerSupportFlags = AFX_RESTART_MANAGER_SUPPORT_RESTART_ASPECTS;
Niveau 3 : redémarrage, réouverture des fichiers ouverts, et sauvegarde automatique :
m_dwRestartManagerSupportFlags = AFX_RESTART_MANAGER_SUPPORT_ALL_ASPECTS;
Par exemple, si l’on souhaite utiliser la récupération d’application sans sauvegarde automatique, on utilisera le niveau 2.
A noter que les niveaux 2 et 3 ne s’appliquent bien sûr qu’aux applications MFC qui utilisent le modèle Document/Vue.
Le délai de sauvegarde automatique
La sauvegarde automatique est effectuée à intervalle régulier (5 minutes par défaut), en appelant les méthodes de sérialisation des objets CDocument de l’application. La sauvegarde s’effectue lorsque l’application est inactive, lors de l’appel de CWinApp::OnIdle().
On peut modifier le délai de sauvegarde automatique lors de l’initialisation du programme en modifiant la valeur CWinApp::m_nAutosaveInterval, comme indiqué ci-dessus. Mais si on désire modifier ce délai pendant le fonctionnement de l’application, par exemple pour laisser l’utilisateur paramétrer le délai de sauvegarde, la modification de cette variable est inopérante. Il faut utiliser les méthodes CDataRecoveryHandler::GetAutosaveInterval et CDataRecoveryHandler::SetAutosaveInterval, dans la classe dérivée de CWinApp :
#include <afxdatarecovery.h> int interval = GetDataRecoveryHandler()->GetAutosaveInterval(); GetDataRecoveryHandler()->SetAutosaveInterval(interval);
Surcharger le code source des MFC
L’avantage des MFC c’est que le code source est fourni, et que si l’on souhaite adapter un comportement, il est très facile de dériver un objet et de surcharger une ou plusieurs méthodes.
L’essentiel de la gestion de la récupération est dans la classe CDataRecoveryHandler, dont le code source se trouve dans le fichier afxDataRecovery.cpp.
Pour modifier et adapter les scénarios de récupération, il faut créer une classe dérivée de CDataRecoveryHandler, et surcharger la méthode virtuelle CWinApp::GetDataRecoveryHandler() pour qu’elle retourne le nouveau type d’objet.
Cela permet par exemple, d’avoir une sauvegarde automatique différente en fonction du type de document, ou tout autre type de modification du comportement par défaut.
Il s’agit bien ici d’un avantage indéniable de C++ et des MFC sur d’autres environnements de développement : si l’on n’est pas satisfait du comportement par défaut, il est toujours possible de le modifier. Cela n’est bien sûr pas possible avec .Net par exemple…
Pour aller plus loin : l’API Win32 Restart Manager
Là où ça devient vraiment intéressant, c’est que Windows propose une API pour son gestionnaire de redémarrage (Restart Manager). Il est possible de commander le gestionnaire Windows de redémarrage pour, par exemple, faire redémarrer une application.
La fonction RmShutdown stoppe une application, mais seulement si l’application concernée supporte la récupération d’application.
La fonction RmRestart redémarre une application qui vient d’être fermée.
Ces fonctions sont utilisées par les programmes d’installation lorsqu’ils appliquent une mise à jour. Si le programme en train d’être mis à jour est lancé, il doit être fermé pour que la mise à jour s’applique. Puis, la nouvelle version est relancée lorsque la mise à jour est appliquée.
En utilisant ces fonctions, on pourra tester plus facilement le redémarrage d’une application. Cela évite de devoir lancer l’application, attendre une minute, et provoquer un plantage.
Windows utilise également ces fonctions lorsqu’il intercepte le crash des applications qui utilisent le gestionnaire de redémarrage.
Google Chrome utilise un gestionnaire de redémarrage pour ses fréquentes mises à jour. Microsoft Word utilise le gestionnaire de redémarrage pour éviter que l’on perde des documents non sauvegardés.
Avec RmShutdown, il est donc possible de fermer et redémarrer notre application TestRecover pour tester le redémarrage. Mais cela fonctionne aussi avec Chrome et Word. Voici le code qui effectue cette manipulation :
#include "windows.h" #include "RestartManager.h" #pragma comment(lib, "Rstrtmgr.lib") int wmain(int argc, WCHAR* argv[]) { DWORD dwSessionHandle = 0; WCHAR wszSessionKey[CCH_RM_SESSION_KEY+1]; LPCWSTR pwzResourcesToRestart[] = { L"C:\\Users\\Pierre\\AppData\\Local\\Google" L"\\Chrome\\Application\\chrome.exe" }; if (RmStartSession(&dwSessionHandle, 0, wszSessionKey) == ERROR_SUCCESS) { // Recherche l'application et l'enregistre if (RmRegisterResources(dwSessionHandle, 1, pwzResourcesToRestart, 0, NULL, 0, NULL) == ERROR_SUCCESS) { // Ferme l'application DWORD dw = RmShutdown(dwSessionHandle, RmForceShutdown, NULL); if (dw == ERROR_SUCCESS) { // redémarre l'appli dw = RmRestart(dwSessionHandle, 0, NULL); if (dw == ERROR_SUCCESS) { return 0; } } } } }
Ce code fonctionne avec Google Chrome, Microsoft Word (remplacer le chemin par “C:\\Program Files\\Microsoft Office\\Office14\\WINWORD.EXE”), et bien sûr avec notre exemple TestRecover.