De lundi à mercredi prochain auront lieu les Techdays 2010 à Paris, l’évènement Microsoft France de l’année. J’aurai le plaisir d’y co-animer 3 sessions :

  • Model-View-ViewModel et testabilité pour WPF et Silverlight
    Lundi de 13h à 14h en compagnie de Richard Clark.

Cette session sera réalisée en WPF, mais tout ce que l’on va montrer reste valable avec Silverlight. Au niveau testabilite / isolation de composant, on abordera des cas typiques : Tests de composants asynchrones, isolation de la couche ViewModel par rapport à la couche Service, injection de dépendances…

  • Développement Microsoft Surface
    Lundi de 14h30 à 15h30 en compagnie de Cyril Giacopino

Cette session couvrira le SDK Surface 1.0 SP1, et présentera les diverses fonctionnalité au travers d’une application développée au sein d’Access it par Julien Corioland, moi-meme, et le résultat de la réflection de toute l’équipe parisienne autour de scénarios collaboratifs avec Surface et Team Foundation Server (et la participation d’Aude Mousset pour que ce soit un minimum joli…)

  • Workshop – Développement parallèle
    Mardi de 17h30 à 18h30 avec Patrice Lamarche

Une session dédiée aux nouveautés du Framework .Net 4.0 en terme de multi-threading, où vous pourrez vous essayer au différentes formes que peut prendre la parallélisation : Data Parallelism avec PLinq, Task Parallelism avec TPL, et pipelines d’agents avec la communication par messages… Une session très interactive, avec en cadeau des démo Bonux des nouveautés des outils de debug de VS 2010, et d’un outil de détection de Dead Lock sorti des labo de Microsoft Research : Chess.

 

Cette année, rassurez vous, je ne ferai pas de démo sous SOS devant un amphi de 400 personnes… C’est que des sessions avec des trucs faciles dedans !


Je viens de mettre à jour le projet CodePlex avec les dernières sources, et j’en ai profité pour en faire une Release publique.

Cette version inclue la manipulation par gestures et un moteur d’inertie, en plus des fonctionnalités présentées précédement, et de quelques BugFixes.

J’en ai bien sur aussi profité pour mettre à jour le Showroom.

 

A venir :

  • Une librairie d’algorithmes de layouts
  • Un portage WPF
  • Un portage Surface

 

Comme d’habitude, tout Feedback est le bienvenu !


Hier soir, en compagnie de Julien Corioland, j’ai animé un Live Meeting à propos de MEF (vous pouvez d’ailleurs le regarder / télécharger à cette adresse).

Comme promis, voici donc le code source de mes démos (allez voir le blog de Julien pour retrouver les siennes) : www.simonferquel.net/blog/5-AdminMenu.rar


Un des types de layouts particulièrement en vogue dans les applications RIA est le Coverflow (le nom provient d'un mode de navigation introduit par Apple dans iTunes, iPod et iPhone).

 

Depuis la sortie de Silverlight, de nombreux contrôles ont été publiés par la communauté mais aucun n'est parfait pour le moment à mes yeux. Après un peu de réflexion j'en suis donc venu à m'établir une Feature-list et de lancer un projet CodePlex. Les fonctionnalités que je souhaite implémenter sont les suivantes :

 

  • Scalabilité : le contrôle doit pouvoir contenir des milliers d’éléments sans broncher. Pour ceci je compte mettre en place de la Virtualisation de conteneurs
  • Data-Binding : le contrôle doit être un ItemsControl, accepter une ItemsSource et un ItemTemplate, et supporter le Binding TwoWay sur ses SelectedIndex et SelectedItem
  • Styling/Templating : Le contrôle doit être aisément personnalisable, exposer des VisualStates et fournir une expérience Design Time parfaite. De plus, il doit fournir un moyen à son ItemTemplate de connaitre l’état de sélection de l’élément courant
  • Extensible : Je n’ai jamais vu 2 projets ou le client voulait exactement le même algorithme de layout : plus ou moins d’éléments visibles, rotation des éléments différente, effet de perspective ou à plat… Le meilleur moyen de proposer quelque chose qui s’adapte vraiment à chaque cas, est d’externaliser la logique de layout et de proposer au développeur l’utilisant de fournir sa propre implémentation sans avoir à implémenter toutes les taches annexes (entrées utilisateur, logique de sélection, virtualisation…)
  • Expérience “Out of the Box” (sans style / layout particulier) suffisante pour prototyper des applications qui en jettent
  • Modèle de manipulations riche (sélection par clic, au clavier, par “panning”, touch…). Fournir une API de manipulation extensible (yet to be designed pour le moment, mais j’ai quelques idées a ce sujet)

Mes plans sont d’avoir une version stabilisée d’ici la RC de Silverlight 4 avec toutes les fonctionnalités décrites ci-dessus, en License MS-PL.

 

D’ici-là, je laisserai à disposition une page de demo régulièrement mise à jour avec les nouveautés apportées. Un projet Codeplex est aussi en préparation, je pousserai les sources dessus après avoir peaufine 2-3 choses…

 

image

 

image

 

Bien entendu, tout feedback sur le look-and-feel et proposition de fonctionnalité est fortement encouragé…

 

 

Simon

 

 

Update : Le projet est Codeplexifié à l’adresse suivante : http://slflow.codeplex.com/. La qualité du code est pas encore clean, ca nécessite un peu de refactoring, de testing et de doc pour attendre un niveau de qualité acceptable, mais vous pouvez commencer à jouer avec et à faire du Feedback :-).


Une des plus grandes sources de MemoryLeaks dans les applications .Net (en particulier dans le cas d’applications Windows Forms, WPF et Silverlight), sont la création d’event handlers sur des events exposés par des objets ayant un temps de vie long, ou sur des events statiques (typiquement un évènement de la classe Microsoft.Win32.SystemEvents, ou un évènement exposé par un singleton – oui je sais, les puristes de la POO qui vont lire cette phrase vont vomir).

 

La raison est très simple : quand on crée un délégué (EventHandler ou autre), celui-ci possède une référence forte sur l’objet qui prend en charge l’évènement. De plus, quand on ajoute un délégué à un évènement (typiquement MonObjetSource.MonEvent += monEventHandler), cet évènement a une référence forte au délégué. Du coup, tant que l’on ne se désabonne pas, l’objet prenant en charge l’évènement reste référencé, et si la source de l’évènement n’est pas elle-même déréférencée (ce qui n’est jamais le cas avec des events statiques par exemple), l’objet prenant en charge l’évènement ne sera jamais collecté par le Garbage Collector : et on a un jouli potentiel memory leak (les puristes appellent ca plutôt un Handle Leak, parce que la source du problème n’est pas un oublie de désallocation de mémoire, mais un oublie de déréférencement).

 

Le cas typique, c’est un composant qui s’abonne à un évènement système, mais qui ne se désabonne jamais. Potentiellement à cause d’un oubli d’appel à une méthode Dispose ou quelque chose dans le genre. Un autre cas courrant, c’est le controlleur (ou le ViewModel) d’une vue qui s’abonne à un évènement d’un objet métier à longue durée de vie, et qui ne s’y désabonne pas (en Silverlight, c’est d’ailleurs très compliqué de se désabonner d’un évènement quand un contrôle sort de l’arbre visuel, car on n’a pas d’évènement UnLoad).

 

Comment faire face à ce problème (pas super simple à debugger pour courroner le tout, souvent incompris par les développeurs novices, et encore plus souvent détecté par le client qui se plaint d’une consommation mémoire désastreuse, à qui souvent les développeurs disent “ca peut pas venir de nous, nous on gère pas la mémoire, c’est le Garbage Collector qui sait”, et qui du coup s’en va propager la nouvelle comme quoi franchement, les Applis .Net c’est super lourd, faut 40 go de RAM pour les faire tourner, et au bout de 16 heures d’activité, ca fait systématiquement une OutOfMemoryException) ? Il existe plusieurs possibilités (liste pas forcément exhaustive) :

 

  • Toujours, toujours, TOUJOURS se désabonner à un évènement auquel on s’est attaché, sauf pour :
    • Les évènements des contrôles si et seulement si ils sont pris en charge par le UserControl ou le formulaire qui les a créés (et qui les contient)
    • Si l’objet qui prend en charge l’évènement est lui même un singleton (oui je sais les puristes, le singleton c’est pas bon)
  • Utiliser le WeakEventManager / WeakEvents pattern (mais c’est pas disponible sous Silverlight, et en plus c’est compliqué)
  • Créer un objet WeakEventHandler qui servira de proxy entre l’évènement et sa cible (l’idée est de référencer la cible uniquement via une WeakReference).

 

Le problème de cette dernière solution (la seule possible dans certains cas avec Silverlight), c’est que la classe WeakEventHandler est compliquée à implémenter, et peut potentiellement cacher un nouveau leak. Je vous donne donc ici, mon implémentation de référence, ainsi qu’un code snippet permettant de générer des WeakEventHandlers spécifiques à un évènement donné. L’idée étant d’avoir au final une utilisation aussi simple que :

 

PropertyChangedWeakEventHandler.Initialize(this, EventSource,
    (target,s, a)=>target.Messages.Add(a.PropertyName+ " changed"));
 

La méthode initialize prend 3 paramètres : la cible de l’évènement (qui sera encapsulée dans une WeakReference), l’objet source de l’évènement PropertyChanged, et un délégués statiques (qui n’a donc pas de référence à l’objet courrant et ne provoquera pas de leak). Ce dernier paramètre est vérifié à l’exécution en regardant si sa propriété Target est non nulle. Le délégué obtiendra une référence à l’objet cible grace à son premier paramètre.

 

Le code derrière cette classe est assez complexe, il repose lui même sur une classe générique abstraite que j’utilisais précédement directement, mais qui demandait un peu plus de plomberie, et était du coup beaucoup plus susceptible de provoquer des bugs.

 

Du coup, je vous file juste les sources contenant tout ce qu’il faut pour prendre en charge les évènements PropertyChanged, ainsi qu’un code Snippet permettant de créer vos propres WeakEventHandlers spécifiques (pour CollectionChanged, SystemEvents etc.) sans avoir à comprendre comment fonctionne la classe de base (allez, petit spoiler quand même, elle a 4 paramètres génériques, et fait largement partie des bouts de codes les moins lisibles que j’ai peu produire) : SStuff.Events.zip.


C# 4.0 introduit une nouveauté très intéressante au niveau des déclarations d’interfaces / délégués génériques : la notion de Co-variance et Contra-variance.

La co-variance permet de dire que si un type implémente IMonInterface<TypeDérivé>, il implémente aussi IMonInterface<TypeParent>.

Pour la contra-variance, c’est exactement le contraire.

Du coup, les développeurs de la BCL ont modifié la déclaration de pas mal d’interfaces et délégués génériques pour profiter de ces règles : ainsi tout IEnumerable<string> implémente automatiquement IEnumerable<object> et tout délégué de type Action<object> peut être utilisé à la place d’un Action<string>.

 

En fait cette notion de co-variance / contra-variance, bien que non déclarable en C# et VB dans leur versions actuelle est présente dans les spécifications du langage MSIL depuis .Net 2.0, et si l’on en a le courage, on peut tout a fait créer des interfaces / délégués co/contra-variants. Pour ceci, il faut simplement indiquer un “+” (co-variant) ou un “-“ (contra-variant) devant le paramètre du type. Voici un exemple avec 2 interfaces définie en MSIL :

.assembly extern mscorlib
{
  .publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) 
  .ver 2:0:0:0
}
.assembly Cotypes
{
  .ver 0:0:0:0
}
.module Cotypes.dll


// +T indique que l'interface est CoVariante (toute implementation de IValueProvider<ChildClass>
// implemente IValueProvider<ParentClass>) .class interface public abstract auto ansi Cotypes.IValueProvider`1<+T> { .method public hidebysig newslot abstract virtual instance !T GetValue() cil managed { } } // -T indique que l'interface est ContraVariante (toute implementation de IValueConsumer<ParentClass>
// implemente IValueConsumer<ChildClass>) .class interface public abstract auto ansi Cotypes.IValueConsumer`1<-T> { .method public hidebysig newslot abstract virtual instance void SetValue(!T 'value') cil managed { } }

Une fois compilée sous forme de DLL, on peut alors implémenter ces interfaces en C# et profiter la co/contra-variance :

static void Main(string[] args)
{
    IValueProvider<string> stringProvider = new StringValueProvider("HelloWorld");
    IValueProvider<object> objProvider = stringProvider as IValueProvider<object>;

    IValueConsumer<object> objConsumer = new ConsoleDisplayer();
    IValueConsumer<string> stringConsumer = objConsumer as IValueConsumer<string>;

    objConsumer.SetValue(objProvider.GetValue());
    stringConsumer.SetValue(stringProvider.GetValue());
}

En C# 4.0, la seule différence (en dehors du fait que l’écriture de telles interfaces peut se faire directement en C#) est qu’il n’y a pas besoin du mot-clef as, l’opération de cast se fait implicitement.

Vous pouvez télécharger les sources de cet exemple ici.


Lorsque l’on développe des applications avec WPF ou Silverlight, une des tâches les plus répétitives et les plus propices aux erreurs est l’implémentation de INotifyPropertyChanged et la génération d’évènements lors de l’affectation d’une propriété sur nos objets source de DataBinding. Voici par exemple une classe Customer implémentant INotifyPropertyChanged d’une manière classique :

public class Customer : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }

    private string _firstName;
    public string FirstName
    {
        get { return _firstName; }
        set
        {
            if (_firstName != value)
            {
                _firstName = value;
                OnPropertyChanged("FirstName");
            }
        }
    }

    private string _lastName;
    public string LastName
    {
        get { return _lastName; }
        set
        {
            if (_lastName != value)
            {
                _lastName = value;
                OnPropertyChanged("LastName");
            }
        }
    }
}

Ce code illustre très bien deux problèmes :

  • C’est très verbeux
  • Une faute de frappe dans la chaine de caractères passée à la méthode OnPropertyChanged est vite arrivée (ca m’est d’ailleurs arrivé pendant l’écriture de cet article, car je switch pas mal entre clavier Azerty et Qwerty)

Je reviendrai un peu plus tard sur le premier problème, pour l’instant nous allons nous concentrer sur le second qui est pour moi le plus critique. En plus du risque de faute de frappe, il expose aussi le développeur à des erreurs possibles lors d’un potentiel Refactoring. Pour rendre ce code plus sûr et vérifié à la compilation, il existe plusieurs alternatives :

  • Utiliser un composant ObservableObject (voir http://compositewpf.codeplex.com/) qui encapsule l’implémentation d’INotifyPropertyChanged via une classe séparée : cette solution est séduisante à première vue mais conduit à un nombre peu commode d’indirections lorsque l’on veut accéder à une valeur (monCustomer.FirstName=”toto” devient monCustomer.FirstName.Value = “toto”)
  • Utiliser la reflection pour récupérer le nom de la propriété courrante :
    public class Customer : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        protected void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    
        private string _firstName;
        public string FirstName
        {
            get { return _firstName; }
            set
            {
                if (_firstName != value)
                {
                    _firstName = value;
                   
                    OnPropertyChanged( MethodInfo.GetCurrentMethod()
                        .Name.Substring(4));
                }
            }
        }
    
        private string _lastName;
        public string LastName
        {
            get { return _lastName; }
            set
            {
                if (_lastName != value)
                {
                    _lastName = value;
                    OnPropertyChanged( MethodInfo.GetCurrentMethod()
                        .Name.Substring(4));
                }
            }
        }
    }
    Cette solution est originale, mais faire appel aux API d’introspection à chaque affectation de propriété a un impact très négatif sur les performances
  • Ma solution préférée : passer par des arbres d’expression Linq en modifiant légèrement la signature de OnPropertyChanged :
using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Linq;
using System.ComponentModel;
using System.Reflection;
using System.Linq.Expressions;

namespace SStuff.NotifySample
{
    public class Customer : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        protected void OnPropertyChanged<T>(Expression<Func<T>> propAccess)
        {
            if (PropertyChanged != null)
            {
                var asMember = propAccess.Body as MemberExpression;
                if (asMember == null)
                    return;

                PropertyChanged(this, new PropertyChangedEventArgs(asMember.Member.Name));
            }
        }

        private string _firstName;
        public string FirstName
        {
            get { return _firstName; }
            set
            {
                if (_firstName != value)
                {
                    _firstName = value;
                   
                    OnPropertyChanged( ()=>FirstName);
                }
            }
        }

        private string _lastName;
        public string LastName
        {
            get { return _lastName; }
            set
            {
                if (_lastName != value)
                {
                    _lastName = value;
                    OnPropertyChanged( ()=> LastName);
                }
            }
        }
    }
}

Cette solution a l’avantage d’être TypeSafe, relativement concise et plus lisible que la version basée sur la Reflection, et l’impact du parcours d’arbre d’expression sur les performances est censé être relativement réduit.

Cela dit, toutes ces solutions ont en commun le fait de ne pas résoudre mon premier problème : ce code est très verbeux. A l’occasion des derniers Mecredis du Developpement (du jeudi) j’ai imaginé une solution qui me parait bien plus élégante : depuis quelques temps j’aborde tous mes développements d’applications WPF / Silverlight en utilisant le framework d’injection de dépendances Unity. Cela me permet entre autres d’isoler mes différents ViewModel afin de pouvoir les tester unitairement, et de créer des implémentations additionnelles spécifiques au DesignTime. En combinant injection de dépendance et création de types dynamiques (en utilisant Reflection.Emit), on peut ainsi retarder l’implémentation d’INotifyPropertyChanged à l’exécution ! Avec bien sûr un surcoup au démarrage (le temps de générer l’IL correspondant aux types dynamiques) qui est compensé ensuite par des performances tout à fait satisfaisantes, et une beaucoup plus grande pureté du code :

public class Customer
{
    [Notify]
    public virtual string FirstName { get; set; }
    [Notify]
    public virtual string LastName { get; set; }
}

Pour enregistrer ensuite le type dans Unity, le code est ensuite ultra-simple (grâce aux méthodes d’extension):

container
    .RegisterType(typeof(Customer), 
                  typeof(Customer).AsNotify(null));

La méthode d’extension AsNotify génère un type hérité de Customer dynamiquement implémentant INotifyPropertyChanged correctement. Le paramètre optionel permet de fournir un MethodInfo au cas où une méthode OnPropertyChanged existerait déjà sur le type de base.

Pour créer une instance de mon Customer dynamique, je passe alors pars Unity : container.Resolve<Customer>();

Pour permettre des scénarios assez avancés avec l’injection de dépendance, tous les constructeurs de la classe de base sont reproduit dans le type dynamique, ainsi cette méthode s’intègre très bien dans une architecture MVVM avec injection de dépendances.

Le code du NotifyTypeBuilder ainsi qu’un exemple MVVM avec Unity sont récupérable ici.


Le titre de ce post peut paraitre un peu obscur, mais il fait référence à une technique expliquée par Joe Duffy dans son livre Concurrent Programming on Windows (pour info, le monsieur est Lead developper et architecte sur PFX, il envoit sacrément du pâté et son bouquin est un must-read absolu pour tout développeur souhaitant garder son job dans les 5 ans à venir).

 

Un objet privatisable répond astucieusement à la problématique bien connue dans le développement de solutions multi-thread qu’est le partage d’un état en lecture-écriture. Le partage d’état connait deux contraintes importantes :

  • On doit se protéger des Race-conditions (multiples load-store en parallèle)
  • Les modifications d’états composites doivent apparaître de manière atomique (l’objet doit apparaitre dans un état valide et cohérent à tout moment)

Une solution très simple à cette problématique est l’utilisation d’un Mutex ou Monitor (le monitor étant une version allégée du Mutex, nécessitant moins de switch user-mode / kernel-mode):

 

public class Exemple
{
    private MySharedState _sharedState = new MySharedState();

    public void FirstThread()
    {
        // make some long-running task

        // get the exclusive access on _sharedState
        lock (_sharedState)
        {
            // read-write access with mutual exclusion
        }
    }

    public void SecondThread()
    {
        // make some long-running task

        // get the exclusive access on _sharedState
        lock (_sharedState)
        {
            // read-write access with mutual exclusion
        }
    }
}

 

Cette solution bien que fonctionnelle, cette solution pose d’importants problèmes :

  • Si un développeur “oublie” de poser un lock lors d’une lecture-écriture, on peut se retrouver avec un bug de type Race Condition
  • Si un développeur ne pose pas de lock sur un bloc contenant plusieurs lectures de propriétés différentes, il y a un risque d’incohérence (un autre thread aura pu modifier l’état de l’objet entre 2 reads)
  • Un seul thread ne peut entrer dans le lock à un moment T, même si tous les threads concurrents n’ont besoin que d’un accès en lecture (ce qui peut poser un gros problème de performance si la majeur partie des accès se font en lecture seule).

 

Un objet privatisable pallie à tous ces problèmes. L’idée est que l’état partagé est accessible en lecture seule par défaut, et pour pouvoir le modifier, il faut d’abord le “privatiser” (si on essaie de modifier une propriété sans privatiser l’objet auparavant, on reçoit une exception). Privatiser un objet revient à poser un verrou exclusif en lecture-écriture sur l’objet pour le thread courant. Un seul thread peut privatiser l’objet à chaque instant, et pendant toute la durée de la privatisation, les accès à l’objet (en écriture comme en lecture) sont bloqués (grâce à un ReaderWriterLockSlim). Une fois les modifications terminées, le thread “owner” publie l’objet qui redevient accessible en lecture seule pour tout le monde jusqu’à la prochaine privatisation. De plus, plusieurs threads peuvent accéder simultanément en lecture-seule à l’objet, pour éviter des problèmes de performance.

 

Les lectures concurrentes ne s’entre-bloquent pas, les “états intermédiaires” ne sont visibles que par le thread responsable du changement d’état, et les développeurs n’ont pas besoin de poser un “lock” manuellement.

 

Bien sûr, la création d’un tel objet n’est pas forcément super simple, surtout si en plus de ca, l’objet est une source de data-binding dans un client riche (il faut alors retarder les évènements PropertyChanged / CollectionChanged afin que le thread GUI ne soit pas bloqué trop longtemps sur une ré-évaluation de propriété). J’ai donc créé 3 classes réutilisables simplement pour créer des objets privatizables :

  • PrivatizableObject : classe abstraite fournissant le comportement de base, implémentant INotifyPropertyChanged avec retardement, et permettant la privatisation de graphes d’objets complexes
  • PrivatizableList<T> : dérive de PrivatizableObject et implémentant IList<T>
  • PrivatizableObservableCollection<T> : dérive de PrivatizableList<T> et implémente INotifyCollectionChanged avec retardement

Pour créer un PrivatizableObject personnalisé, il suffit alors de dériver de PrivatizableObject, et d’utiliser la syntaxe suivante pour déclarer une propriété :

private class TestablePrivatizableObject : PrivatizableObject
{
    private string _testProperty;
    public string TestProperty
    {
        get
        {
            base.BeginRead();
            string value = _testProperty;
            base.EndRead();
            return value;
        }
        set
        {
            MarkPropertyChanged("TestProperty");
            _testProperty = value;
        }
    }

    public TestablePrivatizableObject(bool privatize) 
        : base(privatize)
    { }

    public TestablePrivatizableObject(PrivatizableObject privatizationController)
        : base(privatizationController) { }
}
 

Comme vous pouvez le voir, l’implémentation n’a plus grand chose de compliqué !

 

A l’utilisation, là aussi rien de bien méchant :

 

Accéder à une propriété :

string value = myObject.TestProperty;

 

Modifier l’état partagé:

using (myObject.Privatize())
{
    myObject.TestProperty = "test";
myObject.TestProperty2 = "test2"; } // a la fin du bloc, l’objet est automatiquement publié

 

Effectuer une série de “reads” atomique :

using (testee.CreateReadRegion())
{
value1 = myObject.TestProperty;
value2 = myObject.TestProperty2; }

 

Cette solution, bien que fournie avec une batterie de tests unitaires, est fournie “as-is”, mais est totalement libre de droits :

Téléchargez la solution


Avant-hier vient de s’achever une page de ma vie professionnelle importante : mon job (chez Winwise) vient de se terminer. J’ai donc désormais pas mal de temps libre, et quelques articles techniques ne devraient pas tarder à voir le jour sur ce blog (notamment des choses autour du développement parallèle, et sûrement une série sur DirectX 10, maintenant que j’ai montré comment faire interagir WPF avec cette technologie).

Je profite aussi de ce post pour vous rappeler que mes bons services sont à louer… Donc si vous avec des besoins particuliers en conseil ou développement custom autour des technologies .Net (et plus particulièrement C# 3.0 / Linq, WPF, Silverlight, WCF, Visual Studio SDK, DirectX, C++ / CLI ou développement parallèle), n’hésitez pas à me contacter via ce blog.


Depuis la version 3.5 SP1 du Framework .Net, il est possible en WPF de consommer une surface rendue à l’aide de DirectX 9 (grâce à XNA, Managed DirectX ou en C++ / CLI). Malheureusement, de base, WPF ne supporte pas les textures produites par DirectX 10. D’autres solutions existent, comme par exemple l’utilisation de SlimDX, mais jusqu’à maintenant, elles passaient toutes par de l’interopérabilité Win32, et ne permettaient donc pas d’intégrer le contenu DX10 au moteur d’animations / transformations de WPF.

 

Le but de cet article est de montrer une technique permettant sous Windows VISTA, 7 et à priori supérieur d’injecter une surface rendue en DirectX 10 dans le moteur de composition de WPF.

Un nain rendu en DirectX10 intégré à WPF
Un nain rendu en DirectX10 / HLSL4 et mélangé à du contenu WPF

 

Le principe

Le principe est d’exploiter les fonctionnalités apportées par Windows VISTA et son système WDDM (Windows Display Driver Model) à DirectX 9 et plus particulièrement le système de partage de ressources, qui permet de partager des objets en mémoire sur le GPU entre plusieurs devices DirectX. On va donc créer à partir d’un Device DirectX 9 Ex (DirectX 9 Ex est la version de DirectX9 adaptée au WDDM de VISTA / 7) une texture que l’on va partager avec un Device DirectX10, effectuer un rendu dedans à l’aide de DirectX 10, et la présenter à WPF grâce à DirectX 9. Les performances sont assurées grâce au fait que le partage de ressources tel qu’implémenté dans Windows VISTA / 7 n’implique pas de recopie en mémoire système : la texture ne quitte jamais la mémoire de la carte graphique, de son rendu en DirectX10 jusqu’à son exploitation par le moteur de composition de WPF (basé lui-même sur DirectX 9 Ex).

 

Les obstacles

L’obstacle le plus gênant a été de trouver de la documentation sur le Resource Sharing entre DirectX 9 et DirectX 10. Il y’a effectivement pas mal de contraintes, et les formats de textures supportés à la fois par DirectX9 et DirectX 10 ne sont pas légions… Et aucun d’entre eux n’est compatible avec WPF !

 

Il a donc fallu passer par une technique de conversion bien connue des développeurs Direct3D (et dont les performances sont impressionnantes par rapport à ce qu’on obtiendrait en faisant ca sur le CPU) baptisée empiriquement la technique des 2 gros triangles. Il s’agit simplement de créer une texture DirectX 9 compatible WPF, et d’effectuer dedans le rendu de 2 polygones recouvrant l’intégralité du ViewPort, auxquels on applique la texture non supportée (pour des performances optimales, on désactive le plus de traitements possibles, notamment la rastérisation, l’éclairage, le Z-Buffer…).

 

La réalisation

La réalisation ensuite est relativement simple (pour peu que l’on connaisse un minimum WPF, C++ / CLI et les API DirectX).

 

On se retrouve effectivement avec une classe C++ Native dont le but est de créer les Devices DirectX 9 EX et DirectX 10, leurs ressources associées et d’effectuer le rendu, une classe C++ / CLI servant de proxy à la classe de rendu, et un contrôle WPF récupérant le handle de surface DirectX 9 Ex et l’affichant grâce au composant D3DImage (faites une recherche sur votre moteur de recherche préféré sur D3DImage, y’a pas mal de documentation sur le sujet). Dans l’exemple illustrant cet article, le composant WPF envoie les paramètres de rendu à la classe proxy qui appelle la classe de rendu qui charge alors les fichiers de modèles, les shaders et quelques paramètres associés.

 

Voici quelques morceaux de choix du code de l’exemple :

 

Création des textures DirectX 9 (NativeRenderer.cpp) :

 

// Creating DX9 textures 

HRESULT hr = m_deviceEx->CreateTexture(width,
    height,
    1,
    D3DUSAGE_RENDERTARGET,
    D3DFMT_A16B16G16R16F, // Required for DirectX10 Interop
    // but does not work with WPF D3DImage. So we need to copy into an other 
    // D3D9 texture with correct WPF format. For this we simply render a rectangle
    D3DPOOL_DEFAULT, 
    &m_renderTexture,
    &shareHandle);
assert(SUCCEEDED(hr));

// This texture will be passed to WPF D3DImage
hr = m_deviceEx->CreateTexture(width,
    height,
    1,
    D3DUSAGE_RENDERTARGET,
    D3DFMT_A8R8G8B8,
    D3DPOOL_DEFAULT,
    &m_renderTextureCopy,
    NULL);
assert(SUCCEEDED(hr));

 

 

Ouverture de la texture partagée par le device DirectX 10

 

// Now we open the shared texture from the DX10 Device
ID3D10Resource * pTempResource;
hr = m_device10->OpenSharedResource(shareHandle, __uuidof(ID3D10Resource), (void**)&pTempResource);
assert(SUCCEEDED(hr));
hr = pTempResource->QueryInterface(__uuidof(ID3D10Texture2D), (void**)&m_renderTexture10);
assert(SUCCEEDED(hr));
SAFE_RELEASE(pTempResource);

// And we create a Render Target View from it
hr = m_device10->CreateRenderTargetView(m_renderTexture10, NULL, &m_rtv);
assert(SUCCEEDED(hr));

 

 

Configuration du pipeline DX9 pour la conversion de texture à l’aide des 2 gros triangles

 

// The texture in which DX10 content is drawn is set as an input Texture
// in the DX9 device
hr = m_deviceEx->SetTexture(0, m_renderTexture);
assert(SUCCEEDED(hr));


// And the output is set to the texture which has a WPF compatible format
hr = m_renderTextureCopy->GetSurfaceLevel(0, &m_renderTarget);
assert(SUCCEEDED(hr));

hr = m_deviceEx->SetRenderTarget(0, m_renderTarget);
assert(SUCCEEDED(hr));
// This is the rectangle that will be used for the texture conversion 
// rendering pass
m_quad[0].x = 0;
m_quad[0].y = 0;
m_quad[0].z = 0.5;
m_quad[0].rhw = 1.0;
m_quad[0].u = 0;
m_quad[0].v = 0;

m_quad[1].x = (float)width;
m_quad[1].y = 0;
m_quad[1].z = 0.5;
m_quad[1].rhw = 1.0;
m_quad[1].u = 1;
m_quad[1].v = 0;

m_quad[2].x = 0;
m_quad[2].y = (float)height;
m_quad[2].z = 0.5;
m_quad[2].rhw = 1.0;
m_quad[2].u = 0;
m_quad[2].v = 1;

m_quad[3].x = (float)width;
m_quad[3].y = (float)height;
m_quad[3].z = 0.5;
m_quad[3].rhw = 1.0;
m_quad[3].u = 1;
m_quad[3].v = 1;

IDirect3DVertexBuffer9* vertBuffer;
hr = m_deviceEx->CreateVertexBuffer(4*sizeof(QuadVertex), 
D3DUSAGE_WRITEONLY,
QUAD_FORMAT,
D3DPOOL_DEFAULT, &vertBuffer,NULL); assert(SUCCEEDED(hr)); void* bufferData = NULL; hr = vertBuffer->Lock(0,0, &bufferData, 0); memcpy(bufferData, (void*)m_quad, 4*sizeof(QuadVertex)); vertBuffer->Unlock(); hr = m_deviceEx->SetStreamSource(0, vertBuffer, 0, sizeof(QuadVertex)); assert(SUCCEEDED(hr)); SAFE_RELEASE(vertBuffer);
 

Rendu (effectué sur l’évènement Rendering du CompositionTarget) :

 

HRESULT hr;

if(m_rtv == NULL)
    return;

// Send the transformation matrices to the GPU
ApplyMatrices();

// Update the BumpForce shader variable
m_fBumpForceVariable->SetFloat(m_bumpForce);

// Update the fTime shader variable
float time = (float)m_stopWatch.GetElapsedSeconds();
m_fTimeVariable->SetFloat(time);

// Clear the Depth Buffer
m_device10->ClearDepthStencilView(m_dsv, D3D10_CLEAR_DEPTH, 1.0,0);

// Clear the render target
float clearColor[] = {0.0,0.0,0.0,.0};
m_device10->ClearRenderTargetView(m_rtv, clearColor);    

// Render the mesh
if(m_meshManager != NULL)
{
    ID3DX10Mesh *mesh = m_meshManager->GetMesh();
    
    ID3D10InputLayout * inputLayout = m_meshManager->GetInputLayout();
    m_device10->IASetInputLayout(inputLayout);

    DWORD partCount = m_meshManager->GetMaterialCount();
    D3D10_TECHNIQUE_DESC td;

    for(DWORD i = 0;i<partCount;i++)
    {
        const MATERIAL10* mat = m_meshManager->GetMaterial(i);
        m_txDiffuseVariable->SetResource(mat->diffuseMap);
        m_txBumpVariable->SetResource(mat->bumpMap);
        m_renderTechnique->GetDesc(&td);
        m_hasEffect9Variable->SetBool((BOOL)mat->hasEffect9 && m_activateSpecularLighting);
        for (DWORD p = 0; p < td.Passes;p++)
        {
            ID3D10EffectPass *pass = m_renderTechnique->GetPassByIndex(p);
            pass->Apply(0);
            HRESULT hr;
            hr = mesh->DrawSubset(i);
            
            assert(SUCCEEDED(hr));
        }
    }
}

// Flush the generated content
m_device10->Flush();



// Begin the DX9 pass
m_deviceEx->BeginScene();

// Clear the WPF Render Target
hr = m_deviceEx->Clear(0,NULL,D3DCLEAR_TARGET, D3DCOLOR_ARGB(0,0,0,0), 1.0,0);

// Draw the 2 big triangles
hr = m_deviceEx->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);

// End the DX9 pass
m_deviceEx->EndScene();
hr = m_deviceEx->Present(NULL,NULL,NULL,NULL);

 

 

Si ce code ne vous fait pas peur, à priori je n’ai pas besoin de vous mettre en évidence le code de la classe proxy ni celle du contrôle WPF. Vous pouvez retrouver l’intégralité du code (et des assets graphiques tirés des samples du SDK DirectX) ici.

Les pré-requis sont :

  • VS 2008 SP1 (avec support de C# et C++)
  • Windows Vista ou 7
  • Une carte 3D DirectX 10
  • Le SDK DirectX de mars 2009
  • Les C++ Directories bien configurés dans Visual Studio pour pouvoir accéder aux includes / libs du sdk DirectX

 

Conclusion

 

Cette technique ouvre la porte à de nouveaux scénarios d’intéraction avec du contenu DirectX. On peut imaginer des outils de production 3D, des gestionnaires d’assets pour des éditeurs de jeu vidéo, des environements de développement de hlsl, des interfaces de simulation en 3D… Le tout en obtenant à la fois les avantages de la productivité apportée par WPF, et les performances et les possibilités en terme de rendu 3D de DirectX 10 (et apriori, la même chose sera possible avec DirectX 11 !)