Asynchronisme et Scenarios Hybrides avec le nouveau Runtime Windows (WinRT)

Avec la nouvelle mouture de Windows, il est possible de développer des applications de manière traditionnelles, mais également des applications dites au Style Metro, pour en savoir plus je ne peux que vous encourager à aller voir le centre de développement Windows.

Avant de poursuivre cet article et pour bien comprendre ce que peut être un scénario hybride dans le contexte d’une application Metro, nous avons besoins d’appréhender plusieurs notions :

  1. Le Runtime Windows ou WinRT, sont des API natives à la plate-forme sur lesquelles vont s’appuyer les applications Metro.
  2. La majeure partie des APIs de la WinRT, sont asynchrones.
  3. Il est possible de développer des applications au style Metro, avec le langage et l’interface de son choix.
    1. HTML5/CCS3 et JavaScript
    2. C#/VB .NET et XAML
    3. C++ et XAML.
  4. Il est possible de mixer les langages dans un même projet, et plusieurs scénarios hybrides sont possibles
    1. Composant C++->C#/VB XAML
    2. Composant C++->HTML 5/JavaScript
    3. Composant C#/VB.NET ->C++/XAML
    4. Composant C#/VB.NET ->HTML 5/JavaScript
  5.       
  6. Pour que ces scénarios soient possibles, WinRT définie un format de Métadonnées inclus dans un fichier avec extension WinMD , et qui sont exposées via une projection qui permet de faire la “transition” entre les différents langages.

Nous allons nous concentrer sur un scénario très simple, ou le projet est développé, pour la partie couche de présentation en HTML 5/JavaScript, qui appelle des composants développés en C++ et ou en C#.

Mais avant d’aller plus loin,  nous pourrions nous poser la question légitime,  pourquoi mettre en place ce type de scénarios Hybrides ?

De mon point de vue :

  • Vous avez des composants, qui sont testés et éprouvés depuis longtemps.
  • Des composants qui pallient à un manque de la WinRT et qui ne serait pas possible d’implémenter en JavaScript.
  • Vous souhaitez utiliser les possibilités intrinsèque de chaque langage, comme par exemple, les librairies parallèles disponibles en C++ (PPL) ou TPL pour C#/VB.NET pour des raisons de performances par exemple.

      

Tout est asynchrone avec la WinRT

La WinRT a été conçue, pour que la majeure partie des API s’exécutent en mode asynchrone. La nomenclature de ces API est simple, on commence par le nom de l’API finie par le suffixe Async

Exemple :

CaptureFileAsync(), FilePickerAsync(), OpenFileAsync(), LoadFromUriAsync(), GetTextAsync(), etc…

Chaque opération asynchrone, est définie au cœur de la WinRT, par 4 interfaces disponibles dans l’espace de nom Windows.Foundation.

  •  IAsyncOperation, IAsyncOperationWithProgress, lorsque l’opération asynchrone retourne un résultat et report l’état d’avancement de l’opération en cours.
  •  IAsyncAction et IAsyncActionWithProgress, lorsqu’elle ne retourne pas de résultat excepté l’état d’avancement de la dite opération.

Ces quatre interfaces possèdent les propriétés Completed et Progress (selon les cas) sur lesquels vous pouvez définir un délégué qui sera appelé lorsque l’opération sera terminée ou en cours d’état d’avancement. Elles possèdent également entre autre les méthodes Cancel et Close (dérivées de l’interface IAsyncInfo)

Consommer les méthodes asynchrones

Pour consommer ce type d’API, plusieurs solutions s’offrent à nous.

La version dite manuelle en utilisant les interfaces et en y accrochant un délégué sur la propriété Completed et Progress

C++

Code Snippet

  1. CameraCaptureUI camera;
  2. IAsyncOperation<StorageFile^>^ captureOperation=camera.CaptureFileAsync(CameraCaptureUIMode::Photo);
  3.     
  4. captureOperation->Completed=ref new AsyncOperationCompletedHandler<StorageFile^>([this](IAsyncOperation<StorageFile^>^ operationFile,AsyncStatus statusFile)
  5. {
  6.     StorageFile^ file=operationFile->GetResults();
  7.     IAsyncOperation<IRandomAccessStream^>^ openOperation= file->OpenAsync(FileAccessMode::Read);
  8.     openOperation->Completed=ref new AsyncOperationCompletedHandler<IRandomAccessStream^>([this](IAsyncOperation<IRandomAccessStream^>^ operationStream,AsyncStatus statusStream)
  9.     {
  10.         IRandomAccessStream^ rndStream=operationStream->GetResults();
  11.         this->Dispatcher->InvokeAsync(CoreDispatcherPriority::Low,
  12.             ref new InvokedHandler([rndStream,this](Object^ TSender, InvokedHandlerArgs^ args)
  13.             {
  14.                 BitmapImage^ bmp=ref new BitmapImage();
  15.                 bmp->SetSource(rndStream);
  16.                 imgCapture->Source=bmp;
  17.             }),this,nullptr);
  18.     });
  19. });

C#

Code Snippet

  1. CameraCaptureUI camera = new CameraCaptureUI();
  2.                 IAsyncOperation<StorageFile> captureOperation = camera.CaptureFileAsync(CameraCaptureUIMode.Photo);
  3.                 captureOperation.Completed = new AsyncOperationCompletedHandler<StorageFile>((IAsyncOperation<StorageFile> operationFile, AsyncStatus statusFile) =>
  4.                     {                        
  5.                         StorageFile photoFile = operationFile.GetResults();
  6.                         BitmapImage bmp = new BitmapImage(new Uri(photoFile.Path));                                        
  7.                         imgCapture.Source = bmp;                                                            
  8.                     });

 

Soit en utilisant des commodités apportées par les langages, comme le modèle async\await de C# ou la librairie PPL de C++

    C++ utilisation de l’objet task pour encapsuler les appels asynchrones  

Code Snippet

  1. CameraCaptureUI camera;
  2.     task<StorageFile^> captureTask(camera.CaptureFileAsync(CameraCaptureUIMode::Photo));
  3.     captureTask.then([this](StorageFile^ file)->IAsyncOperation<IRandomAccessStream^>^
  4.     {
  5.         BitmapImage^ bmp=ref new BitmapImage(ref new Uri(file->Path);        
  6.         imgCapture->Source=bmp;
  7.  
  8.     });

C# utilisation du modèle asynchrone

Code Snippet

  1. private async void Button_Click_2(object sender, RoutedEventArgs e)
  2.         {
  3.             CameraCaptureUI camera = new CameraCaptureUI();
  4.             var captureFile = await camera.CaptureFileAsync(CameraCaptureUIMode.Photo);
  5.             BitmapImage bmp = new BitmapImage(new Uri(captureFile.Path));
  6.             imgCapture.Source = bmp;                          
  7.         }

On y notera dans les deux solutions, une notation plus simple à écrire et plus compréhensible.

Dans notre exemple, ce que nous souhaitons, c’est créer un projet HTML /JavaScript qui consomme un composant WinRT développé en C++ et ou en C#  

Composant très simple qui devra :

  • Gérer l’interface IAsyncActionWithProgress<TProgress>
  • Remonter son état d’avancement à l’appelant

Rien de bien compliqué afin de comprendre les différents mécanismes d’implémentations

 

Création d’un composant WinRT et du fichier de métadonnées WinMD

En C++

Il suffit de choisir le modèle WinRT Component DLL comme illustré sur la figure suivante :

image

Le fichier WinMD, sera créé automatiquement lors de la compilation et qui sera à ajouter comme une nouvelle référence avec les autres langages.

Une fois le projet crée, dans le fichier d’entête, il suffit de déclarer la fonction GetMagicNumber() qui retourne l’interface

IAsyncActionWithProgress<int>. Nous retournons l’état d’avancement dans un int.

Code Snippet

  1. #pragma once
  2. #include <ppltasks.h>
  3. #include <thread>
  4. #include <chrono>
  5. using namespace Concurrency;
  6. using namespace std;
  7. using namespace Windows::Foundation;
  8.  
  9. namespace CppComponent
  10. {
  11.     public ref class WinRTComponent sealed
  12.     {
  13.     public:
  14.         WinRTComponent();
  15.         IAsyncActionWithProgress<int>^ GetMagicNumberAsync();
  16.     private:
  17.         
  18.     };
  19. }

A noter ici que nous incluons le fichier d’entête ppltasks.h afin d’utiliser la méthode create_async et la classe progress_reporter, tous les deux définies dans l’espace de nom Concurrency.

Code Snippet

  1. IAsyncActionWithProgress<int>^ WinRTComponent::GetMagicNumberAsync()
  2. {
  3.     return create_async([=](progress_reporter<int> progress)
  4.     {
  5.         for(int i=0;i<43;i++)
  6.         {
  7.             std::this_thread::sleep_for(chrono::milliseconds(100));
  8.             progress.report(i);
  9.         }
  10.     });
  11. }

Ici create_async est utilisée en conjonction avec une lambda expression qui définira le type d’interface retournée, en l’occurrence ici un IAsynActionWithProgress<int>. A noter enfin que nous simulons une charge de 100 millisecondes et que nous retournons l’état de la progression de la boucle.

Remarque : Pour l’implémentation des autres interfaces, c’est la même chose.

Dans cet exemple la lambda ne prend aucun paramètre et ne retourne rien, l’interface pris en compte est IAsyncAction

Code Snippet

  1. IAsyncAction^ WinRTComponent::ExecuteUneMethodeAsync()
  2. {
  3.     return create_async([]()
  4.     {
  5.         //
  6.     });
  7. }

La lambda ne prend aucun paramètre mais retourne un int, l’interface retournée par la méthode sera de type IAsyncOperation<int>

Code Snippet

  1. IAsynOperation<int> WinRTComponent::ExecuteUneMethodeAsync()
  2. {
  3.     return create_async([]()
  4.     {
  5.         return 42;
  6.     });
  7. }

Pour la dernière interface, IAsyncOperationWithProgress<float,int> , la lambda prend un paramètre de type progress_report<int> et retourne un float,

Code Snippet

  1.  
  2. IAsynOperationWithProgress<float, int> WinRTComponent::ExecuteUneMethodeAsync()
  3. {
  4.     return create_async([](progress_reporter<int> progress)
  5.     {
  6.         for (i=0,i<43,i++)
  7.         {
  8.                 progress.report(i);
  9.         }
  10.         return 42.0f;
  11.     });
  12. }

    

En C# (c’est la même chose en VB.NET)

Il suffit de créer un projet de type Class Library comme illustré sur la figure suivante :

image

Par contre, à la différence de C++,

  1. il faudra indiquer au compilateur de créer le fichier WinMD, comme illustré ici :

    image
    Ceci a pour effet de créer le fichier WinMD que nous pourrons ajouter en tant que référence, afin de pouvoir utiliser le composant avec les autres langages.

  2. De marquer la classe en tant que scellée (sealed C#, NotInheritable en VB.NET), de toute manière le compilateur vous l’indiquera.

Maintenant nous allons créer notre méthode GetMagicNumber() qui retournera également l’interface IAsyncActionWithProgress<int>. Comme illustré dans le listing suivant :

Code Snippet

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading.Tasks;
  6. using System.Threading;
  7. using Windows.Foundation;
  8. using System.Runtime.InteropServices.WindowsRuntime;
  9.  
  10. namespace CsComponent
  11. {
  12.     public sealed class Class1
  13.     {
  14.                                       
  15.         public IAsyncActionWithProgress<int>  GetMagicNumberAsync()
  16.         {                        
  17.             return AsyncInfo.Run<int>((CancellationToken token, IProgress<int> progress) =>
  18.                 {                                        
  19.                     return  Task.Run(async () =>
  20.                           {
  21.                              for (int i = 0; i < 43; i++)
  22.                              {
  23.                                  
  24.                                  token.ThrowIfCancellationRequested();
  25.                                  //Simule une charge
  26.                                  await Task.Delay(150);                                 
  27.                                  progress.Report(i);
  28.                              }                             
  29.                          });        
  30.                 });
  31.             
  32.         }        
  33.         
  34.     }
  35. }

    

Nous utilisons l’espace de nom System.Runtime.Interopservices.WindowsRuntime et la méthode statique surchargée (4 signatures correspondant aux 4 interfaces), Run de la class AsyncInfo  pour construire unes des 4 interfaces du modèles asynchrone de la WinRT. A noter que nous démarrons une Task manuellement qui sera retournée par la méthode Run. Cette task pouvant être arrêtée à l’aide du jeton de type CancellationToken. Enfin l’état d’avancement de l’opération asynchrone est reporté à l’aide de l’interface IProgress<int>.

 

Création du client Javascript

Je ne vais pas rentrer dans les détails d’implémentation de JavaScript, d’autres que moi pourrons sans doute mieux en parler.

Néanmoins, comme nous venons de le voir tous est asynchrone avec la WinRT et JavaScript n’échappe pas à la règle.

  En JavaScript on utilisera tout simplement la notion de promise symbolisée par la méthode then à ajouter à la fin de l’appel de la méthode et qui indique ce qu’il faut faire une fois que l’opération est terminée. Comme illustré  dans le listing suivant :

Code Snippet

  1. function UpdateProgress(percent) {   
  2.     var dbg = document.getElementById('dbg');
  3.     dbg.textContent = percent;
  4.  
  5. }
  6.  
  7. function GetMagicNumber() {
  8.  
  9.     var magic = new CppComponent.WinRTComponent();
  10.     //var magic = new CsComponent.Class1();
  11.     magic.getMagicNumberAsync().then(
  12.         function () {
  13.             //Fait Quelque chose une fois l'opration termine
  14.         },
  15.         function (error) {
  16.             UpdateProgress(0);
  17.         },
  18.         function (progress) {
  19.             UpdateProgress(progress);
  20.         });
  21.         
  22. }

Code Snippet

  1. function UpdateProgress(percent) {  
  2.     var dbg = document.getElementById('dbg');
  3.     dbg.textContent = percent;
  4.  
  5. }
  6.  
  7. function GetMagicNumber() {
  8.  
  9.     var magic = new CppComponent.WinRTComponent();
  10.     //var magic = new CsComponent.Class1();
  11.     magic.getMagicNumberAsync().then(
  12.         function () {
  13.             //Fait Quelque chose une fois l'opration termine
  14.         },
  15.         function (error) {
  16.             UpdateProgress(0);
  17.         },
  18.         function (progress) {
  19.             UpdateProgress(progress);
  20.         });
  21.        
  22. }

On notera ici que l’on utilise tout simplement notre composant WinRT C++ et ou C# en l’instanciant avec le mot clé new. Ceci est rendu possible, car nous avons ajouté en tant que nouvelles références (Click droit dans l’explorateur de solution->References-> Add New Reference) le ou les fichiers WinMD associés.

La méthode then, prend trois paramètres, une fonction qui sera exécutée lorsque l’opération est terminée, une fonction pour la gestion des erreurs, et une fonction qui permet de reporter à l’écran l’état de progression de l’opération.

Enfin, vous aurez constaté que la méthode GetMagicNumber(), commence par un “g” en minuscule, alors que nous l’avons définie avec un “G” en majuscule, ce n’est pas une erreur, mais inhérent au langage JavaScript qui utilise la notation camel.

 

Voila vous savez tout, à vous maintenant de jouer et si vous souhaitez aller plus loin je ne peux que vous encourager à visiter le centre de développement Windows.

 

Eric Vernié

Fier d’être développeur

 

Pour aller plus loin :

Centre de développement Windows

API reference for Metro style apps

Roadmap for Metro style apps using JavaScript

Roadmap for Metro style apps using C# or Visual Basic

Roadmap for Metro style apps using C++

Creating Windows Runtime Components

Creating Windows Runtime Components in C++

Creating Windows Runtime Components in C# and Visual Basic

Quickstart: using the await operator for asynchronous programming

Asynchronous programming in C++