Windows 執行階段元件與 C++/CX

注意

本主題可協助您維護 C++/CX 應用程式。 但我們建議您將 C++/WinRT 用於新的應用程式。 C++/WinRT 是完全標準現代的 Windows 執行階段 (WinRT) API 的 C++17 語言投影,僅實作為標頭檔案式程式庫,以及設計用來提供您現代化 Windows API 的第一級存取。 若要了解如何使用 C++/WinRT 建立 Windows 執行階段元件,請參閱使用 C++/WinRT 的 Windows 執行階段元件

本主題說明如何使用 C++/CX 來建立 Windows 執行階段元件,此元件是可從使用任何 Windows 執行階段語言 (C#、Visual Basic、C++ 或 JavaScript) 所建置的通用 Windows 應用程式呼叫的元件。

使用 C++ 建置 Windows 執行階段元件有幾個原因。

  • 在複雜或計算密集型操作中獲得 C++ 的效能優勢。
  • 重複使用已經撰寫和測試的程式碼。

當您建置包含 JavaScript 或.NET 專案以及 Windows 執行階段元件專案的解決方案時,JavaScript 專案檔案和已編譯的DLL 將合併到一個套件中,您可以在模擬器中本機偵錯該套件,也可以在連接裝置上遠端偵錯該套件。 您也可以只將元件專案作為擴充 SDK 發佈。 如需詳細資訊,請參閱建立軟體開發套件

通常,在對 C++/CX 元件進行編碼時,請使用常規 C++ 函式庫和內建類型,但在抽象二進位介面 (ABI) 邊界處除外,在該邊界處您要在另一個.winmd 套件中的程式碼之間傳遞資料。 在那裡,使用 Windows 執行階段類型和 C++/CX 支援的特殊語法來建立和操作這些類型。 此外,在 C++/CX 程式碼中,使用委派和事件等類型來實作可以從元件引發,並在 JavaScript、Visual Basic、C++ 或 C# 中處理的事件。 如需 C++/CX 語法的詳細資訊,請參閱 Visual C++ 語言參考 (C++/CX)

大小寫和命名規則

JavaScript

JavaScript 會區分大小寫。 因此,您必須遵循以下大小寫慣例:

  • 當您參考 C++ 命名空間和類別時,請使用與 C++ 端所用的相同大小寫。
  • 呼叫方法時,即使方法名稱在 C++ 端大寫,也請使用駝峰式大小寫。 例如,C++ 方法 GetDate() 必須從 JavaScript 作為 getDate() 呼叫。
  • 可啟動的類別名稱和命名空間名稱不能包含 UNICODE 字元。

.NET

.NET 語言遵循其標準的大小寫規則。

具現化物件

只有 Windows 執行階段類型可以跨 ABI 邊界傳遞。 如果元件具有 std::wstring 之類的類型作為公用方法中的傳回類型或參數,則編譯器將引發錯誤。 Visual C++ 元件擴充功能 (C++/CX) 內建類型包含常見的純量,例如 int 和 double,以及其 typedef 對等的 int32、float64 等。 如需詳細資訊,請參閱類型系統 (C++/CX)

// 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);
    }

};
//Instantiation in JavaScript (requires "Add reference > Project reference")
var nativeObject = new CppComponent.SampleRefClass();
//Call a method and display result in a XAML TextBlock
var num = nativeObject.LogCalc(21.5);
ResultText.Text = num.ToString();

C++/CX 內建類型、程式庫類型和 Windows 執行階段類型

可啟動類別 (也稱為 ref 類別) 是可以從另一種語言 (例如 JavaScript、C# 或 Visual Basic) 具現化的類別。 若要從另一種語言使用,元件必須至少包含一個可啟動的類別。

Windows 執行階段元件可以包含多個公用可啟動類別,以及僅在該元件內部已知的其他類別。 將 WebHostHidden 屬性套用到 JavaScript 不會顯示的 C++/CX 類型。

所有公用類別必須駐留在與元件中繼資料檔案同名的同一根命名空間中。 例如,名為 A.B.C.MyClass 的類別必須在名為 A.winmd、A.B.winmd 或 A.B.C.winmd 的中繼資料檔案中定義,才能執行個體化。 DLL 的名稱不需符合 .winmd 檔案名稱。

與其他類別一樣,用戶端程式碼使用 new (Visual Basic 中的 New) 關鍵字建立元件的執行個體。

可啟動類別必須宣告為 public ref class sealedref class 關鍵字告訴編譯器將類別建立為 Windows 執行階段相容類型,而 sealed 關鍵字指定該類別不能繼承。 Windows 執行階段目前不支援通用繼承模型; 有限的繼承模型支援建立自訂 XAML 控制項。 如需詳細資訊,請參閱 Ref 類別與結構 (C++/CX)

對於 C++/CX,所有數值基元都在預設命名空間中定義。 Platform 命名空間包含 Windows 執行階段類型系統特定的 C++/CX 類別。 其中包括 Platform::String 類別和 Platform::Object 類別。 特定的集合類型 (例如 Platform::Collections::Map 類別和 Platform::Collections::Vector 類別) 在 Platform::Collections 命名空間中定義。 這些類型實作的公用介面在 Windows::Foundation::Collections 命名空間 (C++/CX) 中定義。 JavaScript、C# 和 Visual Basic 所使用的正是這些介面類型。 如需詳細資訊,請參閱類型系統 (C++/CX)

傳回內建類型值的方法

    // #include <valarray>
public:
    double LogCalc(double input)
    {
        // Use C++ standard library as usual.
        return std::log(input);
    }
//Call a method
var nativeObject = new CppComponent.SampleRefClass;
var num = nativeObject.logCalc(21.5);
document.getElementById('P2').innerHTML = num;

傳回自訂值結構的方法

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;}
        }
    };
}

若要跨 ABI 傳遞使用者定義的值結構,請定義 JavaScript 物件,其成員與 C++/CX 中定義的值結構成員相同。 然後,您可以將該物件做為參數傳遞給 C++/CX 方法,以便將該物件隱式轉換為 C++/CX 類型。

// 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 的類別 (未顯示)。

在 .NET 語言中,您只需建立 C++/CX 元件中定義類型的變數。

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++/CX 公用 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++/CX ref 類別中的多載,就像在其他 .NET 類別中一樣。

Datetime

在 Windows 執行階段中,Windows::Foundation::DateTime 物件只是一個帶正負號的 64 位元整數,表示 1601 年 1 月 1 日之前或之後的每 100 奈秒間隔數。 Windows:Foundation::DateTime 物件上沒有方法。 相反,每種語言都以該語言本身的方式投影 DateTime:JavaScript 中的 Date 物件以及 .NET 中的 System.DateTime 和 System.DateTimeOffset 類型。

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;
    }
};

當您將 DateTime 值從 C++/CX 傳遞到 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 語言將 System.DateTime 傳遞給 C++/CX 元件時,該方法將其作為 Windows::Foundation::DateTime 接受。 當元件將 Windows::Foundation::DateTime 傳遞給 .NET 方法時,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();
}

集合和陣列

集合一律會作為 Windows 執行階段類型 (例如 Windows::Foundation::Collections::IVector^ 和 Windows::Foundation::Collections::IMap^) 的控點跨 ABI 邊界傳遞。 例如,如果您傳回 Platform::Collections::Map 的控點,它會隱式轉換為 Windows::Foundation::Collections::IMap^。 集合介面在命名空間中定義,該命名空間與提供具體實作的 C++/CX 類別不同。 JavaScript 和.NET 語言會使用這些介面。 如需詳細資訊,請參閱集合 (C++/CX) 以及陣列和 WriteOnlyArray (C++/CX)

傳遞 IVector

// 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;
}
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 語言會將 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

// #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;
}
// 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 語言請參閱 IMap 和 IDictionary<K、V>。

private void GetDictionary()
{
    var nativeObject = new CppComponent.CollectionExample();
    IDictionary<int, string> d = nativeObject.GetMap();
    ResultText.Text += d[2].ToString();
}

屬性

C++/CX 元件擴充功能中的公用 ref 類別透過使用 property 關鍵字將公用資料成員做為屬性公開。 該概念與 .NET 屬性相同。 平凡屬性類似於資料成員,因為其功能是隱式的。 非平凡屬性具有明確的 get 和 set 存取子,以及一個命名的私用變數,該變數是該值的「備份存放區」。 在此範例中,私用成員變數 _propertyAValue 是 PropertyA 的備份存放區。 屬性可以在其值變更時觸發事件,並且用戶端應用程式可以註冊以接收該事件。

//Properties
public delegate void PropertyChangedHandler(Platform::Object^ sender, int arg);
public ref class PropertyExample  sealed
{
public:
    PropertyExample(){}

    // Event that is fired when PropertyA 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;
};
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 語言會存取本機 C++/CX 物件上的屬性,就像存取 .NET 物件上的屬性一樣。

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;

}

委派和事件

委派是代表函式物件的 Windows 執行階段類型。 您可以將委派與事件、回呼和非同步方法呼叫結合使用,以指定稍後要執行的動作。 與函式物件一樣,委派透過使編譯器能夠驗證函式的傳回類型和參數類型來提供類型安全性。 委派的宣告類似函式簽章,實作類似類別定義,而叫用類似函式叫用。

新增事件接聽程式

您可以使用 event 關鍵字來宣告指定委派類型的公用成員。 用戶端程式碼可透過使用特定語言提供的標準機制來訂閱事件。

public:
    event SomeHandler^ someEvent;

此範例使用與先前屬性區段相同的 C++ 程式碼。

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 語言中,訂閱 C++ 元件中的事件與訂閱 .NET 類別中的事件相同:

//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 方法,可讓多個處理常式訂閱單一事件。

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()));
    }
    //...
};
// 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 ");

在 C# 中,任意數量的事件處理程序都可以使用 += 運算子來訂閱事件,如上例所示。

列舉

C++/CX 中的 Windows 執行階段列舉是透過使用公用類別列舉來宣告的; 它類似於標準 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;
};

列舉值會以整數的形式在 C++/CX 和 JavaScript 之間傳遞。 您可以選擇宣告一個包含與 C++/CX 列舉相同命名值的 JavaScript 物件,並以以下方式使用它。

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];

C# 和 Visual Basic 都提供對列舉的語言支援。 這些語言看到 C++ 公用列舉類別就像看到 .NET 列舉一樣。

非同步方法

若要使用其他 Windows 執行階段物件公開的非同步方法,請使用工作類別 (並行執行階段)。 有關詳細資訊,請參閱工作平行處理原則 (並行執行階段_

若要在 C++/CX 中實作非同步方法,請使用 ppltasks.h 中定義的 create_async 函式。 如需詳細資訊,請參閱使用 C++/CX 為 UWP 應用程式建立非同步作業。 如需範例,請參閱建立 C++/CX Windows 執行階段元件,並從 JavaScript 或 C# 呼叫該元件的逐步解說。 .NET 語言使用 C++/CX 非同步方法,就像使用 .NET 中定義的任何非同步方法一樣。

例外狀況

您可以擲回 Windows 執行階段定義的任何異常類型。 您無法從任何 Windows 執行階段 例外狀況類型衍生自訂類型。 但是,您可以擲回 COMException,並提供可由擷取例外狀況之程式碼存取的自訂 HRESULT。 無法在 COMException 中指定自訂訊息。

偵錯秘訣

當您偵錯具有元件 DLL 的 JavaScript 解決方案時,您可以將偵錯工具設定為啟用逐步執行指令碼或逐步執行元件中的原生程式碼,但這兩個不能同時執行。 若要變更設定,請在方案總管中選擇 JavaScript 專案節點,然後選擇 [屬性]、[偵錯]、[偵錯工具類型]。

請務必在套件設計工具中選取適當的功能。 例如,如果您嘗試使用 Windows 執行階段 API 開啟使用者圖片庫中的映像檔,請務必選取清單設計器的 [功能] 窗格中的 [圖片庫] 核取方塊。

如果您的 JavaScript 程式碼無法識別元件中的公用屬性或方法,請務必在 JavaScript 中使用駝峰式大小寫。 例如,LogCalc C++/CX 方法必須在 JavaScript 中參考為 logCalc。

如果從解決方案中刪除 C++/CX Windows 執行階段元件專案,則也必須從 JavaScript 專案中手動移除專案參考。 如果不這樣做,後續的偵錯或建置作業就無法進行。 如果需要,您可以將組件參考新增到 DLL。