在 C++ 中建立 Windows 執行階段元件
本文說明如何使用 C++ 建立 Windows 執行階段元件,這是可從使用 JavaScript (或者 C#、Visual Basic 或 C++) 建置之 Windows 市集應用程式呼叫的 DLL。建置這類元件的原因有幾個:
獲得 C++ 在複雜或密集運算作業中的效能優勢。
重複使用已撰寫並測試的程式碼。
當您建置包含 JavaScript 或 .NET 專案與 Windows 執行階段元件專案的方案時,JavaScript 專案檔與已編譯的 DLL 會合併成單一套件,如此您就能在模擬器進行本機偵錯,或是在遠端共用連線裝置上進行偵錯。您也可以透過擴充功能 SDK 的格式,僅散佈元件專案。如需詳細資訊,請參閱HOW TO:建立軟體開發套件。
一般而言,當您撰寫 C++ 元件的程式碼時,應使用一般的 C++ 程式庫和內建型別,除非是在對其他 .winmd 封裝中的程式碼往來傳遞資料的抽象二進位介面 (ABI) 界限上。那時則請使用 Windows 執行階段型別,以及 Visual C++ 為了建立及管理那些型別而特地支援的特殊語法。此外,請在您的 Visual C++ 程式碼中使用 delegate 和 event 這類的型別來實作可從您的元件引發並且能在 JavaScript、Visual Basic 或 C# 中處理的事件。如需新 Visual C++ 語法的詳細資訊,請參閱 Visual C++ 語言參考 (C++/CX)。
大小寫和命名規則
JavaScript
JavaScript 區分大小寫。因此,您必須遵守下列大小寫慣例:
當您參考 C++ 命名空間和類別時,請使用 C++ 中使用的相同大小寫。
當您呼叫方法時,即使方法名稱的第一個字母在 C++ 中為大寫,也請使用 Camel 命名法的大小寫慣例。例如,在 JavaScript 中呼叫 C++ 方法 GetDate() 時,必須改為呼叫 getDate()。
可啟用的類別名稱和命名空間名稱不可包含 UNICODE 字元。
.NET
.NET 語言都遵循其一般大小寫規則。
具現化物件
只有 Windows 執行階段型別可以跨 ABI 界限傳遞。如果元件有 std::wstring 這類的型別做為公用方法中的傳回型別或參數,則編譯器會發出錯誤。Visual C++ 元件擴充功能 (C++/CX) 內建型別包括一般的純量型別,例如 int 和 double 以及其 typedef 對等項目 (int32 和 float64 等等)。如需詳細資訊,請參閱型別系統 (C++/CX)。
C++
// ref class definition in C++
public ref class SampleRefClass sealed
{
// Class members...
// #include <valarray>
public:
double LogCalc(double input)
{
// Use C++ standard library as usual.
return std::log(input);
}
};
JavaScript
//Instantiation in JavaScript (requires "Add reference > Project reference")
var nativeObject = new CppComponent.SampleRefClass();
.NET
//Call a method and display result in a XAML TextBlock
var num = nativeObject.LogCalc(21.5);
ResultText.Text = num.ToString();
C++ 內建型別、程式庫型別和 Windows 執行階段型別
可啟用類別 (也稱為 ref 類別) 是可在其他語言 (如 JavaScript、C# 或 Visual Basic) 中執行個體化的類別。元件至少要包含一個可啟用類別,才可供在其他語言中使用。
Windows 執行階段元件可包含多個公用的可啟用類別,以及其他只有在元件內部才認得的類別。所有公用類別都必須位於相同的根命名空間 (與元件中繼資料檔案同名) 中。對於不想讓 JavaScript 看見的 C++ 型别,請套用 [WebHostHidden] 屬性。
用戶端程式碼可使用 new (在 Visual Basic 中為 New) 關鍵字建立元件的執行個體 (對於任何類別都一樣)。
可啟用類別必須宣告為 public ref class sealed。ref 類別關鍵字會指示編譯器將類別建立成相容於 Windows 執行階段的型別,而 sealed 關鍵字則是指定此類別無法被繼承。Windows 執行階段目前不支援一般化繼承模型,因為受限制的繼承模型支援建立自訂 XAML 控制項。如需詳細資訊,請參閱 Ref 類別和結構 (C++/CX)。
對 C++ 而言,所有的數值基本型别都定義於預設命名空間中。Platform Namespace包含 Windows 執行階段型別系統的特定 C++ 類別。其中包括 Platform::String 類別和 Platform::Object 類別。具象的集合型別 (例如 Platform::Collections::Map 類別和 Platform::Collections::Vector 類別) 定義於 Platform::Collections 命名空間中。這些型别所實作的公用介面是定義於 Windows::Foundation::Collections 命名空間 (C++/CX) 中。它是 JavaScript、C# 和 Visual Basic 所使用的這些介面型別。如需詳細資訊,請參閱型別系統 (C++/CX)。
會傳回內建型別之值的方法
C++
// #include <valarray>
public:
double LogCalc(double input)
{
// Use C++ standard library as usual.
return std::log(input);
}
JavaScript
//Call a method
var nativeObject = new CppComponent.SampleRefClass;
var num = nativeObject.logCalc(21.5);
document.getElementById('P2').innerHTML = num;
.NET
會傳回自訂值結構的方法
value struct 是一般舊資料物件,這個物件可以包含預設是公用的欄位。value struct 是以傳值方式傳遞。
C++
namespace CppComponent
{
// Custom struct
public value struct PlayerData
{
Platform::String^ Name;
int Number;
double ScoringAverage;
};
public ref class Player sealed
{
private:
PlayerData m_player;
public:
property PlayerData PlayerStats
{
PlayerData get(){ return m_player; }
void set(PlayerData data) {m_player = data;}
}
};
}
JavaScript
若要跨 ABI 傳遞使用者定義的值結構,請定義一個 JavaScript 物件,這個物件要與在 C++ 中定義的值結構具有相同的成員。接著,您可以將該物件當做引數傳遞至 C ++ 方法,以便將物件隱含地轉換成 C++ 型別。
// Get and set the value struct
function GetAndSetPlayerData() {
// Create an object to pass to C++
var myData =
{ name: "Bob Homer", number: 12, scoringAverage: .357 };
var nativeObject = new CppComponent.Player();
nativeObject.playerStats = myData;
// Retrieve C++ value struct into new JavaScript object
var myData2 = nativeObject.playerStats;
document.getElementById('P3').innerHTML = myData.name + " , " + myData.number + " , " + myData.scoringAverage.toPrecision(3);
}
另一種方式是定義會實作 IPropertySet (未顯示) 的類別。
C#
在 .NET 語言中,您已建立型別是在 C++ 元件中定義的變數。
private void GetAndSetPlayerData()
{
// Create a ref class
var player = new CppComponent.Player();
// Create a variable of a value struct
// type that is defined in C++
CppComponent.PlayerData myPlayer;
myPlayer.Name = "Babe Ruth";
myPlayer.Number = 12;
myPlayer.ScoringAverage = .398;
// Set the property
player.PlayerStats = myPlayer;
// Get the property and store it in a new variable
CppComponent.PlayerData myPlayer2 = player.PlayerStats;
ResultText.Text += myPlayer.Name + " , " + myPlayer.Number.ToString() +
" , " + myPlayer.ScoringAverage.ToString();
}
多載方法
C++ 公用 ref 類別可以包含多載方法,不過 JavaScript 區分多載方法的能力有限。例如,它可以區別下列簽章:
public ref class NumberClass sealed
{
public:
int GetNumber(int i);
int GetNumber(int i, Platform::String^ str);
double GetNumber(int i, MyData^ d);
};
但是無法區分下列簽章:
int GetNumber(int i);
double GetNumber(double d);
或是下列簽章:
為防發生無法區別的情形,您可以將 Windows::Foundation::Metadata::DefaultOverload 屬性套用至標頭檔中的方法簽章,確保 JavaScript 一律會呼叫特定多載。
下列 JavaScript 一律會呼叫屬性化多載:
var nativeObject = new CppComponent.NumberClass();
var num = nativeObject.getNumber(9);
document.getElementById('P4').innerHTML = num;
.NET
.NET 語言可以辨識 C++. ref 類別中的多載,就如同辨識所有 .NET Framework 類別中的多載一樣。
DateTime
在 Windows 執行階段中,Windows::Foundation::DateTime 物件只是代表 1601 年 1 月 1 日之前和之後的 64 位元帶正負號整數 (間隔為 100 奈秒),因此 Windows:Foundation::DateTime 物件沒有方法。相反地,使用每種語言撰寫專案時,都可以使用該語言原生的 DateTime 表達方式:JavaScript 中有 Date 物件,而 .NET Framework 中則有 System.DateTime 和 System.DateTimeOffset 型別。
C++
public ref class MyDateClass sealed
{
public:
property Windows::Foundation::DateTime TimeStamp;
void SetTime(Windows::Foundation::DateTime dt)
{
auto cal = ref new Windows::Globalization::Calendar();
cal->SetDateTime(dt);
TimeStamp = cal->GetDateTime(); // or TimeStamp = dt;
}
};
JavaScript
當您從 C++將 DateTime 值傳給 JavaScript 時,JavaScript 會將這個值視為 Date 物件,並且依照預設顯示為完整格式的日期字串。
function SetAndGetDate() {
var nativeObject = new CppComponent.MyDateClass();
var myDate = new Date(1956, 4, 21);
nativeObject.setTime(myDate);
var myDate2 = nativeObject.timeStamp;
//prints long form date string
document.getElementById('P5').innerHTML = myDate2;
}
.NET
當 .NET 語言將 System.DateTime 傳給 C++ 元件時,方法會將這個值當做 Windows::Foundation::DateTime;當元件將 Windows::Foundation::DateTime 傳給 .NET Framework 方法時,Framework 方法則會將這個值當做 DateTimeOffset。
private void DateTimeExample()
{
// Pass a System.DateTime to a C++ method
// that takes a Windows::Foundation::DateTime
DateTime dt = DateTime.Now;
var nativeObject = new CppComponent.MyDateClass();
nativeObject.SetTime(dt);
// Retrieve a Windows::Foundation::DateTime as a
// System.DateTimeOffset
DateTimeOffset myDate = nativeObject.TimeStamp;
// Print the long-form date string
ResultText.Text += myDate.ToString();
}
集合和陣列
集合跨 ABI 界限傳遞時一律是採 Windows 執行階段型别之控制代碼的形式,例如 Windows::Foundation::Collections::IVector^ 與 Windows::Foundation::Collections::IMap^。例如,如果您傳回 Platform::Collections::Map 的控制代碼,它會隱含地轉換成 Windows::Foundation::Collections::IMap^。集合介面是在個別命名空間中定義,與提供具象實作的 C++ 類別不同。JavaScript 和 .NET 語言都會使用這些介面。如需詳細資訊,請參閱集合 (C++/CX) 和 Array 和 WriteOnlyArray (C++/CX)。
傳遞 IVector
C++
// Windows::Foundation::Collections::IVector across the ABI.
//#include <algorithm>
//#include <collection.h>
Windows::Foundation::Collections::IVector<int>^ SortVector(Windows::Foundation::Collections::IVector<int>^ vec)
{
std::sort(begin(vec), end(vec));
return vec;
}
JavaScript
var nativeObject = new CppComponent.CollectionExample();
// Call the method to sort an integer array
var inVector = [14, 12, 45, 89, 23];
var outVector = nativeObject.sortVector(inVector);
var result = "Sorted vector to array:";
for (var i = 0; i < outVector.length; i++)
{
outVector[i];
result += outVector[i].toString() + ",";
}
document.getElementById('P6').innerHTML = result;
.NET
.NET 語言會將 IVector<T> 當做 IList<T>。
private void SortListItems()
{
IList<int> myList = new List<int>();
myList.Add(5);
myList.Add(9);
myList.Add(17);
myList.Add(2);
var nativeObject = new CppComponent.CollectionExample();
IList<int> mySortedList = nativeObject.SortVector(myList);
foreach (var item in mySortedList)
{
ResultText.Text += " " + item.ToString();
}
}
傳遞 IMap
C++
// #include <map>
//#include <collection.h>
Windows::Foundation::Collections::IMap<int, Platform::String^> ^GetMap(void)
{
Windows::Foundation::Collections::IMap<int, Platform::String^> ^ret =
ref new Platform::Collections::Map<int, Platform::String^>;
ret->Insert(1, "One ");
ret->Insert(2, "Two ");
ret->Insert(3, "Three ");
ret->Insert(4, "Four ");
ret->Insert(5, "Five ");
return ret;
}
JavaScript
// Call the method to get the map
var outputMap = nativeObject.getMap();
var mStr = "Map result:" + outputMap.lookup(1) + outputMap.lookup(2)
+ outputMap.lookup(3) + outputMap.lookup(4) + outputMap.lookup(5);
document.getElementById('P7').innerHTML = mStr;
.NET
.NET 語言會將 IMap 當做 IDictionary<K,V>。
private void GetDictionary()
{
var nativeObject = new CppComponent.CollectionExample();
IDictionary<int, string> d = nativeObject.GetMap();
ResultText.Text += d[2].ToString();
}
屬性
透過 property 關鍵字,Visual C++ 元件擴充功能中的公用 ref 類別會將公用資料成員當做屬性來公開。這個概念與 .NET Framework 的屬性相同。trivial 屬性由於其功能都是隱含的,因此與資料成員類似。非簡單式屬性具有明確的 get 和 set 存取子,以及做為值之「備份存放區」的具名私用變數。在此範例中,私用成員 variable _propertyAValue 是 PropertyA 的備份存放區。屬性可以在自己的值變更時引發事件,而用戶端應用程式可登錄成為該事件的接收者。
C++
//Properties
public delegate void PropertyChangedHandler(Platform::Object^ sender, int arg);
public ref class PropertyExample sealed
{
public:
PropertyExample(){}
// Event that is fired when PropetyA changes
event PropertyChangedHandler^ PropertyChangedEvent;
// Property that has custom setter/getter
property int PropertyA
{
int get() { return m_propertyAValue; }
void set(int propertyAValue)
{
if (propertyAValue != m_propertyAValue)
{
m_propertyAValue = propertyAValue;
// Fire event. (See event example below.)
PropertyChangedEvent(this, propertyAValue);
}
}
}
// Trivial get/set property that has a compiler-generated backing store.
property Platform::String^ PropertyB;
private:
// Backing store for propertyA.
int m_propertyAValue;
};
JavaScript
var nativeObject = new CppComponent.PropertyExample();
var propValue = nativeObject.propertyA;
document.getElementById('P8').innerHTML = propValue;
//Set the string property
nativeObject.propertyB = "What is the meaning of the universe?";
document.getElementById('P9').innerHTML += nativeObject.propertyB;
.NET
.NET 語言存取原生 C++ 物件之屬性的方式,就如同存取 .NET Framework 物件的屬性一樣。
private void GetAProperty()
{
// Get the value of the integer property
// Instantiate the C++ object
var obj = new CppComponent.PropertyExample();
// Get an integer property
var propValue = obj.PropertyA;
ResultText.Text += propValue.ToString();
// Set a string property
obj.PropertyB = " What is the meaning of the universe?";
ResultText.Text += obj.PropertyB;
}
委派和事件
delegate 是一種代表函式物件的 Windows 執行階段型別。您可以將委派連同事件、回呼和非同步方法呼叫一起使用,以指定後續要執行的動作。如同函式物件,委派也可讓編譯器驗證函式的傳回型別和參數型別,以提供型別安全。委派的宣告類似於函式簽章、實作類似於類別定義,而叫用則類似於函式叫用。
加入事件接聽程式
您可以使用 event 關鍵字,宣告所指定委派型別的公用成員。用戶端程式碼可使用特定語言中所提供的標準機制來訂閱事件。
C++
public:
event SomeHandler^ someEvent;
上述範例與前一個屬性區段中都使用相同的 C++ 程式碼。
JavaScript
function Button_Click() {
var nativeObj = new CppComponent.PropertyExample();
// Define an event handler method
var singlecasthandler = function (ev) {
document.getElementById('P10').innerHTML = "The button was clicked and the value is " + ev;
};
// Subscribe to the event
nativeObj.onpropertychangedevent = singlecasthandler;
// Set the value of the property and fire the event
var propValue = 21;
nativeObj.propertyA = 2 * propValue;
}
.NET
在 .NET 語言中,在 C++ 元件中訂閱事件與在 .NET Framework 類別中訂閱事件相同:
//Subscribe to event and call method that causes it to be fired.
private void TestMethod()
{
var objWithEvent = new CppComponent.PropertyExample();
objWithEvent.PropertyChangedEvent += objWithEvent_PropertyChangedEvent;
objWithEvent.PropertyA = 42;
}
//Event handler method
private void objWithEvent_PropertyChangedEvent(object __param0, int __param1)
{
ResultText.Text = "the event was fired and the result is " +
__param1.ToString();
}
為一個事件加入多個事件接聽程式
JavaScript 的 addEventListener 方法可讓多個處理常式訂閱單一事件。
C++
public delegate void SomeHandler(Platform::String^ str);
public ref class LangSample sealed
{
public:
event SomeHandler^ someEvent;
property Platform::String^ PropertyA;
// Method that fires an event
void FireEvent(Platform::String^ str)
{
someEvent(Platform::String::Concat(str, PropertyA->ToString()));
}
//...
};
JavaScript
// Add two event handlers
var multicast1 = function (ev) {
document.getElementById('P11').innerHTML = "Handler 1: " + ev.target;
};
var multicast2 = function (ev) {
document.getElementById('P12').innerHTML = "Handler 2: " + ev.target;
};
var nativeObject = new CppComponent.LangSample();
//Subscribe to the same event
nativeObject.addEventListener("someevent", multicast1);
nativeObject.addEventListener("someevent", multicast2);
nativeObject.propertyA = "42";
// This method should fire an event
nativeObject.fireEvent("The answer is ");
.NET
如上述範例所示,在 C# 中,無論事件處理常式的數目為何,都可以使用 += 運算子來訂閱事件。
列舉
使用 public class enum 可以在 C++ 中定義 Windows 執行階段列舉,這個列舉與標準 C++ 中的範圍列舉相似。
C++
public enum class Direction {North, South, East, West};
public ref class EnumExampleClass sealed
{
public:
property Direction CurrentDirection
{
Direction get(){return m_direction; }
}
private:
Direction m_direction;
};
JavaScript
列舉值在 C++ 與 JavaScript 之間是以整數的形式傳遞。您可以選擇性地將 JavaScript 物件宣告為含有與 C++ 列舉相同的具名值,然後再加以使用,如下所示。
var Direction = { 0: "North", 1: "South", 2: "East", 3: "West" };
//. . .
var nativeObject = new CppComponent.EnumExampleClass();
var curDirection = nativeObject.currentDirection;
document.getElementById('P13').innerHTML =
Direction[curDirection];
.NET
C# 和 Visual Basic 語言都支援列舉。這些語言看到 C++ 公用列舉類別,就像看到 .NET Framework 的列舉一樣。
非同步方法
若要使用其他 Windows 執行階段物件所公開的非同步方法,請使用 task 類別 (並行執行階段)。如需詳細資訊,請參閱 Asychronous Programming in C++和工作平行處理原則 (並行執行階段)。
若要以 C++ 實作非同步方法,請使用 ppltasks.h 中所定義的 create_async 函式。如需詳細資訊,請參閱使用 C++ 為 Windows 市集應用程式建立非同步作業。如需範例,請參閱 逐步解說:以 C++ 建立基本 Windows 執行階段元件,然後從 JavaScript 呼叫該元件。.NET 語言使用 C++ 非同步方法的方式,就如同使用 .NET Framework 中定義的所有非同步方法一樣。
例外狀況
您可以擲回 Windows 執行階段所定義的任何例外狀況型別。您無法從任何 Windows 執行階段例外狀況型別衍生自訂型別。但您可以擲回 COMException,並提供自訂的 HRESULT 以供攔截到例外狀況的程式碼存取。您無法在 COMException 中指定自訂訊息。
偵錯提示
對含有元件 DLL 的 JavaScript 方案進行偵錯時,您可以設定偵錯工具為啟用逐步執行指令碼或是逐步執行元件中的原生程式碼,但不可兩者同時啟用。若要變更設定,請在 [方案總管] 中選取 JavaScript 專案節點,然後依序選擇 [屬性]、[偵錯]、 [偵錯工具類型]。
請務必在封裝設計工具中選取適當的功能。例如,若您嘗試使用 Windows 執行階段 API 開啟檔案,請務必選取封裝設計工具的 [功能] 窗格中的 [文件庫存取] 核取方塊。
如果 JavaScript 程式碼似乎無法辨識元件中的公用屬性或方法,請確定您在 JavaScript 中使用的是 Camel 命名法的大小寫慣例。例如,LogCalc C++ 方法必須當做 JavaScript 中的 logCalc 來參考。
如果從方案中移除 C ++ Windows 執行階段元件專案,也必須手動從 JavaScript 專案中移除該專案的參考。若未執行此動作,後續的偵錯或建置作業將無法執行。之後如有必要,您可以加入 DLL 的組件參考。
請參閱
概念
逐步解說:以 C++ 建立基本 Windows 執行階段元件,然後從 JavaScript 呼叫該元件