MFC : utiliser la récupération d’application (recovery manager)

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.

mfc-app-wizard-recover-options

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.

mfc-app-wizard-view-type

 

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.

close-frozen-app

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 :

app-frozen-restart

Et Windows relance l’application !

app-recover

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 :

recover-documents

La restauration des documents s’effectue sans problème :

recovered-app

 

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 :

MFC-app-Windows7-previews

 

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.

Omnitouch : la nouvelle interface utilisateur de Microsoft Research

Un Kinect amélioré sur l’épaule, et ça donne Omnitouch, la nouvelle interface utilisateur inventée par Microsoft Research.

On se croirait dans une pub ou un film de science fiction :

microsoftomnitouch

Précision : nous ne sommes pas le 1er avril.

 

Les possibilités des Nouvelles Interfaces Utilisateurs (NUI) comme Kinect sont déjà impressionnantes malgré leur jeune âge. Evidemment, avec ce truc sur l’épaule on a un peu l’air d’un guignol, ou d’Antoine de Maximy, mais bon…

Bien sûr toutes les applis basées sur cette interface pourront être programmées en C++, comme celles qui existent déjà pour le SDK Kinect.

Le standard C++11 officiellement adopté par l’ISO

logo_iso

L’ISO est une organisation non gouvernementale, le plus grand éditeur mondial de normes internationales. Basé à Genève, il a le mérite de parler français en plus de l’anglais.

C’était annoncé, l’ISO a officiellement publié la nouvelle norme du C++ sous le numéro ISO 14882. Les évolutions du langages C++, que l’on appelait C++0x s’appellent maintenant C++11. C’est la première évolution majeure de C++ depuis 1998 avec la norme C++98.

C++11 contient notamment :

  • les fonctions lambda
  • sémantique “move”
  • les templates acceptant un nombre variable d’arguments (variadic templates)
  • les pointeurs intelligents (smart pointers) ne nécessitant plus l’emploi de delete.
  • parallélisme.
  • et bien d’autres

CPP11

Le but de C++11 est d’offrir le confort d’écriture de code des langages managés comme C#, tout en offrant une bien meilleure performance qu’un environnement nécessitant une machine virtuelle. Rien de moins !

Le comité ISO indique dans un communiqué de presse sur C++11 :

«Ces dernières années, le secteur s’est davantage tourné vers des environnements plus novateurs à code managé comme Java par exemple, qui font passer le confort du programmeur avant la puissance d’expression et la performance, puisqu’ils sollicitent par exemple constamment un récupérateur de mémoire, des métadonnées pour l’implémentation dynamique (metadata for reflexion) et une exécution du code sous le contrôle d’une machine virtuelle – autant de fonctions qui pèsent sur la performance même si elles sont superflues, voire inutilisées», explique Herb Sutter, animateur du groupe de travail de l’ISO qui a élaboré cette norme.

«Ces langages de programmation ont toujours leur place. Toutefois, la programmation dite traditionnelle, qui utilise des langages natifs comme C++, qui met en avant la puissance d’expression et a pour principe d’éviter de surcharger inutilement le système, n’a jamais vraiment été abandonnée. À présent que les améliorations apportées à C++11 intègrent bon nombre des points forts des langages managés, la version modernisée du code C++ est aussi irréprochable et sûre que n’importe quel autre code moderne, et aussi rapide en termes de performance par défaut et d’accès total et à tout moment au système d’exploitation sous-jacent.»

Que demander de plus ?

Le document ISO numéro 14882 constituant désormais la norme officielle du C++ est disponible pour la modique somme de 352 francs suisses (285 € !) On attendra que ce document soit republié ailleurs gratuitement, ça ne saurait tarder !

Et Visual C++ ?

A noter que la prochaine version de Visual C++ (Visual Studio 2012) dont une préversion est déjà disponible, n’implémentera qu’une partie des nouveautés de C++11. Le détail des fonctionnalités de C++11 implémentées dans VC++ 2010 et bientôt dans VC++ 2012 est disponible sur le blog de Visual C++.

Mais suite à la conférence Buid Windows (sur www.buildwindows.com) le mois dernier, Microsoft a entendu fort et clair l’avis des utilisateurs de Visual C++ qui réclament unanimement plus de C++11 dans Visual C++ ! Cette liste de fonctionnalités de C++11 sera plus longue qu’initialement prévue, tant mieux !

Deboguer Visual C++ : activer les traces de MSBuild

Quelquefois, la construction d’un projet Visual C++ ne se comporte pas comme on l’attend.

Par exemple, il arrive que Visual Studio veuille systématiquement compiler le projet avant de déboguer l’application, alors que le projet vient d’être compilé. C’est agaçant. “Pourquoi m’indique-t’il que le projet n’est pas à jour alors qu’il vient d’être compilé ?” se surprend-on à maugréer.

Si le processus de construction du projet ne se passe pas comme attendu, il existe un moyen d’en connaitre la cause : l’activation de traces, de message d’états qui indiquent en détail ce qui se passe lors de la construction. Pour des raisons de performances, ces messages sont désactivés par défaut.

Pour les activer, il faut ajouter les lignes suivantes dans le fichier %PROGRAMFILES%\Microsoft Visual Studio 10.0\Common7\IDE\devenv.exe.config, après la balise </configSections>:

<system.diagnostics>

<switches>

<add name=”CPS” value=”4″ />

</switches>

</system.diagnostics>

Il est conseillé de faire une copie de ce fichier avant de le modifier. Il faut disposer des droits d’administrateur pour effectuer la modification.

Les messages et les traces sont affichés avec l’application DEBUGVIEW que l’on peut télécharger sur le site technet : http://technet.microsoft.com/en-us/sysinternals/bb896647.aspx

Debug-view

Il n’est pas possible de voir les traces dans la fenêtre Output de Visual Studio.

Voici le genre de message que l’on peut obtenir:

[4616] Project ‘D:\Users\Pierre\Documents\Visual Studio 2010\Projects\RibbonGadgets \RibbonGadgets.vcxproj’ not up to date because ‘D:\USERS\PIERRE\DOCUMENTS\VISUAL STUDIO 2010\PROJECTS\RIBBONGADGETS\MAINFRM.H’ was modified at 10/11/2011 20:02:30, which is newer than ‘D:\USERS\PIERRE\DOCUMENTS\VISUAL STUDIO 2010\PROJECTS\RIBBONGADGETS\DEBUG\RIBBONGADGETS.EXE’ which was modified at 09/20/2011 22:14:55.