Partager via


Procédure pas à pas : création d'un composant Windows Runtime de base en C++ et appel de ce composant depuis JavaScript

Cette procédure pas à pas montre comment créer une DLL de composant Windows Runtime de base qui peut être appelée à partir de JavaScript, C# ou de Visual Basic. Avant de commencer cette procédure pas à pas, assurez-vous de comprendre les concepts tels que l'interface binaire abstraite (ABI), les classes ref et les extensions de composants Visual C++ qui facilitent l'utilisation des classes ref. Pour plus d'informations, consultez Création de composants Windows Runtime en C++ et Référence du langage Visual C++ (C++/CX).

Création du projet du composant C++

Dans cet exemple, nous commençons par créer le projet du composant, mais vous pouvez très bien créer le projet JavaScript en premier. L'ordre n'a pas d'importance.

Notez que la classe principale du composant contient des exemples de propriété et de définitions de méthode, ainsi qu'une déclaration d'événement. Ces éléments sont fournis uniquement pour vous montrer le fonctionnement. ils ne sont pas obligatoires, et dans cet exemple, nous remplacerons le code généré par notre propre code.

Pour créer le projet de composant C++

  1. Dans la barre de menus Visual Studio, choisissez Fichier, Nouveau, Projet.

  2. Dans la boîte de dialogue Nouveau projet, dans le volet gauche, développez Visual C++, puis sélectionnez le nœud pour les applications Windows Store.

  3. Dans le volet central, sélectionnez Composant Windows Runtime, puis nommez le projet CppLib.

  4. Cliquez sur le bouton OK.

Ajout d'une classe activable au composant

Une classe activable est une classe que JavaScript peut créer à l'aide d'une expression new. Dans votre composant, vous devez la déclarer comme public ref class sealed. En fait, les fichiers Class1.h et .cpp ont déjà une classe de référence. Vous pouvez modifier le nom, mais dans cet exemple nous utiliserons celui par défaut : Class1. Vous pouvez définir des classes ref supplémentaires dans votre composant si elles sont requises. Pour plus d'informations sur les classes ref, consultez Système de type (C++/CX).

Ajout des instructions #include et using obligatoires

Pour ajouter les instructions #include et using obligatoires

  1. Ajoutez ces directives #include à Class1.h:

    #include <collection.h>
    #include <amp.h>
    #include <amp_math.h>
    
  2. Ajoutez ces directives #include à Class1.cpp:

    #include <ppltasks.h>
    #include <concurrent_vector.h>
    
  3. Ajoutez ces instructions using à Class1.cpp:

    using namespace concurrency;
    using namespace Platform::Collections;
    using namespace Windows::Foundation::Collections;
    using namespace Windows::Foundation;
    using namespace Windows::UI::Core;
    

Ajout d'une méthode publique synchrone à la classe

Les méthodes qui s'exécutent rapidement peuvent être implémentées comme des méthodes synchrones. Elles s'exécutent sur le même thread que l'appelant JavaScript. Tous les types de paramètre et les valeurs de retour sur des méthodes publiques doivent être des types compatibles Windows Runtime.

Pour ajouter une méthode publique synchrone à la classe

  1. Ajoutez ces déclarations à la classe dans Class1.h:

    public:
    
        Windows::Foundation::Collections::IVector<double>^  ComputeResult(double input);
        Windows::Foundation::IAsyncOperationWithProgress<Windows::Foundation::Collections::IVector<int>^, double>^
            Class1::GetPrimesOrdered(int first, int last);
        Windows::Foundation::IAsyncActionWithProgress<double>^ Class1::GetPrimesUnordered(int first, int last);
        event PrimeFoundHandler^ primeFoundEvent;
    private:
        bool is_prime(int n);
        Windows::UI::Core::CoreDispatcher^ m_dispatcher;
    private:
        void ComputeResultImpl(concurrency::array_view<float, 1>&);
    
  2. Ajoutez ces implémentations à la Class1.cpp:

    void Class1::ComputeResultImpl(array_view<float, 1>& logs) 
    {
        parallel_for_each(
            logs.extent,
             [=] (index<1> idx) restrict(amp) {
                logs[idx] = concurrency::fast_math::log10(logs[idx]);
            }
        );
    }
    //Public API
    IVector<double>^ Class1::ComputeResult(double input)
    {
       // Implement your function in ISO C++ or
       // call into your C++ lib or DLL here. This example uses AMP.
       float numbers[] = { 1.0, 10.0, 60.0, 100.0, 600.0, 10000.0 };
        array_view<float, 1> logs(6, numbers);
       ComputeResultImpl(logs);
    
       // Return a Windows Runtime-compatible type across the ABI
       auto res = ref new Vector<double>();
       int len = safe_cast<int>(logs.extent.size());
       for(int i = 0; i < len; i++)
       {      
          res->Append(logs[i]);
       }
       return res;
    }
    

Ajout de méthodes publiques asynchrones à la classe

Méthodes d'implémentation dont l'exécution en tant que méthodes asynchrones peut prendre un certain temps. Une méthode asynchrone s'exécute sur un thread d'arrière-plan et ne bloque pas le thread JavaScript. Créez la méthode publique async à l'aide de la méthode create_async en interne. JavaScript appelle la méthode comme elle n'importe quelle promesse. Comme avec une méthode synchrone, tous les types de paramètre doivent être des types compatibles Windows Runtime. Dans une méthode asynchrone, le type de retour doit être l'un de ces éléments :

  • IAsyncAction pour les méthodes async qui ne retournent aucune valeur et ne fournissent pas d'informations de progression.

  • IAsyncActionWithProgress pour les méthodes async qui ne retournent aucune valeur, mais fournissent des informations de progression.

  • IAsyncOperation<T> pour les méthodes async qui retournent une valeur T, mais ne fournissent pas d'informations de progression.

  • IAsyncOperationWithProgress<T> pour les méthodes async qui retournent une valeur T et fournissent également des informations de progression.

Pour plus d'informations, consultez Création d'opérations asynchrones en C++ pour les applications Windows Store.

Pour ajouter une méthode asynchrone qui retourne un résultat et fournit des informations de progression

  1. Ajoutez ces déclarations à la classe dans Class1.h:

        Windows::Foundation::IAsyncOperationWithProgress<Windows::Foundation::Collections::IVector<int>^, double>^
            Class1::GetPrimesOrdered(int first, int last);
        Windows::Foundation::IAsyncActionWithProgress<double>^ Class1::GetPrimesUnordered(int first, int last);
        event PrimeFoundHandler^ primeFoundEvent;
    private:
        bool is_prime(int n);
    
  2. Ajoutez ces implémentations à la Class1.cpp:

    // Determines whether the input value is prime.
    bool Class1::is_prime(int n)
    {
       if (n < 2)
          return false;
       for (int i = 2; i < n; ++i)
       {
          if ((n % i) == 0)
             return false;
       }
       return true;
    }
    
    // This method computes all primes, orders them, then returns the ordered results.
    IAsyncOperationWithProgress<IVector<int>^, double>^ Class1::GetPrimesOrdered(int first, int last)
    {
        return create_async([this, first, last]
        (progress_reporter<double> reporter) -> IVector<int>^ {
            // Ensure that the input values are in range.
            if (first < 0 || last < 0) {
                throw ref new InvalidArgumentException();
            }
            // Perform the computation in parallel.
            concurrent_vector<int> primes;
            long operation = 0;
            long range = last - first + 1;
            double lastPercent = 0.0;
    
            parallel_for(first, last + 1, [this, &primes, &operation, 
                range, &lastPercent, reporter](int n) {
                    // Report progress message.
                    double progress = 100.0 * InterlockedIncrement(&operation) / range;
                    if (progress >= lastPercent)
                    {
                        reporter.report(progress);
                        lastPercent += 1.0;
                    }
    
                    // If the value is prime, add it to the local vector.
                    if (is_prime(n)) {
                        primes.push_back(n);
                    }
            });
    
    
            // Sort the results.
            std::sort(begin(primes), end(primes), std::less<int>());
    
            // Copy the results to an IVector object. The IVector
            // interface makes collections of data available to other
            // Windows Runtime components.
            IVector<int>^ results = ref new Vector<int>();
            std::for_each(std::begin(primes), std::end(primes), [&results](int prime) {
                results->Append(prime);
            });
            reporter.report(100.0);
            return results;
        });
    }
    

Pour ajouter une méthode asynchrone qui déclenche des événements et fournit les informations de progression

  1. Ajoutez ces déclarations publiques à la classe dans Class1.h:

    Windows::Foundation::IAsyncActionWithProgress<double>^ Class1::GetPrimesUnordered(int first, int last);
    event PrimeFoundHandler^ primeFoundEvent;
    

    Ajoutez cette déclaration privée :

    Windows::UI::Core::CoreDispatcher^ m_dispatcher;
    

    Ajoutez le délégué suivant dans la portée d'espace de noms dans Class1.h:

    public delegate void PrimeFoundHandler(int i);
    

    Nous utilisons cet objet pour rassembler les événements à partir de la fonction parallèle vers le thread d'interface utilisateur.

  2. Ajoutez ces implémentations à la Class1.cpp:

    // This method returns no value. Instead, it fires an event each time a prime is found, and transfers the prime through the event.
    IAsyncActionWithProgress<double>^ Class1::GetPrimesUnordered(int first, int last)
    {
    
       auto window = Windows::UI::Core::CoreWindow::GetForCurrentThread();
       m_dispatcher = window->Dispatcher;
    
        return create_async([this, first, last](progress_reporter<double> reporter) {
            // Ensure that the input values are in range.
            if (first < 0 || last < 0) {
                throw ref new InvalidArgumentException();
            }
            // Perform the computation in parallel.
            concurrent_vector<int> primes;
            long operation = 0;
            long range = last - first + 1;
            double lastPercent = 0.0;
    
            parallel_for(first, last + 1, [this, &primes, &operation, range, &lastPercent, reporter](int n) 
            {
                // Report progress message.
                double progress = 100.0 * InterlockedIncrement(&operation) / range;
                if (progress >= lastPercent)
                {
                    reporter.report(progress);
                    lastPercent += 1.0;
                }
    
                // If the value is prime, add it to the local vector.
                if (is_prime(n))
                {
                    primes.push_back(n);
                    m_dispatcher->RunAsync( CoreDispatcherPriority::Normal,
                         ref new DispatchedHandler([this, n]() 
                           {
                                 this->primeFoundEvent(n);
                           }, Platform::CallbackContext::Any));
    
                }
            });
            reporter.report(100.0);
        });
    }
    
    

Création d'un projet JavaScript

Pour créer un projet JavaScript

  1. Dans l'Explorateur de solutions, dans le menu contextuel du nœud Solution, choisissez Ajouter, Nouveau projet.

  2. Développez JavaScript et choisissez Application vide.

  3. Acceptez le nom par défaut App1 en cliquant sur le bouton OK.

  4. Cliquez avec le bouton droit sur le nœud de projet App1 et choisissez Définir comme projet de démarrage.

  5. Ajoutez une référence de projet à CppLib :

    1. Dans le menu contextuel du nœud Références, choisissez Ajouter une référence.

    2. Dans le volet gauche de la boîte de dialogue Gestionnaire de références, sélectionnez Solution, puis Projets.

    3. Dans le volet central, sélectionnez CppLib et cliquez sur le bouton OK.

Ajout de code HTML qui appelle les gestionnaires d'événements JavaScript

Collez ce code HTML dans le nœud <body> de la page default.html :

<div id="LogButtonDiv">
     <button id="logButton" onclick="LogButton_Click()">Logarithms using AMP</button>
 </div>
 <div id="LogResultDiv">
     <p id="logResult"></p>
 </div>
 <div id="OrderedPrimeButtonDiv">
     <button id="orderedPrimeButton" onclick="ButtonOrdered_Click()">Primes using parallel_for with sort</button>
 </div>
 <div id="OrderedPrimeProgress">
     <progress id="OrderedPrimesProgressBar" value="0" max="100"></progress>
 </div>
 <div id="OrderedPrimeResultDiv">
     <p id="orderedPrimes">
         Primes found (ordered):
     </p>
 </div>
 <div id="UnorderedPrimeButtonDiv">
     <button id="ButtonUnordered" onclick="ButtonUnordered_Click()">Primes returned as they are produced.</button>
 </div>
 <div id="UnorderedPrimeDiv">
     <progress id="UnorderedPrimesProgressBar" value="0" max="100"></progress>
 </div>
 <div id="UnorderedPrime">
     <p id="unorderedPrimes">
         Primes found (unordered):
     </p>
 </div>

Ajout des styles

Supprimez le style body, puis ajoutez ceux-ci au fichier default.css :


#LogButtonDiv { background: maroon; border: orange solid 1px; -ms-grid-row: 1; /* default is 1 */; -ms-grid-column: 1; /* default is 1 */ } #LogResultDiv { background: black; border: red solid 1px; -ms-grid-row: 1; -ms-grid-column: 2; } #UnorderedPrimeButtonDiv, #OrderedPrimeButtonDiv { background: green; border: orange solid 1px; -ms-grid-row: 2; -ms-grid-column:1; } #UnorderedPrimeDiv, #OrderedPrimeDiv { background: maroon; border: red solid 1px; -ms-grid-row: 2; -ms-grid-column:1; } #UnorderedPrimeProgress, #OrderedPrimeProgress { background: lightgray; border: red solid 1px; -ms-grid-row: 2; -ms-grid-column: 2; -ms-grid-column-span: 2; } #UnorderedPrimeResult, #OrderedPrimeResult { background: black; border: red solid 1px; -ms-grid-row: 2; -ms-grid-column: 3; }

Ajout de gestionnaires d'événements JavaScript faisant appel au DLL de composant

Ajoutez les fonctions suivantes à la fin du fichier default.js : Ces fonctions sont appelées lorsque les boutons de la page principale sont sélectionnés. Notez que JavaScript active la classe C++, appelle ensuite ses méthodes, puis utilise des valeurs de retour pour remplir les étiquettes HTML.

var nativeObject = new cpplib.Class1(); function LogButton_Click() { var val = nativeObject.computeResult(0); var result = ""; for (i = 0; i < val.length; i++) { result += val[i] + "<br/>"; } document.getElementById('logResult').innerHTML = result; } function ButtonOrdered_Click() { document.getElementById('orderedPrimes').innerHTML = "Primes found (ordered): "; var asyncResult = nativeObject.getPrimesOrdered(2, 1000).then( function (v) { for (var i = 0; i < v.length; i++) document.getElementById('orderedPrimes').innerHTML += v[i] + " "; }, function (error) { document.getElementById('orderedPrimes').innerHTML += " " + error.description; }, function (p) { var progressBar = document.getElementById( "OrderedPrimesProgressBar"); progressBar.value = p; }); } function ButtonUnordered_Click() { document.getElementById('unorderedPrimes').innerHTML = "Primes found (unordered): "; nativeObject.onprimefoundevent = handler_unordered; var asyncResult = nativeObject.getPrimesUnordered(2, 1000).then( function () { }, function (error) { document.getElementById("unorderedPrimes").innerHTML += " " + error.description; }, function (p) { var progressBar = document.getElementById("UnorderedPrimesProgressBar"); progressBar.value = p; }); } var handler_unordered = function (n) { document.getElementById('unorderedPrimes').innerHTML += n.target.toString() + " "; };

Exécution de l'application

Appuyez sur F5.

Examen de votre composant dans l'Explorateur d'objets (facultatif)

Dans l'Explorateur d'objets, vous pouvez examiner tous les types Windows Runtime définis dans les fichiers.winmd. Cela inclut les types de l'espace de noms Platform et l'espace de noms par défaut. Toutefois, les types de l'espace de noms Platform::Collections sont définis dans le fichier d'en-tête collections.h, et non dans un fichier winmd. Par conséquent, ces types n'apparaissent pas dans l'Explorateur d'objets.

Pour vérifier votre composant dans l'Explorateur d'objets

  1. Dans la barre de menus Visual Studio, choisissez Afficher, Autres fenêtres, Explorateur d'objets.

  2. Dans le volet gauche de l'Explorateur d'objets, développez le nœud CppLib pour afficher les types et les méthodes qui sont définis dans votre composant.

Conseils de débogage

Pour optimiser le débogage, téléchargez les symboles de débogage à partir des serveurs de symboles publics de Microsoft. Dans le menu principal, sélectionnez Outils, puis Options. Dans la fenêtre de Options, développez Débogage et sélectionnez Symboles. Activez la case à cocher en regard de Serveurs de symboles Microsoft et choisissez OK. Le téléchargement peut prendre un certain temps la première fois. Pour accélérer les performances la prochaine fois que vous appuyez sur F5, utilisez l'espace disponible pour spécifier un dossier local dans lequel mettre en cache les symboles.

Lorsque vous déboguez une solution JavaScript qui a une DLL de composant, vous pouvez définir le débogueur pour activer la progression dans le script ou dans le code natif du composant, mais pas les deux à la fois. Pour modifier la configuration, dans le menu contextuel du nœud de projet JavaScript dans Explorateur de solutions, choisissez Propriétés, Débogage, Type de débogueur.

Veillez à sélectionner les fonctionnalités appropriées dans le concepteur de packages. Par exemple, si vous essayez d'ouvrir un fichier à l'aide des API Windows Runtime, veillez à activer la case à cocher Bibliothèque de documents dans le volet Capacités du concepteur de packages.

Si votre code JavaScript ne semble pas identifier les propriétés ou méthodes publiques du composant, assurez-vous que vous utilisez la casse mixte dans JavaScript. Par exemple, la méthode C++ ComputeResult doit être référencée sous la forme computeResult dans JavaScript.

Si vous supprimez un projet de composant C++ Windows Runtime dans une solution, vous devez également supprimer manuellement la référence de projet dans le projet JavaScript. Sinon, les opérations de débogage ou de génération suivantes ne pourront pas être effectuées. Si nécessaire, ajoutez ensuite une référence d'assembly à la DLL.

Voir aussi

Référence

Roadmap for Windows Store apps using C++

Autres ressources

Développement de l'optimiseur de voyage Bing Maps, une application du Windows Store en JavaScript et C++