演练:用 C++ 创建一个基本的 Windows 运行时组件,然后从 JavaScript 中调用该组件
本演练演示如何创建可从 JavaScript、C# 或 Visual Basic 中调用的基本 Windows 运行时组件 DLL。在开始本演练之前,请确保你已了解抽象二进制接口 (ABI)、ref 类等概念以及更便于使用 ref 类的 Visual C++ 组件扩展。有关更多信息,请参见用 C++ 创建 Windows 运行时组件和 Visual C++ 语言参考 (C++/CX)。
创建 C++ 组件项目
在此示例中,我们首先创建组件项目,但也可以先创建 JavaScript 项目。顺序并不重要。
请注意,组件的主类包含属性和方法定义的示例以及事件声明。提供这些仅为演示具体操作方法。这些内容并非必需内容,在此示例中,我们会将所有生成的代码都替换为自己的代码。
创建 C++ 组件项目
在 Visual Studio 菜单栏上,选择**“文件”、“新建”、“项目”**。
在**“新建项目”对话框的左窗格中,展开“Visual C++”**,然后选择 Windows 应用商店应用程序的节点。
在中间窗格中,选择**“Windows 运行时组件”**,然后将项目命名为 CppLib。
选择**“确定”**按钮。
向组件中添加可激活的类
可激活的类 是 JavaScript 可以使用 new 表达式创建的类。在组件中,将其声明为 public ref class sealed。实际上,Class1.h 和 .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 线程。使用 create_async 方法在内部创建 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);
我们使用此对象将事件从并行函数封送回 UI 线程。
将这些实现添加到 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,然后选择**“确定”**按钮。
添加调用 JavaScript 事件处理程序的 HTML
将此 HTML 粘贴到 default.html 页的 <body> 节点中:
<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; }
添加调入组件 DLL 的 JavaScript 事件处理程序
在 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。
在对象浏览器中检查组件(可选)
在**“对象浏览器”中,可以检查在 .winmd 文件中定义的所有 Windows 运行时类型。这包括 Platform 命名空间和默认命名空间中的类型。但是,Platform::Collections 命名空间中的类型在头文件 collections.h 而不是 winmd 文件中定义。因此,这些类型不会出现在“对象浏览器”**中。
在对象浏览器中检查组件
在 Visual Studio 菜单栏中,选择**“视图”、“其他窗口”、“对象浏览器”**。
在**“对象浏览器”的左窗格中,展开“CppLib”**节点,以显示在组件中定义的类型和方法。
调试提示
为了获得更好的调试体验,请从公共的 Microsoft 符号服务器中下载调试符号。从主菜单上,选择**“工具”,再选择“选项”。在“选项”窗口中,展开“调试”并选择“符号”。选中“Microsoft 符号服务器”旁边的框并选择“确定”**。初次下载符号可能需要一些时间。为了获得更快的性能,下次按 F5 时,请使用提供的空间来指定要在其中缓存符号的本地目录。
在调试包含组件 DLL 的 JavaScript 解决方案时,可以对调试器进行设置,以启用逐句调试脚本或逐句调试组件中的本机代码,但不能同时启用两者。若要更改设置,请在**“解决方案资源管理器”的 JavaScript 项目节点的快捷菜单中,依次选择“属性”、“调试”、“调试器类型”**。
确保在包设计器中选择适当功能。例如,如果你尝试使用 Windows 运行时 API 打开文件,请确保在包设计器的**“功能”窗格中选中“文档库访问”**复选框。
如果 JavaScript 代码看起来无法识别组件中的公共属性或方法,请确保在 JavaScript 中使用 camel 大小写形式。例如,ComputeResult C++ 方法在 JavaScript 中必须作为 computeResult 来引用。
如果从解决方案中删除 C++ Windows 运行时组件项目,还必须从 JavaScript 项目中手动删除项目引用。否则,将无法执行后续的调试或生成操作。如果需要,随后可向 DLL 中添加程序集引用。
请参见
参考
Roadmap for Windows Store apps using C++