演练:用 C++ 创建一个基本的 Windows 运行时组件,然后从 JavaScript 中调用该组件

本演练演示如何创建可从 JavaScript、C# 或 Visual Basic 中调用的基本 Windows 运行时组件 DLL。在开始本演练之前,请确保你已了解抽象二进制接口 (ABI)、ref 类等概念以及更便于使用 ref 类的 Visual C++ 组件扩展。有关更多信息,请参见用 C++ 创建 Windows 运行时组件Visual 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 和 .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 线程。使用 create_async 方法在内部创建 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);
    

    我们使用此对象将事件从并行函数封送回 UI 线程。

  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,然后选择**“确定”**按钮。

添加调用 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 文件中定义。因此,这些类型不会出现在“对象浏览器”**中。

在对象浏览器中检查组件

  1. 在 Visual Studio 菜单栏中,选择**“视图”“其他窗口”“对象浏览器”**。

  2. 在**“对象浏览器”的左窗格中,展开“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++

其他资源

在 JavaScript 和 C++ 中开发 Windows 应用商店应用程序 Bing 地图行程优化器