Visual Studio 2012 adopte le look Metro

La version béta de Visual Studio 2012 (vs11) n’est pas encore disponible. Elle ne sortira que le 29 février 2012. Mais quelques captures d’écrans de cette version se sont retrouvées sur le blog Microsoft Windows Store for developers.

La version “Developer Preview” de Visual Studio 2012, qui était sortie en septembre 2011, avait le même look que Visual Studio 2010 :

vs11-look-vs2010
Visual Studio 2012 – pré-version sortie en Septembre 2011

La version béta de Visual Studio 2012 qui sortira le 29 février 2012 aura un look Metro : moins d’icônes, des onglets épurés…

visual-studio-2012-beta-metro-look

visual-studio-2012-beta-metro-look2

Un peu triste ?

Est-ce que l’interface utilisateur de Visual Studio 2012 est toujours écrite avec WPF ? Si ce n’est plus le cas, on peut espérer d’importants gains de performance…

visual-studio-2012-xaml-editor
Cliquer sur l’image pour l’agrandir

Cette version est la 11eme de Visual C++, donc son nom officiel est pour l’instant Visual Studio 11. Mais il est quasiment sûr qu’elle s’appellera Visual Studio 2012.

Mise à jour du 23 février 2012 : tous les détails du changement de look sont sur le blog Visual Studio http://blogs.msdn.com/b/visualstudio/archive/2012/02/23/introducing-the-new-developer-experience.aspx

Pourquoi l’implementation de std::shared_ptr est meilleure dans Visual C++ 2012 que dans les autres librairies C++ (boost, gcc, …)

Eh oui, pourquoi l’implémentation de std::shared_ptr est meilleure dans Visual C++ 2012 que dans les autres compilateurs ?

“Parce qu’elle utilise moins de mémoire, et qu’elle est plus rapide”.

Cette réponse est à l’image de la librairie STL : concise, efficace, mystérieuse ;).C’est aussi un prétexte à creuser le fonctionnement interne de shared_ptr. Allons-y.

 

Comment fonctionne un shared_ptr ?

En fait, un shared_ptr est un peu plus qu’un pointeur et un compteur de références. Comme la plupart du code des STL, shared_ptr est un code très dense, et… intelligent !

Un shared_ptr est un objet contenant deux pointeurs, soit 8 ou 16 octets selon qu’on compile en 32 ou 64 bits.

Voici un tout petit extrait de la définition de la classe _Ptr_base, qui sert de base pour shared_ptr et weak_ptr:

template<class _Ty>
class _Ptr_base
{    // base class for shared_ptr and weak_ptr
// ....
private:
    _Ty *_Ptr;
    _Ref_count_base *_Rep;
};

On y repère _Ptr, le pointeur vers l’objet lui-même, et _Rep, le pointeur vers un objet de type _Ref_count_base.

L’objet _Ref_count_base contient les compteurs de référence :

template<class _Ty>
class _Ref_count_base
{
private:
    long _Uses;
    long _Weaks;
    _Ty * _Ptr;
    // ...
};

Cette classe contient deux compteurs de références, en plus du pointeur vers l’objet lui-même :

  • un compteur de références pour les shared_ptr (strong ref)
  • un compteur de références pour les weak_ptr qui pointent vers l’objet (weak ref)

La création d’un shared_ptr provoque la création de deux objets :

  • l’objet shared_ptr lui même (dérivant de _Ptr_base), qui contient deux pointeurs
  • un objet de type _Ref_count_base, qui contient les compteurs de références.

Et c’est sans compter l’objet sur lequel pointe le shared_ptr ! Cela fait trois objets en tout, pour un seul shared_ptr !

image

 

Trois objets pour avoir un seul objet et un shared_ptr. C’est ainsi que fonctionnent les shared_ptr dans boost, gcc, clang, et… Visual C++ 2010.

Evidemment, si l’on crée un deuxième shared_ptr sur le même objet, seul un nouvel objet de type _Ptr_base sera créé. Le même objet _Ref_count_base sera utilisé pour les deux shared_ptr :

image

 

Accessoirement, on peut se demander pourquoi l’on a besoin de deux objets supplémentaires. Pourquoi est-ce que le shared_ptr n’est pas simplement l’adresse de l’objet _Ref_count_base ? Pourquoi a-t’on besoin d’un objet _Ptr_base en plus d’un _Ref_count_base ? Et pourquoi le pointeur vers l’objet cible (_Ty *_Ptr) est-il dupliqué dans _Ref_count_base et dans _Ptr_base ? Eh bien tout simplement pour que l’utilisation d’un shared_ptr soit aussi rapide qu’un pointeur classique. Pour que le déréférencement d’un shared_ptr soit rapide. Le fait d’avoir un pointeur vers l’objet cible (_Ty *_Ptr) en premier dans le shared_ptr permet d’accéder à l’objet cible plus rapidement, sans utiliser d’offset.

 

 

L’optimisation de shared_ptr dans Visual C++ 2012

Mais dans Visual C++ 2012 (VS11), Microsoft a été plus malin, et a remplacé le pointeur vers l’objet cible (_Ty *Ptr) par un champ _Storage bien mystérieux :

class _Ref_count_base
{
private:
    long _Uses;
    long _Weaks;
    typename aligned_storage<sizeof (_Ty),
        alignment_of<_Ty>::value>::type _Storage;
    // ...
};

Le champ _Storage correspond à l’objet alloué. _Storage est initialisé par make_shared, à la taille de l’objet pointé par notre shared_ptr (sizeof(_Ty)). Ainsi, l’objet cible du shared_ptr est placé directement dans l’objet _Ref_count_base.

image

L’avantage est que cela représente une indirection, une allocation, et un pointeur (4 ou 8 octets) de moins. C’est à la fois peu et beaucoup.

C’est peu car cela ne représente que 4 ou 8 octets. Mais c’est énorme car un programme peut contenir des milliers de shared_ptr. La création d’un shared_ptr devient plus rapide avec make_shared, et consomme moins de mémoire. Multiplié par le nombre de shared_ptr dans une application, cela peut faire beaucoup !

Gageons que cette optimisation sera reprise par Boost et les autres compilateurs. C’est le souhait qu’a exprimé Stephan T. Lavavej, programmeurs des librairies STL dans l’équipe Visual C++, lors de la conférence Microsoft Going Native 2012 sur le langage C++.

 

(

Entre parenthèse, une petite astuce.

Lorsqu’on passe un shared_ptr à une fonction, il est recommandé d’utiliser un passage par référence const plutôt qu’un passage par valeur :

void fonc1(const shared_ptr<type> &pointeur);
void fonc2(shared_ptr<type> pointeur);

Le passage par valeur (fonc2) provoquera la création d’un nouvel objet shared_ptr et son initialisation par son move-constructor. Le passage par référence (fonc1) est plus efficace.

)

C++11 dans Visual Studio 2012

 

La version beta de Visual Studio 2012

Une version béta de Visual C++ 2012 sera disponible d’ici quelques jours, au cours du mois de février 2012. Sans doute le même jour que la version béta de Windows 8.

C’est ce qu’a annoncé Herb Sutter le 3 février 2012 à la conférence Microsoft Going Native 2012 sur le langage C++.

herb-sutter-going-native
Herb Sutter à la conférence Going Native 2012

Les nouveauté de cette version béta seront :

  • support des applications pour tablettes Windows 8 (évidemment!)
  • support des processeurs ARM : possibilité de générer du code ARM
  • support de C++AMP
  • support partiel du langage C++11
  • support complet des librairies STL de C++11, notamment les nouveaux objets thread, mutex, atomics, future, async, …

Il était prévu un support partiel de la norme C++11 dans Visual C++ 2012, comme annoncé il y a quelques mois sur le blog de l’équipe Visual C++. Ce choix a finalement été remis en cause. Le support de C++11 sera amélioré dans Visual C++ 2012. Les deux fonctionnalités suivantes de C++11 seront présentes dans la béta de Visual C++ 2012 :

int array[5] = { 1, 2, 3, 4, 5 };
for (int& x : array)
    x *= 2;

 

Des mises à jour du compilateur C++ après la sortie de Visual Studio 2012

Après la sortie de Visual Studio 2012, des mises à jour du compilateur sont prévues dans les mois qui suivront sous la forme de Feature Packs. Cela permettra d’améliorer le support de C++11 sans attendre la version suivante de Visual Studio.

Plusieurs Feature Packs sont prévus. Les fonctionnalités prévues dans ces mises à jour du compilateur VC++ 2012 ne sont pas encore gravées dans le marbre. En voici la liste des principales :

 

we_need_you

Microsoft a besoin de votre avis sur ce qu’il faut mettre dans les Feature Packs de Visual C++ 2012 ! Dites quelles sont vos fonctionnalités favorites de C++11 dans une étude en ligne.

L’étude en ligne sur C++11 est ici : bit.ly/mscpp11

Le futur du langage ISO C++ : les nouvelles librairies PCL

La norme C++11 a été publiée il y a quelque mois à peine, et l’on parle déjà de la version d’après ! Peut-être C++16 ?

C++11 est plus ou moins supporté à ce jour par les différents compilateurs C++ (visual C++, gcc, clang…). Microsoft a encore pas mal de travail, mais l’équipe Visual C++ y travaille pour la prochaine version de Visual Studio.

C++11 est devenu un langage moderne et sûr. Mais C++11 a un énorme point faible par rapport à d’autres langages, comme Java ou C#. Ce point faible, ce sont…

Les librairies du langage !

Voici une représentation de la taille des librairies de quelques langages :

  • C++ 11
  • C# et les librairies .Net de base
  • Java 7
  • C# et les librairies complètes

langage-library-size

Oui, la taille des librairies C++11 est représentée par le petit carré bleu, si petit par rapport aux librairies de C# ou de Java.

Tous les programmeurs C++ le savent, les classes du langage C++, celles qu’on est sûr de retrouver d’un compilateur à l’autre, d’un OS à l’autre, sont peu nombreuses. On doit toujours utiliser des librairies non portables.

Eh bien cela va changer.

Le comité ISO C++ va largement étoffer les librairies standard dans la prochaine version de C++, prévue d’ici quatre ou cinq ans. Ce sera les PCL, Portable C++ Libraries.

C’est ce qu’a annoncé aujourd’hui 3 février 2012 Herb Sutter lors de la conférence Microsoft Going Native 2012 sur le C++.

Toutes ces librairies existent déjà sous une forme ou une autre. On peut citer boost, poco, Qt, MFC, et bien d’autres. Et puis tous les éditeurs de logiciels ont leurs propres librairies : Microsoft Office a ses librairies C++ portables sous Windows et Mac. Idem chez Adobe, Apple, Google, Facebook, … Le problème est que ces librairies ne sont pas standard, et que l’on n’est pas sûr de pouvoir les trouver dans toutes les implémentations des compilateurs C++, sur toutes les plate-formes.

A la demande du comité ISO C++, ces acteurs de l’industrie C++ sont mis à contribution pour apporter le meilleur de leurs librairies à la norme du langage C++. Un peu comme Boost a été mis à contribution pour C++11. Microsoft (notamment l’équipe Office), Apple et Google ont déjà proposé des librairies.

future-cpp-libraries

Voici les principaux domaines qui vont être ajoutés :

Classes de bas niveau :

  • Algorithmes parallèles
  • Types futurs (future.then, continuation)
  • Entrées-sorties asynchrones (async I/O)
  • Conteneurs tolérant le multitâche (thread safe containers)
  • Accès au système de fichier
  • Réseau, sockets
  • Queues de messages (message queues)
  • Serialisation d’objets

Classes de haut niveau :

  • Services Web REST
  • Protocoles HTTP, FTP…
  • Gestion des formats HTML XML XSLT, JSON
  • Paramètres/préférences
  • Compression
  • Cryptographie
  • Traitement de fichiers Son, Image, Video
  • Traitement de bases de données
  • Envoi de SMS

Le but n’est pas de permettre le portage des applications sur plusieurs plate-formes, comme Qt. Une librairie graphique portable sur plusieurs plate-forme serait forcément moins puissante que l’API native du système d’exploitation. Le but n’est pas non plus d’être une lourde surcouche au système d’exploitation, ou de devenir une plate-forme.

On n’en sait pas beaucoup plus pour l’instant, l’essentiel de ce que dit Herb Sutter à la conférence Going Native 2012 sur le sujet est résumé ici. Ce projet vient de démarrer.

Mais quelle évolution ce sera !

Des conférences Microsoft sur C++

On revient toujours à à ses anciennes amours, Microsoft ne fait pas exception.

C++ redevient à la mode ces temps-ci me confiait un chasseur de têtes la semaine dernière. La preuve : deux conférences Microsoft où l’on parle de C++ !

 

Going Native

cpp11-going-native-2012

Les 2 et 3 février 2012, une importante conférence exclusivement dédiée au langage C++11 se tiendra dans les locaux de Microsoft aux Etats-Unis. La conférence Going Native 2012 verra des intervenants prestigieux : Bjarne Stroustrup (créateur du langage C++), des membres éminents du comité C++ comme Herb Sutter, Andrei Alexandrescu, …

“C++11: Going Native” sera retransmise en direct et en différé sur internet gratuitement sur la chaîne Going Native de Channel 9.

Le thème de la conférence GoingNative est assez pointu puisqu’il s’agit de C++11, tout C++11 et rien que C++11 : des sessions techniques sur C++11, aujourd’hui et demain.

 

Techdays

techdays-2012

La semaine d’après on enchaîne avec les Techdays 2012 Microsoft à Paris les 7, 8, 9 février 2012. Parmi toutes les sessions, cinq concerneront C++. Du jamais vu ! Le blog Devosaure d’Eric Vernié en contient la liste : deux en anglais, trois en français :

 

Programmer Massivement Parallèle vos GPUs avec C++ AMP (niveau expert)

La prochaine version de Visual Studio propose une nouvelle technologie appelée C++ Accelerated Massive Parallelism (C++ AMP). Cette librairie permet d’exploiter facilement les nombreux coeurs des cartes GPU disponibles afin d’obtenir des performances extrêmes. À l’instar des librairies parallèles fournies avec Visual Studio 2010, la librairie C++ AMP sera accompagnée d’un écosystème facilitant le cycle de vie d’une application utilisant du code GPU. Que vous soyez développeur C++ ou bien même C#, cette session vous permettra de comprendre à la fois les concepts inhérents à la programmation sur GPU et leurs implémentations respectives avec C++ AMP. Si vous êtes déjà familier avec les technologies CUDA C et OpenCL, cette session vous permettra d’apprécier l’expressivité de C++ AMP associé à son outillage à la fois simple et efficace.

 

Construire des applications parallèles avec Visual Studio 11, quoi de neuf :

C’est une réalité aujourd’hui. Les PC, les tablettes et autres périphériques mobiles sont désormais munis pour la majorité de multi-coeurs. Mais comment les programmer ? Dans cette session, nous vous proposons de faire un tour d’horizon des nouvelles innovations de Visual Studio 11 en termes de parallélismes, qui viennent enrichir celles de Visual Studio 2010. Alors que vous soyez développeur .NET ou Développeur C++, ou les deux ou même pas du tout, cette session vous permettra d’avoir une vue générale sur les nouvelles fonctionnalités de Visual Studio 11 en termes de parallélisme.

 

What’s New in Visual C++ 11 :

In this talk you’ll see the new features and technologies that are coming with Visual C++ vNext, helping you build compelling applications with a renewed developer experience.

 

Application Lifecycle Management Tools for C++ in Visual Studio 11 :

Application Lifecycle Management (ALM) tools are critical for planning, development, testing, and maintenance of native code bases of every size. Visual Studio Ultimate offers many features for managing a C++ code base efficiently and easily. Come join us and see some of the current shipping features for C++ and get a live sneak preview of the features shipping in the next version of Visual Studio. You might be very surprised at all the new features for native C++ developers!

 

Les nouveautés de C++11 : Ecrire du C++ Moderne :

Le langage C++ a toujours la réputation d’un langage complexe, demandant une rigueur de tous les instants et qui peut en rebuter plus d’un. Mais avec l’arrivée de la nouvelle norme C++11 et de son intégration dans Visual Studio 11 (et intégration partielle dans Visual Studio 2010), elle permet comme le souligne Herb Sutter, d’écrire désormais du code “Clean, Safe and Fast”, nous passons à l’ère du C++ moderne.

 

Et une session plus fun, mais néanmoins très intéressante à mon avis : Développement de jeux 2D avec HTML5. Pour joindre l’utile à l’agréable, mais plus pour HTML5 que pour les jeux. HTML5 est partout, et bientôt dans votre application C++ ! HTML5 est un excellent choix pour réaliser l’interface utilisateur d’une application C++.

Intégrer une application C++/MFC avec la barre des tâches de Windows 7

Une application MFC s’intègre facilement avec la barre des tâches et le menu démarrer de Windows 7. Tellement facilement qu’il n’y a souvent rien à faire !

Les classes MFC ont été modifiées avec Visual Studio 2010 SP1 pour gérer l’affichage des miniatures des documents, de la liste des documents récents de Windows 7, et toutes les fonctionnalités de la barre des tâches de Windows 7.

Les modifications apportées aux MFC pour le support de la barre des tâches de Windows 7 est conséquent, et va bien au delà d’une simple encapsulation de l’API COM.

La bonne nouvelle est que ces ajouts sont “gratuits”, pour les applications existantes ou pour vos nouvelles applications MFC. Il suffit de compiler l’application avec Visual Studio 2010, et ça marche, tout simplement.

Si l’on désire effectuer une intégration poussée, on pourra être obligé d’écrire un peu de code…

Il est à noter que la totalité du code ci-dessous s’exécute sous Windows XP. Les méthodes appelées contiennent des tests de la version de Windows, et ne font rien si l’application MFC s’exécute sous Windows XP. L’ajout de ces fonctionnalités ne se fait pas au détriment de la portabilité vers Windows XP ! Pas d’excuse pour ne pas les utiliser :p

Le code source de l’application est disponible en téléchargement, avec un exemple compilé.

http://code.msdn.microsoft.com/Intgrer-une-application-bdfb752a

 

 

Affichage des miniatures

Une application MFC affiche automatiquement les miniatures de ses documents ouverts dans la barre des tâches de Windows 7. Voici par exemple une application MFC multi-documents :

MFC-app-Windows7-previews

Pour cet exemple, j’ai créé une application MFC en utilisant l’assistant App-Wizard, avec les réglages par défaut. Sans écrire une seule ligne de code, j’ai simplement utilisé le type de vue CEditView (voir la doc de CEditView sur MSDN). Je dispose d’une application MFC multi-document qui permet d’ouvrir des documents textes.

La capture d’écran ci-dessus a été effectuée en ouvrant deux documents. L’icône du programme est dédoublée pour bien montrer que deux documents sont ouverts, et deux miniatures des documents sont affichées.

 

 

Gestion des documents récents

Une application MFC gère par défaut une liste des fichiers récemment ouverts. L’objet persistent CRecentFileList gère cette liste (voir la documentation MSDN).

Ces fichiers sont placés par défaut dans le menu Fichier de l’application MFC :

MFC-app-recent-files

Ce qui est nouveau avec les MFC de Visual Studio 2010, c’est que l’objet CRecentFileList partage cette liste avec Windows 7. Il utilise notamment la fonction Win32 SHAddToRecentDocs. Les fichiers récents de l’application sont automatiquement affichés dans le menu Démarrer de Windows 7, avec les fichiers récents des autres applications :

MFC-app-Windows7-recent-files

Les fichiers récents sont aussi affichés dans la barre des tâches, si l’on clique avec le bouton droit de la souris sur l’icône du programme MFC :

MFC-app-Windows7-jumplist

 

Note : pour que la liste des fichiers récents fonctionne correctement, il faut que l’extension de fichier utilisée soit bien associée avec l’application MFC. On peut effectuer cette association en cliquant avec le bouton droit de la souris sur un fichier, puis sur le menu “Ouvrir Avec…”

 

 

Afficher un indicateur de progrès

Lorsqu’une longue opération est en cours, par exemple en tâche de fond, il est intéressant d’avoir une notification d’avancement dans la barre des tâches de Windows 7, comme par exemple :

MFC-Windows7-taskbar-progress-normal     MFC-Windows7-taskbar-progress-error

La classe CMainFrame (correspondant à la fenêtre principale de l’application) contient trois nouvelles méthodes, dont les noms sont explicites. Voici un extrait un peu modifié de la déclaration de la classe CFrameWnd dans le fichier afxwin.h :

class CFrameWnd : public CWnd
{
public:
  // Sets range for Windows 7 progress bar displayed on taskbar.
  // nRangeMin : Minimal value.
  // nRangeMax : Maximal value.
  void SetProgressBarRange(int nRangeMin, int nRangeMax);

  // Sets current position for Windows 7 progress bar displayed on taskbar.
  // nProgressPos : Specifies the position to set.
  //                It must be within range set by SetProgressBarRange.
  void SetProgressBarPosition(int nProgressPos);

  // Sets the type and state of the progress indicator displayed on taskbar.
  // tbpFlags : Flags that control the current state of the progress button.
  //            Specify only one of the following flags;
  //            all states are mutually exclusive of all others:
  //            TBPF_NOPROGRESS, TBPF_INDETERMINATE, TBPF_NORMAL,
  //            TBPF_ERROR, TBPF_PAUSED.
  void SetProgressBarState(TBPFLAG tbpFlags);
};

Ici il faut écrire du code (appeler ces trois méthodes !) pour afficher un indicateur de progrès. Voici un exemple :

CFrameWnd *pMainWnd = (CFrameWnd*)AfxGetMainWnd();
pMainWnd->SetProgressBarRange(0, 100);
pMainWnd->SetProgressBarState(TBPF_NORMAL); // TBPF_ERROR, TBPF_PAUSED...
pMainWnd->SetProgressBarPosition(40);

Le code est explicite. Il faut simplement appeler ces trois méthodes dans la classe CFrameWnd. Ou alors il faut récupérer l’objet CFrameWnd de l’application.

Dans le projet MFC/C++ associé à cet article, le code qui gère l’indicateur de progrès est dans MainFrm.cpp et MainFrm.h, avec le menu “Indicateur de progrès”.

 

 

Afficher une pastille de notification

Il peut être intéressant d’ajouter une petite pastille sur l’icône de l’application, pour afficher un état par exemple. MSN Messenger affiche l’état de la connexion par ce moyen, avec des pastilles de différentes couleurs :

mfc-windows7-overlay-icons

Pour afficher une pastille, il faut d’abord la dessiner. Il faut ajouter un fichier icone dans les ressources de l’application. La taille de cette image doit être de 16 x 16 pixels :

mfc-overlay-icon

Une fois le fichier ICO (16×16 pixels) ajouté dans les ressources, il suffit d’appeler la méthode CFrameWnd::SetTaskbarOverlayIcon pour afficher la pastille.

CFrameWnd *pMainWnd = (CFrameWnd*)AfxGetMainWnd();
pMainWnd->SetTaskbarOverlayIcon(IDR_OVERLAY, L"");

Et la pastille est ajoutée à l’icône de l’application. L’apparition de la pastille se fait avec un sympathique effet de fondu.

mfc-windows7-taskbar-overlay-icon

Si le programme est épinglé à la barre des tâches, la pastille disparait lorsque l’application est fermée.

Dans le projet MFC/C++ associé à cet article, le code qui gère l’indicateur de progrès est dans MainFrm.cpp et MainFrm.h, avec le menu “Pastille”.

 

Ajouter une liste de tâches

Certaines applications offrent la possibilité d’être lancées avec des options particulières. Par exemple, Internet Explorer offre un raccourcis pour le lancer directement en navigation InPrivate :

MFC-IE-Taskbar-tasks

Les MFC facilitent grandement la création d’une telle liste de tâche, grâce à la classe MFC CJumpList (voir la documentation MSDN de CJumpList). CJumpList gère la liste des raccourcis d’une application. On peut ajouter, supprimer des raccourcis, les classer par catégories, etc.

Nous nous contenterons d’ajouter deux tâches. Celles-ci lanceront l’application avec une skin bleue pour l’une, et avec une skin noire pour l’autre. Original…

En fait, une tâche, c’est un fichier exécutable (notre application en l’occurrence) et des paramètres. En cliquant sur le menu de la tâche, on lance tout simplement le fichier exécutable avec les paramètres spécifiés.

Notre première tâche lancera notre application MFC avec “/blue” en paramètre. La seconde utilisera “/black”. Voici le code qui ajoute ces deux tâches au menu de Windows 7 :

// Récupère le chemin du fichier exécutable
TCHAR szModule[MAX_PATH];
DWORD dwFLen = GetModuleFileName(nullptr, szModule, MAX_PATH);

// Ajoute deux tâches au menu de la taskbar
CJumpList TaskList;
TaskList.AddTask(szModule, L"/blue", L"Lance l'application en bleu", 0,0);
TaskList.AddTask(szModule, L"/black", L"Lance l'application en noir", 0,0);

Ce code provoque l’ajout de deux tâches dans le menu de notre application MFC :

MFC-windows7-Taskbar-tasks

Si l’on clique sur la tâche “Lance l’application en bleu”, notre application MFC sera lancée avec le paramètre “/blue”. Il faut que notre application traite les paramètres, et change la couleur en conséquence. Le code suivant, placé dans CMyApp::InitInstance, teste si un paramètre est présent sur la ligne de commande et change la couleur de la skin :

if (!wcscmp(m_lpCmdLine, L"/blue"))
    m_nAppLook = ID_VIEW_APPLOOK_OFF_2007_BLUE;
if (!wcscmp(m_lpCmdLine, L"/black"))
    m_nAppLook = ID_VIEW_APPLOOK_OFF_2007_BLACK;

Dans le projet MFC/C++ associé à cet article, le code qui gère ces deux tâches est dans le fichier principal, MFCTaskbar.cpp, dans la fonction CMFCTaskbarApp::InitInstance().

 

 

Afficher des boutons dans l’aperçu de la barre des tâches

Différentes applications utilisent l’aperçu de la barre des tâches pour y afficher une mini barre d’outils, composée de quelques boutons. C’est pratique, cela permet d’agir sur l’application tout en la laissant à l’état d’icône.

Le lecteur Windows Media affiche trois boutons, et Skype permet de modifier l’état directement dans l’aperçu de la barre des tâches :

taskbar-buttons-mediaplayer   taskbar-buttons-skype

Ceci ne fonctionne que dans une application SDI (Single Document Application). Cela ne fonctionne pas dans une application multi-documents (MDI).

Voici ce que cela peut donner avec une application MFC et deux boutons permettant de changer la couleur de la skin de l’application (bleu ou noir) :

MFC-taskbar-buttons

Pour cela, il faut principalement appeler la méthode COM ITaskbarList3::ThumbBarAddButtons (voir la documentation MSDN).

Les MFC se chargent d’instancier l’objet COM ITaskbarList3. Il existe une seule instance de cet objet par application. On récupère cet objet en utilisant la structure afxGlobalData :

ITaskbarList3 *pTaskBar = afxGlobalData.GetITaskbarList3();

La méthode ThumbBarAddButtons prend en paramètre un tableau de structure THUMBBUTTON. Ce tableau comporte autant d’élément THUMBBUTTON que l’on souhaite créer de bouton. Une structure THUMBBUTTON décrit un bouton : ID de commande, icône, tooltip, etc.

Voici le code qui crée les deux boutons dans l’aperçu de la barre des tâches. Deux structures THUMBBUTTON sont initialisées avant d’appeler ITaskbarList3::ThumbBarAddButtons :

if (afxGlobalData.bIsWindows7) // Windows 7 or higher
{
    THUMBBUTTON btn[2]; // Deux boutons

    btn[0].dwMask = THB_TOOLTIP|THB_ICON;
    btn[0].iId = ID_VIEW_APPLOOK_OFF_2007_BLACK;
    btn[0].hIcon = LoadIcon(IDI_ICON_BLACK);
    wcscpy_s(btn[0].szTip, L"Office black");

    btn[1].dwMask = THB_TOOLTIP|THB_ICON;
    btn[1].iId = ID_VIEW_APPLOOK_OFF_2007_BLUE;
    btn[1].hIcon = LoadIcon(IDI_ICON_BLUE);
    wcscpy_s(btn[1].szTip, L"Office Blue");

    HWND hwndMain = AfxGetMainWnd()->GetSafeHwnd();
    ITaskbarList3 *pTaskBar = afxGlobalData.GetITaskbarList3();

    // Crée les deux boutons
    HRESULT hr = pTaskBar->ThumbBarAddButtons(hwndMain, 2, btn);
    ASSERT(SUCCEEDED(hr));
}

Lorsque l’on clique sur un bouton, un évènement WM_COMMAND est envoyé à la fenêtre principale. L’ID du message WM_COMMAND correspond au champ iId de la structure THUMBBUTTON. Ce message WM_COMMAND a un code de notification spécial, THBN_CLICKED, qui permet de le différencier d’un WM_COMMAND normal.

Pour ajouter un gestionnaire d’évènement, on utilisera les macros ON_CONTROL, ou ON_CONTROL_RANGE, qui permettent de gérer les notifications THBN_CLICKED :

ON_CONTROL(THBN_CLICKED, ID_VIEW_APPLOOK_OFF_2007_BLUE, OnClickBlue)
ON_CONTROL(THBN_CLICKED, ID_VIEW_APPLOOK_OFF_2007_BLACK, OnClickBlack)
void CMainFrame::OnClickBlue()
{
    ...
}

Dans le projet MFC/C++ associé à cet article, le code qui gère la création des deux boutons est dans le fichier principal, MFCTaskbar.cpp, dans la fonction CMFCTaskbarApp::InitInstance(). Les gestionnaires d’évènements pour ces boutons sont dans le fichier MainFrm.cpp.

 

 

Télécharger le code

Un projet C++ est disponible en téléchargement, reprenant l’ensemble des fonctionnalités présentées. Le code source est inclus, ainsi qu’un exécutable compilé indépendant.

http://code.msdn.microsoft.com/Intgrer-une-application-bdfb752a

C’est avec des petits détails comme ceux-ci que l’expérience utilisateur d’une application est transformée. Ajouter ces fonctionnalités ne coute pas grand chose, grâce aux classes MFC. Et les utilisateurs apprécieront !

2011 un grand cru pour le C++

Pour le petit monde du développement d’applications sous Windows, 2011 a été une année pleine de surprises. Deux évènements majeurs ont marqué l’année pour le langage C++ :

  • la norme C++11
  • la renaissance de C++

 

C++11

Nous avons enfin eu notre nouvelle version du langage; C++11. Bjarne Stroustrup, le créateur du langage C++ déclare :

Programmer en C++11 donne l’impression d’utiliser un nouveau langage.

Un langage plus sûr, plus propre, plus puissant. Maintenant, quand je replonge dans du code C++ “classique” avec des new et des delete par exemple, j’ai la même impression bizarre que quand je visite le grenier de mon grand père. Les lignes de code C++ qui n’utilisent pas les fonctionnalités de C++11 ont pris un sacré coup de vieux…

Visual C++ 2010 propose déjà pas mal d’éléments de C++11. Et cela va s’améliorer. La prochaine version de Visual C++ (2012?) en apportera un peu plus. Mais un feature pack pour Visual C++ 2012 avec plein de nouveautés C++11 sortira un peu après.

Un chapitre de la documentation de la prochaine version de Visual C++ (2012) s’appelle Welcome back to C++ et présente ce nouveau langage C++11.

 

La renaissance de C++

Microsoft a fait un détour de dix ans par le monde du code interprété avec .Net. Le message était clair : le code .Net est supérieur en bien des points au code natif. Plus sûr et plus fiable tout en étant plus facile à écrire, et même parfois plus rapide que le code natif. Bref, point de salut hors Visual Basic ou C#.

La réalité s’est révélée toute autre. Microsoft a modifié sa stratégie cette année, notamment lors de la conférence Build Windows. WinRT, la nouvelle API de Windows, est écrite en C++, et accessible en C++ aussi bien qu’en Visual Basic. Microsoft parle de la “renaissance de C++”, avec même un podcast iTunes. Le code natif C++ n’est officiellement plus le vilain petit canard des langages supportés par Microsoft.

Pendant dix ans, Microsoft a poussé l’environnement .Net. Beaucoup de monde a suivi. Maintenant que la période du “tout .Net” est terminée, les remous sont nombreux. Les responsables de projets et développeurs Silverlight et WPF ont peur de voir ces environnements devenir obsolète. Par exemple, le logiciel Evernote a tenté d’utiliser WPF. Mais face aux problèmes rencontrés, ils ont préféré réécrire entièrement le logiciel en C++ natif : http://blog.evernote.com/2010/10/26/evernote-4-for-windows-is-here/

Peut-être qu’on peut maintenant distinguer clairement deux type d’applications sur le desktop :

  • Les applications grand public comme Word, Photoshop ou Google Chrome (consumer applications) qui continueront à être écrites en C++ natif, comme il y a dix ans.
  • Les applications de gestion qui utilisent des bases de données (LOB ou Line Of Business applications). C++ n’est pas adapté à ce genre d’application. Elles-ci seront toujours écrites en Visual Basic, en C#, ou bien en HTML5. Il y a dix ans, elles étaient écrites avec… Visual Basic 6 !

Direct2D et MFC, partie 1/N

Pourquoi Direct2D ?

GDI a très longtemps été un bon moyen d’afficher des graphismes à l’écran. Pendant 20 ans, nous avons utilisé GDI pour afficher graphiques, images et textes dans nos applications Windows. Avec Windows XP est apparu GDI+, une extension objet de GDI, avec des possibilités plus étendues. Mais GDI a été conçu à une époque où n’existaient ni cartes graphiques ni appareils photo numériques. Avec l’arrivée de Windows Vista, de WPF, GDI montre son âge, et apparait aujourd’hui largement dépassé.

old-horse-winxp-gdiUn développeur codant avec GDI+ sous Windows XP

Faire évoluer GDI s’est avéré impossible, son architecture étant trop limitée. Par exemple, GDI ne supporte pas les pixels de plus de 32 bits, et est incompatible avec les images à haute gamme dynamique. Et surtout, GDI est incompatible avec DirectX. Impossible de faire cohabiter les deux pour bénéficier de l’accélération matérielle et des animations que l’on souhaite dans les programmes d’aujourd’hui.

En 2006-2007, Microsoft a décidé de créer une nouvelle API graphique pour Windows 7. Elle permet d’afficher des graphismes, des images et du texte, et remplacera GDI. Les objectifs sont :

  • Plus de fonctionnalités que GDI et GDI+
  • Utilisation de l’accélération matérielle et les processeurs GPU des cartes graphiques. Performances nettement supérieures.
  • Interopérabilité avec les autres technologies graphiques de Windows : GDI, GDI+ (possibilité d’utiliser Direct2D dans un HDC créé par GDI/GDI+), Direct3D (possibilité de dessiner dans une surface Direct3D), Windows Image Codecs (WIC) pour lire et écrire les images dans les formats les plus courants, etc.

Si l’utilité de l’accélération matérielle est évidente, l’interopérabilité est également très importante. En effet, GDI est totalement incompatible avec Direct3D ou DirectDraw. Si l’on veut écrire une application graphique rapide avec DirectX, on ne peut utiliser ni GDI (pour afficher du texte par exemple), ni les contrôles standard de Windows. C’est très gênant, car cela implique une réécriture totale du code si l’on souhaite migrer de GDI à DirectDraw par exemple.

Ce problème est bien connu des développeurs de jeux avec Direct3D, qui doivent utiliser des librairies spécifiques basées uniquement sur DirectX pour afficher des interfaces homme-machines. Si l’on veut utiliser un simple champ Edit, il faut le coder en utilisant DirectX !

Autre exemple, l’application Picasa utilise l’accélération matérielle avec DirectDraw pour obtenir une fluidité suffisante. Mais les programmeurs n’ont pas pu utiliser GDI ni le système de fenêtres et de contrôles de Windows : ils ont dû écrire leur propre système pour gérer l’interface homme machine !

Microsoft a souhaité éviter ces écueils dès la conception de Direct2D.

Pour accélérer l’affichage d’une application GDI, il n’est pas nécessaire de modifier tout le code graphique. On pourra n’utiliser l’API Direct2D que sur certains points, et continuer à utiliser GDI pour le reste. Direct2D est le successeur de GDI et GDI+, et il est possible d’adopter petit à petit cette nouvelle API.

Et bien sûr, Direct2D utilise toute la puissance des processeurs GPU de nos cartes graphiques. Imaginez-vous ces chevaux qui piaffent d’impatience avec GDI dans vos cartes graphiques ? Ils sont occupés avec Direct2D !

running-horses-win7-direct2d
Un programme utilisant Direct2D s’exécute sous Windows 7 (Crédits photos www.sxc.hu/)

 

La compatibilité de Direct2D

Direct2D est construit sur Direct3D 10, la version de Direct3D qui est sortie avec Windows 7. Direct2D utilise toute la puissance de Direct3D. Cela lui permet non seulement d’être plus rapide que GDI, mais d’être plus puissant en même temps. Par exemple, l’affichage Direct2D de texte avec antialiasing est plus rapide que l’affichage GDI de texte sans antialising ! Dans les prochaines versions de Windows, Direct2D suivra bien sûr l’évolution des performances des versions à venir de Direct3D.

Direct2D fonctionne également avec une carte graphique compatible avec Direct3D 9. Et si votre PC possède une carte graphique ancienne, antérieure à Direct3D 9, Direct2D fonctionne en utilisant un mode logiciel.

Direct2D a été créé pour Windows 7, mais est également disponible sous Windows Vista.

Sous Windows 8, GDI n’est plus supporté dans WinRT. Il n’est pas possible d’utiliser GDI ou GDI+ pour écrire des applications Metro utilisant WinRT sous Windows 8. Il faut utiliser à la place Direct2D qui est complètement supporté dans WinRT. Voir l’article sur les API graphiques supportées dans WinRT sous Windows 8. Un argument de plus pour s’y mettre !

Par contre, Direct2D n’existe pas sous Windows XP.

Peut-être êtres vous concerné par l’absence de Direct2D sous Windows XP. Peut-être vous dites-vous : “Je ne vais pas utiliser Direct2D qui n’est pas disponible sous XP, alors qu’un tiers de mes clients utilisent encore XP. Je ne vais pas me priver d’un tiers de mes clients”.

En effet, Windows XP est encore installé sur un peu moins de 30% des PC en cette fin 2011. Mais ce chiffre diminue rapidement depuis la sortie de Windows 7, à un rythme d’environ 15-20% par an. D’ici un an, les utilisateurs de Windows XP devraient vraiment se faire rare. Un an, c’est à peu près le temps qu’il faut pour qu’un développement qui commence maintenant arrive chez les clients.

Et puis la prochaine version de Visual Studio, le 11eme du nom, qui devrait sortir en 2012, ne supportera plus Windows XP. Les applications générées par Visual Studio 2012 ne pourront pas s’exécuter sous XP. Tous les éditeurs de logiciels vont être obligés d’abandonner XP. La bonne nouvelle, c’est que vos concurrents aussi abandonneront XP en même temps que vous !

Enfin, si un client possède encore Windows XP et n’a pas encore migré vers Windows 7, c’est qu’il limite son budget. Il aura également du mal à acheter des logiciels, fussent-ils compatibles XP !

En conclusion, le temps est venu d’utiliser Direct2D sans retenue, et sans regret pour XP ! Quoi qu’il arrive, Direct2D remplacera GDI et GDI+.

 

Direct2D : COM or not COM

Direct2D est très différent de GDI et de GDI+. On peut placer Direct2D à mi-chemin de GDI et de Direct3D. C’est dire si Direct2D se trouve à la croisée de deux chemins très différents.

Direct2D est une API Win32, écrite en code natif bien sûr. Elle est disponible sous la forme d’objets COM. Microsoft a choisi de faire renaitre les objets COM 20 ans après la création de ce modèle objet. Même le successeur de Win32, WinRT sous Windows 8, est composé d’objets COM. C’est dire que la technologie COM est bien vivante sous Windows.

Concrètement, on pourra utiliser les objets Direct2D COM de deux manières :

- en manipulant directement les objets COM (ou en utilisant la librairie de templates ATL). Cela permet une utilisation efficace et de bas niveau. Une documentation d’ATL en français est disponible, mais elle date un peu. Il est préférable de se reporter sur une documentation en anglais d’ATL. Mais cette utilisation de bas niveau est fastidieuse et verbeuse. Si on peut s’en passer, on évitera.

- en utilisant les classes MFC qui encapsulent les objets COM Direct2D dans des objets C++, plus faciles et agréables à utiliser. Ouf ! Pas besoin d’utiliser l’horrible code COM/ATL.

Et il n’y a pas d’autre choix ! Soit vous utilisez directement les objets COM Direct2D, soit vous utilisez les MFC.

Pour vous aider à choisir entre ATL et MFC, voici un exemple code Direct2D qui crée un objet décrivant une police de caractères.

D’abord la version MFC :


// MFC : crée un objet TextFormat 

CD2DTextFormat textFormat(GetRenderTarget(), L"Verdana", 50);

Puis la version COM :


// Crée un objet COM TextFormat 

IDWriteTextFormat *pTextFormat = NULL;
HRESULT hr = m_pDWriteFactory->CreateTextFormat(L"Verdana", NULL,
        DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL,
        50, L"", &pTextFormat);
if (hr != S_OK)
    return; // erreur 

pTextFormat->Release();

En utilisant directement COM, on devra écrire du code plus verbeux, gérer les codes HRESULT, et surtout se préoccuper du comptage des références sur les pointeurs. En revanche, les MFC permettent d’utiliser une programmation plus objet notamment avec la technique RAII de C++.

Comme d’habitude au royaume des classes MFC, les classes Direct2D sont d’assez bas niveau. On pourra regretter de ne pas avoir d’interface de très haut niveau. Par contre, cela garanti une performance optimale, avec du code “près du métal”.

Note 1 : Les classes MFC encapsulant Direct2D sont une nouveauté du Service Pack 1 de Visual Studio 2010. Si vous avez Visual Studio 2010, vérifiez que vous avez bien installé le Service Pack 1 de Visual Studio 2010 pour utiliser Direct2D avec les MFC.

Note 2 : La version gratuite Visual C++ Express 2010 ne dispose pas des classes MFC. Avec Visual C++ Express, il n’y a pas d’autre choix que d’utiliser COM directement (bon courage !).

 

L’utilisation de Direct2D

L’utilisation de Direct2D doit se faire en trois étapes :

1 – Au début du programme : initialisation de COM, de Direct2D et création d’objets Direct2D de base :

    • Les premiers objets sont des objets “Fabrique” ou “Factory”. Ils vont servir à créer tous les autres objets Direct2D. Ils sont de type ID2D1Factory ou IDWriteFactory.
    • Puis il faut créer un contexte d’affichage ou RenderTarget. A la différence de GDI où le contexte d’affichage (Device Context HDC) est créé juste avant de dessiner, avec Direct2D on crée un objet RenderTarget au début du programme. Et comme GDI possède différents types de Device Context (Screen DC, Printer DC, Memory DC, …), Direct2D propose différents types de RenderTarget. Le principal type est HwndRenderTarget pour dessiner dans une fenêtre. D’autres types de RenderTarget permettent de dessiner dans un bitmap, dans une brosse, en mémoire, dans une surface Direct3D, etc.

Ce processus d’initialisation se résume au simple appel d’une fonction avec les MFC. Mais si l’on désire utiliser COM directement sans utiliser les MFC, il faut écrire à la main plusieurs dizaines de lignes de code. Les programmeurs DirectX/Direct3D connaissent bien le calvaire de l’initialisation de Direct3D !

 

2 – Création des ressources et objets graphiques à partir de l’objet fabrique. C’est lors de cette deuxième étape que l’on crée les formes, brosses, images, polices, couleurs, gradients, … (shapes, brushes, bitmaps, …) dont on aura besoin ultérieurement pour dessiner.
Cette manière de créer les ressources à l’avance vient de DirectX. Cela permet de factoriser le temps de création des objets graphiques, et d’optimiser le temps de dessin.

 

3 – Dessiner des formes, textes, images, etc. Lors de cette étape, on utilise les ressources crées précédemment, avec l’objet RenderTarget. Cette étape doit être encadrée d’appels à BeginDraw et EndDraw, un peu comme en GDI on devait utiliser BeginPaint et EndPaint :

    • BeginDraw()
    • Utilisation des ressources graphiques pour dessiner
    • EndDraw()

A noter ici une différence importante entre GDI et Direct2D. Direct2D utilise la technique du dessin hors écran (back-buffer ou double-buffer), comme Direct3D. Les fonctions de dessin doivent être encadrées par BeginDraw et EndDraw, et rien n’apparait à l’écran avant l’appel à EndDraw. Les fonctions de dessin modifient une zone graphique située en mémoire, hors de l’écran. Cette zone est affichée instantanément lors de l’appel à EndDraw. Cette technique avancée permet d’éviter le clignotement de l’affichage, surtout avec des animations, et donne un affichage beaucoup plus fluide qu’avec GDI.

 

Du code MFC !

Ecrivons un peu de code ! Comme je n’aime pas trop me frotter à COM directement, créons une application MFC standard avec l’assistant AppWizard de Visual Studio 2010 (menu Fichier/Nouveau/Projet), et appelons-la comme il se doit HelloWorld.

New-MFC-project

Le saviez-vous ? Dennis Ritchie, co-inventeur du langage C, décédé le 12 octobre dernier, est aussi l’initiateur de la tradition d’utiliser “Hello, world” comme message de test pour un programme simple.

En utilisant les paramètres par défaut de l’assistant de création d’application MFC “AppWizard”, nous obtenons un projet C++ qui, lorsqu’il est compilé, donne à peu près l’application suivante :

MFC-helloworld-app

Cette application ne contient aucun code Direct2D. Elle utilise GDI pour afficher l’intégralité de son contenu. Nous allons modifier cela pour que l’intérieur de la fenêtre HelloWorld1 soit dessiné avec Direct2D. Cette fenêtre correspond à la class CHelloWorldView dans notre projet, dans le fichier HelloWorldView.cpp.

 

Initialiser Direct2D

La première chose à faire est d’initialiser Direct2D. Nous l’avons vu un peu plus haut, l’initialisation de Direct2D est la première tâche à effectuer. Cette étape est grandement facilitée par les MFC puisqu’il suffit simplement d’appeler la méthode CWnd::EnableD2DSupport(). Comme cette méthode appartient à la classe CWnd, il faut l’appeler dans une fenêtre. Le constructeur de la classe CHelloWorldView est parfait pour cela :


CHelloWorldView::CHelloWorldView()
{
    // Initialise Direct2D

    EnableD2DSupport();
}

EnableD2DSupport effectue deux choses :

  • Initialiser COM et Direct2D. Cette initialisation n’est effectuée qu’une seule fois, même si EnableD2DSupport est appelé plus d’une fois. Ainsi il n’est pas gênant d’appeler EnableD2DSupport dans le constructeur d’une fenêtre, même s’il est possible de créer plusieurs instances de cette fenêtre.
  • Créer un objet RenderTarget pour la fenêtre associée. L’objet RenderTarget est utilisé par Direct2D pour toutes les primitives de dessin. L’objet RenderTarget est à Direct2D ce que le Device Context (HDC) est à GDI. Toutefois, un objet RenderTarget est généralement créé une seule fois, lors de la création de la fenêtre.

Note : Si l’on n’utilise pas les MFC mais COM directement, il faudra écrire les quelques dizaines de lignes de code correspondantes à la main.

Si l’on compile et exécute l’application après cette simple modification, on s’aperçoit que le contenu de la fenêtre CHelloWorldView est maintenant noir. Il s’est passé quelque chose !

MFC-helloworld-app-direct2D-initialized

Le fait de créer un objet RenderTarget et de l’avoir associé à la fenêtre a pour conséquence de confier son rendu à Direct2D plutôt qu’à GDI.

La taille de l’objet RenderTarget créé correspond à toute la zone client de la fenêtre HelloWorld1. C’est ce que les MFC font par défaut. Mais on aurait pu, à la main, créer un objet RenderTarget plus petit si l’on avait souhaité mélanger rendu GDI et rendu Direct2D dans la même fenêtre.

Ajoutons la ligne suivante en haut du fichier CHelloWorldView.cpp, cela facilitera l’écriture du code :

using namespace D2D1;

 

WM_PAINT et AFX_WM_DRAW2D

La chose à savoir maintenant est qu’avec Direct2D, il faut toujours répondre aux évènements WM_PAINT, comme avec ce bon vieux GDI. Evidemment, on n’appellera pas BeginPaint/EndPaint, mais BeginDraw/EndDraw à la place.

Dans le gestionnaire du message WM_PAINT, nous pourrions récupérer l’objet RenderTarget créé précédemment, et l’utiliser pour dessiner, encadré par des appels à BeginDraw/EndDraw :


void OnPaint()
{
    // Récupère la taille de la zone à dessiner 

    D2D1_SIZE_F renderTargetSize = m_pRenderTarget->GetSize();
    m_pRenderTarget->BeginDraw();

    // utilise m_pRenderTarget pour dessiner

    // ... 

 

    hr = m_pRenderTarget->EndDraw();
    ValidateRect(hwnd, NULL);
}

Sans oublier d’appeler ValidateRect, qui indique que la zone a dessiner a bien été traitée.

En fait, le code ci-dessus correspond à la manière d’utiliser Direct2D sans les MFC.

Si l’on utilise les MFC, on a droit à un traitement de faveur avec du code plus simple, même si, sous le capot, les MFC font quelque chose de totalement équivalent.

Les MFC ont défini le message AFX_WM_DRAW2D qui est envoyé à la fenêtre à la place du message WM_PAINT si la fenêtre possède un objet RenderTarget.

Autrement dit, si l’on veut dessiner avec Direct2D dans notre fenêtre MFC, nous utiliserons le message AFX_WM_DRAW2D à la place du message WM_PAINT. AFX_WM_DRAW2D est un message enregistré de Windows (registered message).

Ajoutons donc dans la classe CHelloWorldView un gestionnaire d’évènement pour le message AFX_WM_DRAW2D. Pour cela, utilisons l’assistant ClassWizard de Visual Studio (menu Projet / Class Wizard ou bien Ctrl+Maj+X). Cet assistant permet d’ajouter facilement des gestionnaires d’évènements.

Pour ajouter le gestionnaire d’évènement OnDraw2D correspondant au message AFX_WM_DRAW2D, il faut sélectionner la classe CHelloWorldView, cliquer sur l’onglet Messages, puis sur le bouton Ajouter un message, et saisir les noms de l’évènement et de la méthode. Ne pas oublier de cocher la case qui indique que l’évènement est un registered message.

Classwizard-add-Direct2D-OnDraw2d

L’assistant génère automatiquement le gestionnaire d’évènement CHelloWorldView::OnDraw2D. On peut y ajouter une ligne pour effacer le fond de la fenêtre :


// Gestionnaire d'évènement AFX_WM_DRAW2D 

afx_msg LRESULT CHelloWorldView::OnDraw2d(WPARAM wParam, LPARAM lParam)
{
    CHwndRenderTarget* pRenderTarget = (CHwndRenderTarget*)lParam;
    pRenderTarget->Clear(ColorF(ColorF::Beige));
    return TRUE;
}

Le contexte de dessin (l’objet CHwndRenderTarget de la fenêtre) est passé en paramètre. On peut l’utiliser pour dessiner. Pour l’instant nous effacerons simplement le contenu de la fenêtre en beige grâce à la méthode CRenderTarget::Clear qui à son tour appelle la méthode COM ID2D1RenderTarget::Clear.

Le gestionnaire d’évènement OnDraw2D retourne TRUE pour indiquer que la fenêtre a été complètement redessinée. La zone de dessin sera validée. En retournant FALSE, la fenêtre est considérée comme n’étant pas redessinée, et un message WM_PAINT classique devra être traité pour dessiner dans la fenêtre.

Il peut être intéressant d’utiliser à la fois OnPaint/WM_PAINT classique et OnDraw2D/AFX_WM_DRAW2D. Si l’on souhaite créer une application qui s’exécute aussi sous Windows XP, on voudra assurer la compatibilité en utilisant GDI et WM_PAINT. Comme les MFC ne sont pas liées statiquement avec la DLL Direct2D mais utilisent LoadLibrary/GetProcAddress, notre application qui utilise Direct2D sous Vista ou Windows 7 peut aussi se lancer sous Windows XP. Même si dans ce dernier cas bien sûr, on ne pourra pas utiliser Direct2D pour afficher le contenu d’une fenêtre.

 

Hello, world, le texte

L’affichage du texte ne fait pas à proprement dit partie de Direct2D, mais de DirectWrite. DirectWrite est le compagnon de Direct2D qui gère les polices de caractères et l’affichage de texte. Si le nom des objets COM de Direct2D commence par ID2D1, le nom des objets COM de DirectWrite commence par IDWrite. Microsoft a décidé de séparer arbitrairement DirectWrite et Direct2D, mais ils sont complémentaires et fonctionnent de la même façon. D’ailleurs le nom des classes MFC gérant DirectWrite commencent par CD2D, comme les classes Direct2D.

Comme nous l’avons vu plus haut, l’affichage avec Direct2D s’effectue en deux étapes ;

  • la création des ressources graphiques
  • le rendu des objets graphiques dans un RenderTarget

L’affichage d’un texte ne déroge pas à la règle. Nous avons besoin de créer deux objets pour afficher un texte :

  • une police de caractère, ou plutôt un objet TextFormat (interface COM IDWriteTextFormat, ou objet MFC CD2DTextFormat). TextFormat définit les attributs de police de caractères (nom, taille, style, …), mais aussi la manière de formater le texte (alignement, justification, interligne, retour à la ligne, etc).
  • une brosse (brush) de type ID2D1SolidColorBrush ou CD2DSolidColorBrush qui indiquera la couleur du texte à afficher.

Direct2D possède différents types de brosses. Nous utiliserons une couleur unie (ID2D1SolidColorBrush ou CD2DSolidColorBrush), mais il existe des dégradés linéaires ou radiants, et des brosses images :

A vous de modifier le code pour utiliser les autres types de brush !

Il faut ajouter la déclaration des deux ressources graphiques dans le fichier HelloWorldView.h :

protected:
    CD2DTextFormat*    m_pTextFormat;
    CD2DSolidColorBrush* m_pBlueBrush;

Puis, ajouter le code qui crée ces deux ressources dans le constructeur de la fenêtre CHelloWorldView, dans HelloWorldView.cpp :

using namespace D2D1;

CHelloWorldView::CHelloWorldView()
{
  // Initialise Direct2D
  EnableD2DSupport();

  // Crée les ressources graphiques D2D
  m_pBlueBrush = new CD2DSolidColorBrush(GetRenderTarget(),
                        ColorF(ColorF::RoyalBlue));

  m_pTextFormat = new CD2DTextFormat(GetRenderTarget(), _T("Gabriola"), 50);
  m_pTextFormat->Get()->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_CENTER);
  m_pTextFormat->Get()->
                SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_CENTER);
}

Après avoir initialisé Direct2D pour la fenêtre CHelloWorldView, on crée un objet Brush de couleur bleue, puis un objet TextFormat. m_pTextFormat contient la description de la police de caractères à utiliser pour l’affichage du texte, ainsi que des attributs de formatage de texte. Ici, le texte est centré verticalement et horizontalement dans la zone où il est affiché.

Il ne reste plus qu’à afficher “Hello, World” dans le gestionnaire d’évènement OnDraw2D de la classe CHelloWorldView, en utilisant les objets Brush et TextFormat créés :

// Gestionnaire d'évènement AFX_WM_DRAW2D
afx_msg LRESULT CHelloWorldView::OnDraw2d(WPARAM wParam, LPARAM lParam)
{
    CHwndRenderTarget* pRenderTarget = (CHwndRenderTarget*)lParam;
    ASSERT_VALID(pRenderTarget);

    // Efface le fond de la fenêtre
    pRenderTarget->Clear(ColorF(ColorF::Beige));

    // Dessine le texte
    CRect rect;
    GetClientRect(rect);
    pRenderTarget->DrawText(_T("Hello, World!"), rect,
                                m_pBlueBrush, m_pTextFormat);
    return TRUE;
}

Le résultat obtenu à l’exécution est quand même extraordinaire, non ?

hello-world-direct2D

 

Télécharger le code

L’exemple de code montré ici est très simple, mais la description du fonctionnement des MFC sous le capot est riche d’enseignement.

Le code complet de la solution est téléchargeable sur la galerie de code de MSDN, sur ce lien : http://code.msdn.microsoft.com/MFC-Direct2D-Hello-World-9aa6ae00

Le code et la page de téléchargement sont disponible en français ou en anglais. Vous pouvez choisir la langue sur le site MSDN en haut à droite de la page.

 

Pour aller plus loin…

D’autres articles viendront compléter ce Hello World. En attendant, pour aller plus loin, il existe quelques ressources sur le web :

Les langages a lecture-seule ou ecriture-seule

Pour se détendre… voici une nouvelle classification des langages :)

Il existe différentes catégories de langages de programmation :

  • des langages à typage explicite (C, C++, Pascal, …) dans lesquels les variables doivent être déclarées, ou à typage implicite (Javascript, Actionscript) où il n’y a pas besoin de déclarer les variables.
  • des langages statiques, ou dynamiques (les éléments du programmes sont connus à l’exécution, pas à la compilation)

et bien d’autres catégories, c’est connu. Mais saviez vous qu’on pouvait aussi donner des attributs de lecture-seule ou d’écriture seule à un langage ?

 

Le langage à lecture seule

Comme un fichier qui est en lecture seule, un langage à lecture seule est un code qui ne peut pas être modifié. Il peut être lu et compris, mais il n’est pas possible d’imaginer pouvoir modifier le code écrit.

Un exemple de code à lecture seule est le code de la librairie STL de C++. Si le code est lisible (à oeil entraîné), la modification d’un seul caractère est impossible tellement le code est précis, pointu et réfléchi.

        // TEMPLATE FUNCTION for_each
template<class _InIt,
    class _Fn1> inline
    _Fn1 _For_each(_InIt _First, _InIt _Last, _Fn1 _Func)
    {    // perform function for each element
    for (; _First != _Last; ++_First)
        _Func(*_First);
    return (_Func);
    }

Les langages à lecture seule ont une syntaxe subtile, et ont des caractères magiques qui modifient complètement le sens d’une expression.

 

Le langage à écriture seule

A ne pas confondre avec le langage à écriture seule qui, comme sa définition le laisse entendre, peut être écrit, mais ne peut pas être lu.

Un exemple valant mieux qu’un long discours, voici un exemple de code à écriture seule :

(function(a,b){function cy(a){return f.isWindow(a)?a:a.nodeType===9?
a.defaultView||a.parentWindow:!1}function cv(a){if(!cj[a])
{var b=f("<"+a+">").appendTo("body"),d=b.css("display");b.remove();
if(d==="none"||d===""){ck||(ck=c.createElement("iframe"),
ck.frameBorder=ck.width=ck.height=0),c.body.appendChild(ck);

C’est tout simplement le début de la librairie Javascript JQuery. Ca a été écrit, mais impossible de le lire !

Plus généralement, seul l’auteur d’un code en écriture seule est capable de savoir ce qu’il y a dedans. C’est synonyme de code-spaghetti. Cela peut être volontaire : certains développeurs cultivent cet attribut car il peut s’agir d’une manière de conserver son poste !

Note : l’un des “inventeurs” du concept de langage à lecture seule est Miguel de Icaza.

C++ code analysis in Visual Studio 2012

Lire cet article en français

lots-of-crashes_thumb1

The problem with this kind of message is that it does not make your customers very found of your application. And what if it happens during trial period?

Good news is, code analysis is one answer to this kind of problem.

Code analysis examines source code at compilation time, and finds bugs before they even happen. It is one of the debugging tools a developer can use. It complements the usual test procedures (functional testing, unit testing …) Its secret is to find unsafe code patterns in source code. And it is efficient.

Code analysis is one of the tools that can be used to create safer applications. It is part of Microsoft SDL methodology (Security Development Lifecycle).

I’m sorry to inform you that Code analysis knows which bugs you create. Hence, it can find them in your code:

  • dereferencing null pointer
  • buffer overrun
  • uninitialized variable
  • memory leak
  • concurrent access problem

Code Analysis in Visual C++ 2012 (VS11) and MFC

 

The team which has created Code analysis for C++ used the huge code base of Windows. They know what type of code is safe, and what type of code can cause a crash!

Code analysis in Visual Studio 2010 is only available in Visual Studio Ultimate and Premium. But Microsoft decided it is important for every C++ developer to have this tool handy. So, a light version of C++ code analysis is included in Visual Studio 11 Express, and all of the 200+ rules are in Visual Studio 11 Professional (from Code Analysis talk at Build Conference)

Code analysis has been improved in Visual Studio 11 in many ways: it is easier to use, thanks to the new Code Analysis window, and it is more powerful, more readable (more explicit messages) and reported problems are more relevant. Code analysis uses rules to find unsafe code patterns. The rules are improved, and cover a wider range of code, including multitasking, and soon 64-bit compilation.

Visual Studio 11 Developer Preview, a pre-release version of Visual Studio vNext, allows everyone to use C++ code analysis and its bug traps, under Windows 7 (Visual Studio 11 is temporary name of next release of Visual Studio.)

When you use C++ MFC application wizard (menu File/New project/MFC Application), a SDL checks checkbox can activate Code analysis:

MFC-app-wizard-11-SDL-check_thumb3

When the project is created, Code analysis can be run very easily:

Visual--studio-2010-build-menu-code-

Code analysis must recompile the project. It is slightly longer than a simple compilation.

Note : for non-MFC C++ projects, code analysis can also be activated in the project properties (menu Project/Properties), in C/C++ general tab.

 

A few bugs detected by Code Analysis

 

All indices of arrays are scrutinized by Code analysis to detect potential overflow, especially in the loops. If an array index goes out of the array boundaries, the problem will be reported.

For example, in CDocument class, let’s create an array and use it like this:

class CMFCApplication1Doc : public CDocument
{
    // ... static const int m_size = 100;
    char m_tab[m_size];
    // ... };
void CMFCApplication1Doc::ShiftArray()
{
    for (int i=0; i< m_size; i++)
    {
        m_tab[i] = m_tab[i+1];
    }
}

Of course there is a problem. It may not be caught by a code review anyway, especially if the code is not as simple as here. Code analysis finds the bug, and notices that valid indices for m_tab array range from 0 to 99, while index 100 is used.:

visual-studio-2012-code-analysis_thu

The code analysis window has a search field. Results can be filtered. Above, only the results for files whose names contain “doc” are displayed.

Here is another unsafe code sample:

void CMFCApplication1Doc::SetDelay(int delay)
{
    CDelayHolder *pObj = nullptr;
    bool isFound = false;
    for (int index = 0; index <10; index++)
    {
        if (index >= delay)
        {
            pObj = new CDelayHolder();
        }
        else {
            if (!isFound)
                isFound = WaitDelay(delay);
            else pObj->Wait(delay);
        }
    }
}

This code is a bit weird but I have seen even stranger code! Code analysis finds the problem and explains it. It occurs during the second time through the loop:

visual-studio-2012-code-analysis-loo

 

Buffer overrun, invalid pointers, memory leaks, and many other problems are sought in the code. Code analysis uses more than 200 rules to check code safety.

 

Customizing Code analysis

 

You can customize the rules of the code analyzer as needed. Create a new rule file (menu File/New File/Code Analysis rule set), and you can disable, enable, or treat certain rules as error or warning.

visual-studio-2012-code-analysis-lis

Code Analysis tab in the Project properties let you select a Code analysis rule set.

Code analysis behavior can also be customized in C++ code, using #pragma warning :

#pragma warning(suppress: 6011) #pragma warning (error: 6001) 

 

Semantic code annotation

 

It is very easy to get a diagnostic on code quality with Visual Studio 11 Code analysis. It is possible to get even more accurate diagnostics if you help the code parser to do its job. SAL (Source code Annotation Language) is a list of keywords you can add in your source code. SAL keywords are recognized by the Code analysis parser, to make the results more accurate.

SAL can be seen as semantic comments in code. They are used by the analyzer to make verifications. SAL keywords are defined in the file sal.h. This is the file you want to check if you want to know how it works.

_In_ and _Out_ are the most used SAL keywords, in a function declaration. For example:

void GetTextProperty(_In_ LPCTSTR szPropertyName,
                    _Out_ LPTSTR szOutBuffer,
                    _In_ int nBufferSize);

_In_ means the function will only read from the buffer. The caller must provide the buffer and initialize it.

_Out_ means the function will only write to the buffer. The caller must provide the buffer, and the function will initialize it.

Code analysis will check that szPropertyName is correctly initialized before the function is called.

Microsoft Windows development team uses SAL, and more than 3 millions SAL annotations have been added to Windows source code. Thus, Windows include files contain useful annotations for our applications. For example, GlobalLock Win32 function is defined in winbase.h:

_Ret_maybenull_ LPVOID WINAPI GlobalLock (_In_ HGLOBAL hMem); 

This is quite explicit: hMem parameter must be initialized before function call and must not be null, while the return value might be null.

Thus, Code analysis will display two messages with following sample code:

HGLOBAL hMem = nullptr;
char *p = (char *)GlobalLock(hMem);
*p = 'a';

visual-studio-2012-code-analysis-glo

The first message indicates that you must not pass a null pointer to GlobalLock. The second says that, since GlobalLock can return a null pointer, it is illegal to dereference it without testing if it is not null.

MFC code also uses SAL. For example, CWnd::GetWindowText is declared as follow:

int GetWindowText(_Out_writes_to_(nMaxCount, return + 1) LPTSTR lpszStrBuf,
    _In_ int nMaxCount) const;

So, Code analysis checks that lpszStrBuf is an array of at least nMaxCount bytes, and nMaxCount must not be null.

A buffer overrun problem is detected in this code sample:

TCHAR buffer[80];
AfxGetMainWnd()->GetWindowText(buffer, 100);

visual-studio-2012-code-analysis-get

 

Parallel code analysis

 

C++ Code analysis can help finding bugs in parallel code. More than 100 rules are particularly relevant on this topic.

One of the common problems of parallel code is avoiding concurrent access to a variable. An error at this level can cause a crash or an application dead lock.

Following code sample uses an integer count, protected by a critical section cs.

_Garded_by_ is a new SAL keyword indicating that a variable must be protected by a lock. Here, cs is the lock which avoids concurrent access on count. Code analysis can now detect any access to count which is not guarded by cs.

typedef struct {
    _Garded_by_(cs) int count;
    CRITICAL_SECTION cs;
}COUNT; 

bool CMFCApplication1Doc::UpdateCount(_In_ COUNT *p, _In_ int diff)
{
    EnterCriticalSection(&p->cs);
    if (p->count < diff)
        return false;
    p->count -= diff;
    LeaveCriticalSection(&p->cs);
    return !p->count;
}

Two problems are detected by Code analysis: a return inside the critical section, and an unprotected access to count at the end of the function:

visual-studio-2012-code-analysis-cri[2]

 

C++ Code analysis – conclusion

 

These last samples impress me. For decades, we were debugging code to find memory overflow in code! And these examples only demonstrate a part of the awesome power of the C++ Code analyzer of Visual Studio 2012. The analyzer will be included in all Visual Studio 2012 versions, including Visual C++ 2012 express, as explained in the Code Analysis talk at Build conference.

One of the conclusions we can draw is you must not use * pointers, operators new and delete anymore, but use C++11 smart pointers . If you are not using shared_ptr or unique_ptr  today, it is essential for you to get started. Smart pointers are really safer than C-style * pointers.

With the new features of C++11 and this new C++ Code analysis feature, it will be very difficult to write unsafe C++ code!