Поделиться через


Пошаговое руководство. Создание основного компонента среды выполнения Windows в C++ и его вызов из кода JavaScript

В этом пошаговом руководстве показано, как создать простую DLL-библиотеку компонента Среда выполнения Windows, вызываемую из компонента JavaScript, C# или Visual Basic. Прежде чем приступать к этому пошаговому руководству, убедитесь, что вы понимаете такие концепции, как абстрактный бинарный интерфейс (Abstract Binary Interface, ABI), классы ref и расширения компонентов Visual C++, которые упрощают работу с классами ref. Дополнительные сведения см. в разделах Создание компонентов среды выполнения Windows в C++ и Справочник по языку C++ (C++/CX).

Создание проекта компонента C++

В этом примере сначала создается проект компонента, но можно сначала создать проект JavaScript. Порядок не имеет значения.

Обратите внимание, что основной класс компонента содержит примеры определений свойств и методов, а также объявление события. Они включаются в проект только для демонстрации. В данном примере они не требуются, и мы заменим весь созданный код нашим собственным кодом.

Создание проекта компонента C++

  1. В меню Visual Studio Файл выберите пункт Новый проект.

  2. В левой области диалогового окна Новый проект разверните узел Visual C++ и выберите узел приложений Магазин Windows.

  3. В центральной области выберите Компонент среды выполнения Windows, а затем назовите проект CppLib.

  4. Нажмите кнопку ОК.

Добавление активируемого класса в компонент

Активируемый класс — это класс, который в коде JavaScript можно создать с помощью выражения new. В вашем компоненте он объявляется как public ref class sealed. В действительности файлы Class1.h и Class1.cpp уже содержат класс ref. Имя можно изменить, однако в данном примере мы будет использовать имя по умолчанию Class1. При необходимости в компоненте можно задать дополнительные классы ref. Дополнительные сведения о классах ref см. в разделе Система типов (C++/CX).

Добавление необходимых директив #include и using

Чтобы добавить необходимые директивы #include и using

  1. Добавьте эти директивы #include в файл Class1.h:

    #include <collection.h>
    #include <amp.h>
    #include <amp_math.h>
    
  2. Добавьте эти директивы #include в файл Class1.cpp:

    #include <ppltasks.h>
    #include <concurrent_vector.h>
    
  3. Добавьте эти с директивы using в файл Class1.cpp:

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

Добавление в класс синхронного открытого метода

Методы, которые выполняются быстро, могут быть реализованы как синхронные методы. Они выполняются в том же потоке, что и вызывающий объект JavaScript. Все параметры и возвращаемые значения открытых методов должны иметь типы, совместимые с Среда выполнения Windows.

Чтобы добавить в класс синхронный открытый метод

  1. Добавьте эти объявления в класс в файле 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. Добавьте эти реализации в файл 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;
    }
    

Добавление в класс асинхронных открытых методов

Реализуйте методы, выполнение которых может занимать некоторое время, в виде асинхронных методов. Асинхронный метод выполняется в фоновом потоке и не блокирует поток JavaScript. Создайте открытый метод async, воспользовавшись внутренним методом create_async. JavaScript вызывает этот метод, как и любое "обещание". Как и в случае синхронного метода, все параметры должны иметь типы, совместимые с Среда выполнения Windows. В асинхронном методе, возвращаемое значение должно иметь один из следующих типов:

  • IAsyncAction для асинхронных методов, не возвращающих значение и не предоставляющих данные о ходе выполнения;

  • IAsyncActionWithProgress для асинхронных методов, возвращающих значение, но не предоставляющих данные о ходе выполнения;

  • IAsyncOperation<T> для асинхронных методов, возвращающих значение T, но не предоставляющих данные о ходе выполнения;

  • IAsyncOperationWithProgress<T> для асинхронных методов, возвращающих значение T и предоставляющих данные о ходе выполнения.

Дополнительные сведения см. в разделе Создание асинхронных операций в C++ для приложений для Магазина Windows.

Добавление асинхронного метода, который возвращает результат и предоставляет данные о ходе выполнения

  1. Добавьте эти объявления в класс в файле 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. Добавьте эти реализации в файл 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;
        });
    }
    

Добавление асинхронного метода, который вызывает событие и предоставляет данные о ходе выполнения

  1. Добавьте следующие открытые объявления в класс в файле Class1.h:

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

    Добавьте следующее закрытое объявление:

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

    Добавьте следующий делегат в область пространства имен в Class1.h:

    public delegate void PrimeFoundHandler(int i);
    

    Мы используем этот объект для маршалирования событий из параллельной функции обратно в поток пользовательского интерфейса.

  2. Добавьте эти реализации в файл 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);
        });
    }
    
    

Создание проекта JavaScript.

Чтобы создать проект JavaScript

  1. В Обозревателе решений в контекстном меню узла Решение выберите Добавить, Новый проект.

  2. Разверните узел JavaScript и выберите Пустое приложение.

  3. Примите имя по умолчанию App1, нажав кнопку ОК.

  4. Щелкните правой кнопкой мыши узел проекта App1 и выберите пункт Назначить запускаемым проектом.

  5. Добавьте ссылку на проект в CppLib.

    1. В контекстном меню узла Ссылки выберите Добавить ссылку.

    2. В левой области диалогового окна Диспетчер ссылок выберите Решение, а затем выберите Проекты.

    3. В центральной области выберите CppLib, а затем нажмите кнопку ОК.

Добавление кода HTML, который вызывает обработчики событий JavaScript

Вставьте следующий код HTML в узел <body> страницы 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>

Добавление стилей

Удалите стиль body, а затем добавьте в файл 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; }

Добавление обработчиков событий JavaScript, которые вызывают компонент DLL

Добавьте в конец файла default.js следующие функции. Эти функции вызываются, когда нажимаются кнопки на главной странице. Обратите внимание, как JavaScript активирует класс C++, а затем вызывает его методы и использует возвращаемые значения для заполнения меток 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() + " "; };

Запуск приложения

Нажмите клавишу F5.

Проверка компонента в обозревателе объектов (необязательно)

В Обозревателе объектов можно проверить все типы Среда выполнения Windows, определенные в файлах WinMD. Сюда входят типы из пространства имен Platform, а также из пространства имен по умолчанию. Однако типы в пространстве имен Platform::Collections определены в файле заголовков collections.h, а не в файле WinMD. Поэтому эти типы не отражаются в Обозреватель объектов.

Проверка компонента в обозревателе объектов

  1. В строке меню среды Visual Studio выберите Вид, Другие окна, Обозреватель объектов.

  2. В левой области Обозревателя объектов разверните узел CppLib, чтобы увидеть типы и методы, которые определены в компоненте.

Советы по отладке

Для более эффективной отладки загрузите символы отладки с открытых серверов символов Майкрософт. В главном меню последовательно выберите пункты Сервис и Параметры. В окне Параметры разверните узел Отладка и выберите Символы. Установите флажок Серверы символов Microsoft и нажмите кнопку ОК. Первоначальная загрузка символов может занять некоторое время. Для повышения производительности при следующем нажатии клавиши F5 используйте предоставленное место, чтобы указать локальный каталог, в котором следует кэшировать символы.

При отладке решения JavaScript, содержащего библиотеку DLL компонента, можно настроить отладчик для пошагового выполнения скрипта или машинного кода в компоненте, однако нельзя отлаживать эти части одновременно. Чтобы изменить этот параметр, в контекстном меню узла проекта JavaScript в Обозревателе решений последовательно выберите пункты Свойства, Отладка, Тип отладчика.

Обязательно выберите соответствующие возможности в конструкторе пакетов. Например, если вы пытаетесь открыть файл с помощью API среды выполнения Windows, необходимо установить флажок Доступ к библиотеке документов в области Возможности конструктора пакетов.

Если коду JavaScript не удается распознавать открытые свойства или методы в компоненте, убедитесь, что в JavaScript используется "верблюжий" стиль имен. Например, метод ComputeResult C++ следует вызывать из JavaScript как computeResult.

При удалении проекта компонента среды выполнения Windows C++ из решения необходимо также вручную удалить ссылку на этот проект из проекта JavaScript. Невыполнение этого требования приведет к невозможности последующей отладки и выполнения операций построения. При необходимости можно добавить ссылку на сборку в библиотеку DLL.

См. также

Ссылки

Roadmap for Windows Store apps using C++

Другие ресурсы

Разработка Bing Maps Trip Optimizer — приложения для Магазина Windows — с помощью JavaScript и C++