Пошаговое руководство. Создание основного компонента среды выполнения 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++
В меню Visual Studio Файл выберите пункт Новый проект.
В левой области диалогового окна Новый проект разверните узел Visual C++ и выберите узел приложений Магазин Windows.
В центральной области выберите Компонент среды выполнения Windows, а затем назовите проект CppLib.
Нажмите кнопку ОК.
Добавление активируемого класса в компонент
Активируемый класс — это класс, который в коде JavaScript можно создать с помощью выражения new. В вашем компоненте он объявляется как public ref class sealed. В действительности файлы Class1.h и Class1.cpp уже содержат класс ref. Имя можно изменить, однако в данном примере мы будет использовать имя по умолчанию Class1. При необходимости в компоненте можно задать дополнительные классы ref. Дополнительные сведения о классах ref см. в разделе Система типов (C++/CX).
Добавление необходимых директив #include и using
Чтобы добавить необходимые директивы #include и using
Добавьте эти директивы #include в файл Class1.h:
#include <collection.h> #include <amp.h> #include <amp_math.h>
Добавьте эти директивы #include в файл Class1.cpp:
#include <ppltasks.h> #include <concurrent_vector.h>
Добавьте эти с директивы 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.
Чтобы добавить в класс синхронный открытый метод
Добавьте эти объявления в класс в файле 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>&);
Добавьте эти реализации в файл 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.
Добавление асинхронного метода, который возвращает результат и предоставляет данные о ходе выполнения
Добавьте эти объявления в класс в файле 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);
Добавьте эти реализации в файл 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; }); }
Добавление асинхронного метода, который вызывает событие и предоставляет данные о ходе выполнения
Добавьте следующие открытые объявления в класс в файле 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);
Мы используем этот объект для маршалирования событий из параллельной функции обратно в поток пользовательского интерфейса.
Добавьте эти реализации в файл 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
В Обозревателе решений в контекстном меню узла Решение выберите Добавить, Новый проект.
Разверните узел JavaScript и выберите Пустое приложение.
Примите имя по умолчанию App1, нажав кнопку ОК.
Щелкните правой кнопкой мыши узел проекта App1 и выберите пункт Назначить запускаемым проектом.
Добавьте ссылку на проект в CppLib.
В контекстном меню узла Ссылки выберите Добавить ссылку.
В левой области диалогового окна Диспетчер ссылок выберите Решение, а затем выберите Проекты.
В центральной области выберите 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. Поэтому эти типы не отражаются в Обозреватель объектов.
Проверка компонента в обозревателе объектов
В строке меню среды Visual Studio выберите Вид, Другие окна, Обозреватель объектов.
В левой области Обозревателя объектов разверните узел 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++