逐步解說:以 C++ 建立基本 Windows 執行階段元件,然後從 JavaScript 呼叫該元件
本逐步解說示範如何建立可從 JavaScript、C# 或 Visual Basic 呼叫的基本 Windows 執行階段元件 DLL。開始本逐步解說之前,請確定您了解一些概念,例如:抽象二進位介面 (Abstract Binary Interface, ABI)、ref 類別,以及讓 ref 類別更容易使用的 Visual C++ 元件擴充功能。如需詳細資訊,請參閱在 C++ 中建立 Windows 執行階段元件和 Visual C++ 語言參考 (C++/CX)。
建立 C++ 元件專案
在這個範例中,我們會先建立元件專案,但您也可以先建立 JavaScript 專案。順序並不重要。
請注意,此元件的主要類別包含屬性和方法定義的範例,以及事件宣告。提供這些項目只是為了顯示其進行方式。這些並不是必要的項目,而且在這個範例中,我們會以自己的程式碼取代所有產生的程式碼。
若要建立 C++ 元件專案
在 Visual Studio 功能表列上,依序選擇 [檔案]、[新增]、[專案]。
在 [新增專案] 對話方塊的左窗格中,展開 [Visual C++],然後選取 Windows 市集應用程式的節點。
在中間窗格中,選取 [Windows 執行階段元件],然後將專案命名為 CppLib。
選擇 [確定] 按鈕。
將可啟用類別加入至元件
「可啟用類別」(Activatable Class) 是 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 執行階段應用程式開發介面來嘗試開啟檔案,請務必選取封裝設計工具的 [功能] 窗格中的 [文件庫存取] 核取方塊。
如果 JavaScript 程式碼似乎無法辨識元件中的公用屬性或方法,請確定您在 JavaScript 中使用 Camel 命名法的大小寫慣例。例如,ComputeResult C++ 方法必須當做 JavaScript 中的 computeResult 來參考。
如果從方案中移除 C ++ Windows 執行階段元件專案,也必須手動移除 JavaScript 專案中的專案參考。若未執行此動作,後續的偵錯或建置作業將無法執行。之後如有必要,您可以加入 DLL 的組件參考。
請參閱
參考
Roadmap for Windows Store apps using C++