Windows 執行階段元件與 C# 和 Visual Basic

您可以使用受控程式碼自行建立 Windows 執行階段類型,並將其封裝在 Windows 執行階段元件中。 您可以在使用 C++、JavaScript、Visual Basic 或 C# 撰寫的通用 Windows 平台 (UWP) 應用程式中使用元件。 本主題將概述建立元件的規則,並就某些層面討論 .NET 對於 Windows 執行階段的支援。 一般而言,該支援依設計應可讓 .NET 程式設計人員清楚理解。 但是,當您建立要與 JavaScript 或 C++ 搭配使用的元件時,必須了解這些語言對於 Windows 執行階段的支援方式有何差異。

如果要建立僅在以 Visual Basic 或 C# 撰寫的 UWP 應用程式中使用的元件,且元件不包含 UWP 控制項,請考慮使用類別庫範本,而不是 Microsoft Visual Studio 中的 Windows 執行階段元件專案範本。 簡單的類別庫限制較少。

注意

對於以 .NET 6 或更新版本撰寫傳統型應用程式的 C# 開發人員,請使用 C#/WinRT 來撰寫 Windows 執行階段元件。 請參閱使用 C#/WinRT 撰寫 Windows 執行階段元件 (部分機器翻譯)。

在 Windows 執行階段元件中宣告類型

在內部,元件中的 Windows 執行階段類型可以使用 UWP 應用程式中允許的任何 .NET 功能。 有關詳細資訊,請參閱適用於 UWP 應用程式的 .NET

在外部,類型的成員只能公開 Windows 執行階段類型的參數和傳回值。 以下清單說明了對 Windows 執行階段元件公開的 .NET 類型限制。

  • 元件中所有公用類型和成員的欄位、參數和傳回值必須是 Windows 執行階段類型。 此限制包括您撰寫的 Windows 執行階段類型,以及 Windows 執行階段本身提供的類型。 它還包括許多 .NET 類型。 包含這些類型是 .NET 提供的支援的一部分,以實現在受控程式碼中自然使用 Windows 執行階段 — 您的程式碼似乎使用熟悉的 .NET 類型,而不是基礎 Windows 執行階段類型。 例如,您可以使用.NET 基本類型 (例如 Int32Double)、特定基礎類型 (例如 DateTimeOffsetUri) 以及一些常用的泛型介面類型,例如 IEnumerable<T> (Visual Basic 中的 IEnumerable(Of T)) 和 IDictionary<TKey,TValue>。 請注意,這些泛型類型的類型引數必須是 Windows 執行階段類型。 這將會在本主題後面的將 Windows 執行階段類型傳遞到受控程式碼將受控類型傳遞到 Windows 執行階段部分中進行討論。

  • 公用類別和介面可以包含方法、屬性和事件。 您可以為事件宣告委派,或使用 EventHandler<T> 委派。 公用類別或介面不能:

    • 是泛型。
    • 實作不是 Windows 執行階段介面的介面 (但是,您可以建立自己的 Windows 執行階段介面並進行實作)。
    • 衍生自 Windows 執行階段以外的類型,例如 System.ExceptionSystem.EventArgs
  • 所有公用類型都必須具有與組件名稱相符的根命名空間,且組件名稱不得以「Windows」開頭。

    提示。 預設情況下,Visual Studio 專案的命名空間名稱與組件名稱相符。 在 Visual Basic 中,此預設命名空間的命名空間陳述式不會顯示在程式碼中。

  • 公用結構不能有除公用欄位之外的任何成員,且這些欄位必須是值類型或字串。

  • 公用類別必須是密封的 (在 Visual Basic 中為 NotInheritable)。 如果您的程式設計模型需要多態性,那麼您可以建立一個公用介面,並在必須是多態性的類別上實作該介面。

偵錯元件

如果您的 UWP 應用程式和元件都是使用受控程式碼建置的,則可以同時偵錯它們。

當使用 C++ 將元件作為 UWP 應用程式的一部分進行測試時,可以同時偵錯受控程式碼和原生程式碼。 預設只有原生代碼。

偵錯原生 C++ 程式碼和受控程式碼

  1. 開啟 Visual C++ 專案的捷徑功能表,然後選擇屬性
  2. 在屬性頁面的設定屬性下,選擇偵錯
  3. 選擇偵錯工具類型,然後在下拉式清單方塊框中將僅限原生變更為混合 (受控和原生)。 選擇確定
  4. 在原生程式碼和受控程式碼中設定斷點。

當您使用 JavaScript 將元件作為 UWP 應用程式的一部分進行測試時,預設解決方案處於 JavaScript 偵錯模式。 在 Visual Studio 中,無法同時偵錯 JavaScript 和受控程式碼。

偵錯受控程式碼而不是 JavaScript

  1. 開啟 JavaScript 專案的捷徑功能表,然後選擇屬性
  2. 在屬性頁面的設定屬性下,選擇偵錯
  3. 選擇偵錯工具類型,然後在下拉式清單方塊中將僅限指令碼變更為僅限受控。 選擇確定
  4. 在受控程式碼中設定斷點,並像往常一樣進行偵錯。

將 Windows 執行階段類型傳遞至受控程式碼

如同先前在 Windows 執行階段元件中宣告類型一節中所提到的,特定 .NET 類型可以出現在公用類別成員的簽章中。 這屬於 .NET 提供的支援,以實現在受控程式碼中自然使用 Windows 執行階段。 其包括基本類型以及一些類別和介面。 當從 JavaScript 或 C++ 程式碼使用元件時,了解 .NET 類型對呼叫者的顯示方式非常重要。 有關 JavaScript 的範例,請參閱逐步說明如何建立 C# 或 Visual Basic Windows 執行階段元件,並從 JavaScript 呼叫它。 本節討論常用的類型。

在 .NET 中,基本類型 (例如 Int32 結構) 具有許多有用的屬性和方法,例如 TryParse 方法。 相較之下,Windows 執行階段中的基本類型和結構就只有欄位。 當您將這些類型傳遞給受控程式碼時,將會顯示成 .NET 類型,而您可以如常使用這些 .NET 類型的屬性和方法。 以下清單總結了 IDE 中自動進行的替換:

  • 如果是 Windows 執行階段基本類型 Int32Int64SingleDoubleBooleanString (Unicode 字元的不可變集合)、EnumUInt32UInt64Guid,請使用 System 命名空間中的同名類型。
  • 如果是 UInt8,請使用 System.Byte
  • 如果是 Char16,請使用 System.Char
  • 如果是 IInspectable 介面,請使用 System.Object

如果 C# 或 Visual Basic 為上述任何類型提供語言關鍵字,則您可以改用該語言關鍵字。

除了基本類型之外,一些基本的、常用的 Windows 執行階段類型也作為其 .NET 對等項出現在受控程式碼中。 例如,假設您的 JavaScript 程式碼使用 Windows.Foundation.Uri 類別,並且您希望將其傳遞給 C# 或 Visual Basic 方法。 受控程式碼中的對等類型是 .NET System.Uri 類別,它是用於方法參數的類型。 您可以判斷 Windows 執行階段類型何時顯示為 .NET 類型,因為當您撰寫受控程式碼時,Visual Studio 中的 IntelliSense 會隱藏 Windows 執行階段類型,並顯示對等的 .NET 類型。 (通常這兩種類型具有相同的名稱。但請注意,Windows.Foundation.DateTime 結構在受控程式碼中顯示為 System.DateTimeOffset,而不是 System.DateTime。)

對於某些常用的集合類型,對應是在 Windows 執行階段類型實作的介面與對應 .NET 類型實作的介面之間進行的。 與上面提到的類型一樣,您可以使用 .NET 類型來宣告參數類型。 這會隱藏類型之間的一些差異,並使撰寫 .NET 程式碼更加自然。

下表列出了最常見的泛型介面類型,以及其他常見的類別和介面對應。 有關 .NET 對應的 Windows 執行階段類型完整清單,請參閱 Windows 執行階段類型的 .NET 對應

Windows 執行階段 .NET
IIterable<T> IEnumerable<T>
IVector<T> IList<T>
IVectorView<T> IReadOnlyList<T>
IMap<K, V> IDictionary<TKey, TValue>
IMapView<K, V> IReadOnlyDictionary<TKey, TValue>
IKeyValuePair<K, V> KeyValuePair<TKey, TValue>
IBindableIterable IEnumerable
IBindableVector IList
Windows.UI.Xaml.Data.INotifyPropertyChanged System.ComponentModel.INotifyPropertyChanged
Windows.UI.Xaml.Data.PropertyChangedEventHandler System.ComponentModel.PropertyChangedEventHandler
Windows.UI.Xaml.Data.PropertyChangedEventArgs System.ComponentModel.PropertyChangedEventArgs

當一種類型實作多個介面時,您可以使用它實作的任何介面作為成員的參數類型或傳回類型。 例如,可以將 Dictionary<int, string> (在 Visual Basic 中為 Dictionary(Of Integer, String)) 作為 IDictionary<int, string>IReadOnlyDictionary<int, string>IEnumerable<System.Collections.Generic.KeyValuePair<TKey, TValue>> 傳遞或傳回。

重要

JavaScript 使用受控類型實作的介面清單中第一個出現的介面。 例如,如果您向 JavaScript 程式碼傳回 Dictionary<int, string>,則無論您指定哪個介面作為傳回類型,它都會顯示為 IDictionary<int, string>。 這代表如果第一個介面不包含出現在後續介面上的成員,則該成員就不會顯示在 JavaScript 中。

在 Windows 執行階段中,IMap<K, V>IMapView<K, V> 透過使用 IKeyValuePair 進行迭代。 當您將它們傳遞給受控程式碼時,它們會顯示為 IDictionary<TKey, TValue>IReadOnlyDictionary<TKey, TValue>,因此您自然可以使用 System.Collections.Generic.KeyValuePair<TKey, TValue> 來列舉它們。

介面在 Managed 程式碼中的顯示方式,會影響到實作這些介面之型别的顯示方式。 例如,PropertySet 類別實作 IMap<K, V>,其在受控程式碼中顯示為 IDictionary<TKey, TValue>PropertySet 會以實作了 IDictionary<TKey, TValue> (而不是 IMap<K, V>) 的形態出現,因此在受控程式碼中,其看似具有 Add 方法 (此方法的行為類似於 .NET Framework 字典上的 Add 方法。 它看起來並沒有 Insert 方法。 您可以在主題逐步說明如何建立 C# 或 Visual Basic Windows 執行階段元件,並從 JavaScript 呼叫它中查看此範例。

將受控類型傳遞給 Windows 執行階段

如上一節所述,某些 Windows 執行階段類型可以在元件成員的簽章中顯示為 .NET 類型,或在 IDE 中使用它們時在 Windows 執行階段成員的簽章中顯示為 .NET 類型。 當您將 .NET 類型傳遞給這些成員,或將它們作為元件成員的傳回值時,它們會在另一端的程式碼中顯示為對應的 Windows 執行階段類型。 有關從 JavaScript 呼叫元件時所產生影響的範例,請參閱逐步說明如何建立 C# 或 Visual Basic Windows 執行階段元件,並從 JavaScript 呼叫它中的「從元件傳回受控類型」一節。

多載方法

在 Windows 執行階段中,方法可以多載。 但是,如果使用相同數量的參數宣告多個多載,則必須將 Windows.Foundation.Metadata.DefaultOverloadAttribute 屬性套用至這些多載之一。 該多載是唯一您可以從 JavaScript 呼叫的多載。 例如,在下列程式碼中,採用 int (Visual Basic 中的 Integer) 的多載是預設多載。

public string OverloadExample(string s)
{
    return s;
}

[Windows.Foundation.Metadata.DefaultOverload()]
public int OverloadExample(int x)
{
    return x;
}
Public Function OverloadExample(ByVal s As String) As String
    Return s
End Function

<Windows.Foundation.Metadata.DefaultOverload> _
Public Function OverloadExample(ByVal x As Integer) As Integer
    Return x
End Function

[重要] JavaScript 可讓您將任何值傳遞給 OverloadExample,並將該值強制轉換為參數所需的類型。 您可以使用「forty-two」、「42」或 42.3 呼叫 OverloadExample,但這些值都會傳遞給預設多載。 上一個範例中的預設多載會分別傳回 0、42 和 42。

您無法將 DefaultOverloadAttribute 特性套用到建構函式。 類別中的所有建構函式都必須具有不同數量的參數。

實作 IStringable

從 Windows 8.1 開始,Windows 執行階段包含 IStringable 介面,其單一方法 IStringable.ToString 提供的基本格式支援與 Object.ToString 提供的差不多。 如果您選擇在 Windows 執行階段元件中匯出的公用受控類型中實作 IStringable,則適用以下限制:

  • 您只能在「類別實作」關係中定義 IStringable 介面,例如 C# 中的以下程式碼:

    public class NewClass : IStringable
    

    或以下 Visual Basic 程式碼:

    Public Class NewClass : Implements IStringable
    
  • 您無法在介面上實作 IStringable

  • 您不能將參數宣告為 IStringable 類型。

  • IStringable 不能是方法、屬性或欄位的回傳類型。

  • 您無法透過使用以下方法定義來隱藏基底類別中的 IStringable 實作:

    public class NewClass : IStringable
    {
       public new string ToString()
       {
          return "New ToString in NewClass";
       }
    }
    

    相反地,IStringable.ToString 實作必須一律覆寫基底類別實作。 您只能藉由針對強型別類別執行個體叫用 ToString 實作來隱藏該實作。

注意

在各種條件下,從原生程式碼呼叫實作 IStringable 或隱藏其 ToString 實作的受控類型可能會產生未預期的行為。

非同步作業

若要在元件中實作非同步方法,請將「Async」新增至方法名稱結尾,並傳回表示非同步作業的 Windows 執行階段介面之一:IAsyncActionIAsyncActionWithProgress<TProgress>IAsyncOperation<TResult>IAsyncOperationWithProgress<TResult, TProgress>

您可以使用 .NET 工作 (Task 類別和通用 Task<TResult> 類別) 來實作非同步方法。 您必須傳回表示正在進行作業的工作,例如從用 C# 或 Visual Basic 撰寫的非同步方法傳回的工作,或從 Task.Run 方法傳回的工作。 如果使用建構函式建立工作,則必須在傳回工作之前呼叫其 Task.Start 方法。

使用 await (Visual Basic 中的 Await) 的方法需要 async 關鍵字 (Visual Basic 中的 Async)。 如果從 Windows 執行階段元件公開此類方法,請將 async 關鍵字套用於傳遞給 Run 方法的委派。

對於不支援取消或進度報告的非同步動作和作業,可以使用 WindowsRuntimeSystemExtensions.AsAsyncActionAsAsyncOperation<TResult> 擴充方法,將工作包裝在適當的介面中。 例如,以下程式碼透過使用 Task.Run<TResult> 方法來實作非同步方法,以啟動工作。 AsAsyncOperation<TResult> 擴充方法會將工作作為 Windows 執行階段非同步作業傳回。

public static IAsyncOperation<IList<string>> DownloadAsStringsAsync(string id)
{
    return Task.Run<IList<string>>(async () =>
    {
        var data = await DownloadDataAsync(id);
        return ExtractStrings(data);
    }).AsAsyncOperation();
}
Public Shared Function DownloadAsStringsAsync(ByVal id As String) _
     As IAsyncOperation(Of IList(Of String))

    Return Task.Run(Of IList(Of String))(
        Async Function()
            Dim data = Await DownloadDataAsync(id)
            Return ExtractStrings(data)
        End Function).AsAsyncOperation()
End Function

以下 JavaScript 程式碼顯示如何使用 WinJS.Promise 物件呼叫該方法。 非同步呼叫完成時,將執行傳遞給 then 方法的函式。 stringList 參數包含 DownloadAsStringAsync 方法傳回的字串清單,並且該函式會執行任何必要的處理。

function asyncExample(id) {

    var result = SampleComponent.Example.downloadAsStringAsync(id).then(
        function (stringList) {
            // Place code that uses the returned list of strings here.
        });
}

對於支援取消或進度報告的非同步操作和操作,請使用 AsyncInfo 類別產生已啟動的工作,並將工作的取消和進度報告功能與相應 Windows 執行階段介面的取消和進度報告功能掛鉤。 有關支援取消和進度報告的範例,請參閱建立 C# 或 Visual Basic Windows 執行階段元件,並從 JavaScript 呼叫該元件的逐步解說

請注意,即使您的非同步方法不支援取消或進度報告,您也可以使用 AsyncInfo 類別的方法。 如果使用 Visual Basic lambda 函式或 C# 匿名方法,請不要為權杖和 IProgress<T> 介面提供參數。 如果您使用 C# lambda 函式,請提供權杖參數,但忽略它。 先前的範例使用 AsAsyncOperation<TResult> 方法,當您改用 AsyncInfo.Run<TResult>(Func<CancellationToken, Task<TResult>>) 方法多載時,看起來會像下方這樣。

public static IAsyncOperation<IList<string>> DownloadAsStringsAsync(string id)
{
    return AsyncInfo.Run<IList<string>>(async (token) =>
    {
        var data = await DownloadDataAsync(id);
        return ExtractStrings(data);
    });
}
Public Shared Function DownloadAsStringsAsync(ByVal id As String) _
    As IAsyncOperation(Of IList(Of String))

    Return AsyncInfo.Run(Of IList(Of String))(
        Async Function()
            Dim data = Await DownloadDataAsync(id)
            Return ExtractStrings(data)
        End Function)
End Function

如果您建立可選擇支援取消或進度報表的非同步方法,請考慮新增沒有取消權杖或 IProgress<T> 介面參數的多載。

擲回例外狀況

您可以擲回適用於 Windows 應用程式的 .NET 中包含的任何例外狀況類型。 您無法在 Windows 執行階段元件中宣告自己的公用例外狀況類型,但可以宣告並擲回非公用類型。

如果您的元件未處理例外狀況,則呼叫您元件的程式碼中會引發相應的例外狀況。 例外狀況向呼叫者顯示的方式取決於呼叫語言支援 Windows 執行階段的方式。

  • 在 JavaScript 中,例外狀況會顯示為物件,其中例外狀況訊息會由堆疊追蹤取代。 在 Visual Studio 中偵錯應用程式時,您可以看到偵錯工具例外狀況對話方塊中顯示的原始訊息文字,其標識為「WinRT 資訊」。 您無法從 JavaScript 程式碼存取原始訊息文字。

    提示。 目前,堆疊追蹤包含受控例外狀況類型,但不建議剖析追蹤來識別例外狀況類型。 而是使用本節後面將說明的 HRESULT 值。

  • 在 C++ 中,例外狀況會顯示為平台例外狀況。 如果受控例外狀況的 HResult 屬性可以對應到特定平台例外狀況的 HRESULT,則使用特定例外狀況; 否則,將擲回 Platform::COMException 例外狀況。 受控例外狀況的訊息文字不可用於 C++ 程式碼。 如果擲回特定平台例外狀況,則會顯示該例外狀況類型的預設訊息文字; 否則,不會出現任何訊息文字。 請參閱例外狀況 (C++/CX)

  • 在 C# 或 Visual Basic 中,例外狀況是一般的受控例外狀況。

當您從元件中擲回例外狀況時,您可以透過拋出其 HResult 屬性值專屬於您元件的非公用例外狀況類型,使 JavaScript 或 C++ 呼叫者更輕鬆地處理例外狀況。 JavaScript 呼叫者可透過例外狀況物件的 number 屬性使用 HRESULT,C++ 呼叫者可透過 COMException::HResult 屬性使用 HRESULT。

注意

對 HRESULT 使用負值。 正值會解釋為成功,且 JavaScript 或 C++ 呼叫者中不會擲回任何例外狀況。

宣告和引發事件

當您宣告用於保存事件資料的類型時,請從 Object 而不是從 EventArgs 衍生,因為 EventArgs 不是 Windows 執行階段類型。 使用 EventHandler<TEventArgs> 作為事件類型,並使用事件引數類型作為泛型類型引數。 就像在 .NET 應用程式中一樣引發事件。

當從 JavaScript 或 C++ 使用 Windows 執行階段元件時,事件會遵循這些語言預期的 Windows 執行階段事件模式。 當您使用 C# 或 Visual Basic 中的元件時,該事件將顯示為一般 .NET 事件。 可在建立 C# 或 Visual Basic Windows 執行階段元件,並從 JavaScript 呼叫該元件的逐步解說中找到範例。

如果實作自訂事件存取子 (在 Visual Basic 中使用 Custom 關鍵字宣告事件),則必須在實作中遵循 Windows 執行階段事件模式。 請參閱 Windows 執行階段元件中的自訂事件和事件存取子。 請注意,當您從 C# 或 Visual Basic 程式碼處理該事件時,它仍然顯示為一般 .NET 事件。

下一步

建立供自己使用的 Windows 執行階段元件後,您可能會發現它封裝的功能對其他開發人員很有用。 您有兩種選擇來封裝元件,以發佈給其他開發人員。 請參閱發佈受控 Windows 執行階段元件

有關 Visual Basic 和 C# 語言功能以及 Windows 執行階段的 .NET 支援的詳細資訊,請參閱 Visual BasicC# 文件。

疑難排解

徵兆 補救方法
在 C++/WinRT 應用程式中,當取用使用 XAML 的 C# Windows 執行階段元件時,編譯器會產生形式為「'MyNamespace_XamlTypeInfo': 不是 'winrt::MyNamespace' 成員」的錯誤,其中 MyNamespace 是 Windows 執行階段元件命名空間的名稱。 在取用 C++/WinRT 應用程式的 pch.h 中,視適當情況新增 #include <winrt/MyNamespace.MyNamespace_XamlTypeInfo.h> 以取代 MyNamespace