用 C# 和 Visual Basic 创建 Windows 运行时组件

有了 .NET Framework 4.5,就可以使用托管代码创建自己的 Windows 运行时类型,这些类型打包在 Windows 运行时组件中。可以通过 C++、JavaScript、Visual Basic 或 C# 在 Windows 应用商店应用程序中使用你的组件。本文概述了创建组件的规则,并讨论了 .NET framework 对 Windows 运行时的支持的某些方面。通常,该支持设计为对 .NET Framework 程序员透明。但是,在创建用于 JavaScript 或 C++ 的组件时,需要注意这些语言对 Windows 运行时的支持方式存在差别。

备注

如果要创建的组件仅用于带 Visual Basic 或 C# 的 Windows 应用商店应用程序,并且该组件不包含 Windows 应用商店控件,请考虑使用“类库(Windows Store 应用程序)”模板而不是“Windows 运行时组件”模板。简单类库所受限制较少。

本文包含以下几个部分:

  • 在 Windows 运行时组件中声明类型

  • 调试组件

  • 将 Windows 运行时类型传递给托管代码

  • 将托管类型传递给 Windows 运行时

  • 传递数组

  • 重载方法

  • 异步操作

  • 引发异常

  • 声明和引发事件

在 Windows 运行时组件中声明类型

在内部,组件中的 Windows 运行时类型可以使用 Windows 应用商店应用程序中允许的任何 .NET Framework 功能。(有关更多信息,请参见.NET for Windows Store 应用程序概述。)在外部,类型的成员只能为其参数和返回值公开 Windows 运行时类型。以下列表介绍了从 Windows 运行时组件公开的 .NET Framework 类型所受的限制。

  • 组件中所有公共类型和成员的字段、参数和返回值都必须为 Windows 运行时类型。

    此限制包括你创建的 Windows 运行时类型以及 Windows 运行时本身提供的类型。它还包括许多 .NET Framework 类型。将这些类型包括进来属于 .NET Framework 提供的支持的一部分,目的是在托管代码中自然地使用 Windows 运行时:你的代码会使用熟悉的 .NET Framework 类型,而不是基础的 Windows 运行时类型。例如,可以使用 .NET Framework 基元类型(如 Int32 和 Double)、某些基本类型(如 DateTimeOffset 和 Uri)以及一些常用的泛型接口类型,如 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 中,代码中不会显示用于此默认命名空间的 Namespace 语句。

  • 除了公共字段外,公共结构不能有任何成员,并且这些字段必须为值类型或字符串。

  • 公共类必须是 sealed(在 Visual Basic 中为 NotInheritable)。如果编程模型要求多态性,则可创建一个公共接口并在必须为多态性的类中实现该接口。

调试组件

如果 Windows 应用商店应用程序和组件都是用托管代码生成的,则可同时对其进行调试。

在使用 C++ 将组件作为 Windows 应用商店应用程序的一部分进行测试时,可以同时调试托管代码和本机代码。默认为仅调试本机代码。

同时调试本机 C++ 代码和托管代码

  1. 打开 Visual C++ 项目的快捷菜单,并选择**“属性”**。

  2. 在属性页中的**“配置属性”下,选择“调试”**。

  3. 选择**“调试器类型”并在下拉列表框中将“仅限本机”更改为“混合(托管和本机)”。选择“确定”**。

  4. 在本机和托管代码中设置断点。

在使用 JavaScript 将组件作为 Windows 应用商店应用程序的一部分进行测试时,默认情况下该解决方案处于 JavaScript 调试模式。在 Visual Studio 2012 和 Visual Studio Express 2012 for Windows 8 中,不能同时调试 JavaScript 和托管代码。

调试托管代码而不是 JavaScript

  1. 打开 JavaScript 项目的快捷菜单,并选择**“属性”**。

  2. 在属性页中的**“配置属性”下,选择“调试”**。

  3. 选择**“调试器类型”并在下拉列表框中将“仅限脚本”更改为“仅限托管”。选择“确定”**。

  4. 在托管代码中设置断点并像通常那样调试。

将 Windows 运行时类型传递给托管代码

如前面的在 Windows 运行时组件中声明类型部分中所述,某些 .NET Framework 类型可以出现在公共类的成员签名中。这是 .NET Framework 提供的支持的一部分,目的是在托管代码中允许自然地使用 Windows 运行时。它包括基元类型以及一些类和接口。在从 JavaScript 或 C++ 代码中使用组件时,了解 .NET Framework 类型如何向调用方显示是非常重要的。有关使用 JavaScript 的示例,请参见演练:用 C# 或 Visual Basic 创建一个简单的组件,然后从 JavaScript 中调用该组件。本节讨论常用类型。

在 .NET Framework 中,基元类型(如 Int32 结构)具有许多有用的属性和方法,如 TryParse 方法。相比之下,Windows 运行时中的基元类型和结构只有字段。这些类型在传递给托管代码时,会显示为 .NET Framework 类型,因此,你可以像通常那样使用 .NET Framework 类型的属性和方法。以下列表总结了在 IDE 中自动进行的替换:

  • 对于 Windows 运行时基元 Int32、Int64、Single、Double、Boolean、String(Unicode 字符的不可变集合)、Enum、UInt32、UInt64 和 Guid,请使用 System 命名空间中的同名类型。

  • 对于 UInt8,请使用 System.Byte。

  • 对于 Char16,请使用 System.Char。

  • 对于 IInspectable 接口,请使用 System.Object。

如果 C# 或 Visual Basic 为上述任意类型提供语言关键字,则可改用该语言关键字。

除了基元类型外,某些基本的常用 Windows 运行时类型会在托管代码中显示为其 .NET Framework 等效类型。例如,假定你的 JavaScript 代码使用 Windows.Foundation.Uri 类,并且你要将其传递给 C# 或 Visual Basic 方法。托管代码中的等效类型是 .NET Framework System.Uri 类,并且这是用于方法参数的类型。你可以判断 Windows 运行时类型何时显示为 .NET Framework 类型,因为 Visual Studio 中的 IntelliSense 会在你编写托管代码时隐藏 Windows 运行时类型,并显示等效的 .NET Framework 类型。(通常,这两种类型的名称相同。但请注意,Windows.Foundation.DateTime 结构在托管代码中显示为 System.DateTimeOffset,而不是 System.DateTime。)

对于某些常用的集合类型,映射存在于 Windows 运行时类型实现的接口与相应的 .NET Framework 类型实现的接口之间。与前面提及的类型相似,可使用 .NET Framework 类型声明参数类型。这会隐藏类型之间的一些差异并使编写 .NET Framework 代码变得更加自然。下表列出了最常用的泛型接口类型,以及其他常用的类和接口映射。有关 .NET Framework 映射的 Windows 运行时类型的完整列表,请参见 Windows 运行时类型的 .NET Framework 映射

Windows 运行时

.NET Framework

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 使用托管类型实现的、在接口列表中显示在最前面的接口。例如,如果将 Dictionary<int, string> 返回给 JavaScript 代码,那么无论你将哪个接口指定为返回类型,它都显示为 IDictionary<int, string>。这意味着,如果第一个接口不包括在后续接口中出现的成员,该成员将对 JavaScript 不可见。

在 Windows 运行时中,使用 IKeyValuePair 来循环访问 IMap<K, V> 和 IMapView<K, V>。在将其传递给托管代码时,它们显示为 IDictionary<TKey, TValue> 和 IReadOnlyDictionary<TKey, TValue>,因此你会自然地使用 System.Collections.Generic.KeyValuePair<TKey, TValue> 来枚举它们。

接口在托管代码中的显示方式会影响实现这些接口的类型的显示方式。例如,PropertySet 类实现 IMap<K, V>,后者在托管代码中显示为 IDictionary<TKey, TValue>。PropertySet 显示为已实现 IDictionary<TKey, TValue> 而不是 IMap<K, V>,因此在托管代码中,它显示为具有 Add 方法,其行为类似于 .NET Framework 字典中的 Add 方法。它不会显示为具有 Insert 方法。可在演练:用 C# 或 Visual Basic 创建一个简单的组件,然后从 JavaScript 中调用该组件一文中查看此示例。

将托管类型传递给 Windows 运行时

如上一节所述,有些 Windows 运行时类型会在组件成员的签名或 Windows 运行时成员的签名(当你在 IDE 中使用时)中显示为 .NET Framework 类型。在将 .NET Framework 类型传递给这些成员或将其用作组件成员的返回值时,它们会对另一端的代码显示为对应的 Windows 运行时类型。从 JavaScript 中调用组件时,这可能会产生一定的影响,如需此方面的示例,请参见演练:用 C# 或 Visual Basic 创建一个简单的组件,然后从 JavaScript 中调用该组件中的“从组件中返回托管类型”。

传递数组

在 Windows 运行时中,所有参数均用于输入或输出;没有 ref 参数(即 Visual Basic 中的 ByRef)。传递给 Windows 运行时组件的数组的内容必须用于输入或输出。也就是说,不得将数组视为可变数组。如果通过值(在 Visual Basic 中为 ByVal)传递数组,则必须应用 ReadOnlyArrayAttribute 特性或 WriteOnlyArrayAttribute 特性来确定目的。请参见向 Windows 运行时组件传递数组

重载方法

在 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 传递任何值,并将该值强制转换为参数要求的类型。可用“四十二”、“42”或 42.3 调用 OverloadExample,但所有这些值都会传递给默认重载。上一示例中的默认重载分别返回 0、42 和 42。

不能将 DefaultOverloadAttribute 特性应用于构造函数。类中的所有构造函数必须具有不同数量的参数。

异步操作

若要在组件中实现异步方法,请在方法名称结尾添加“Async”并返回以下表示异步操作的 Windows 运行时接口之一:IAsyncActionIAsyncActionWithProgress<TProgress>IAsyncOperation<TResult>IAsyncOperationWithProgress<TResult, TProgress>

可以使用 .NET Framework 任务(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 方法启动任务来实现异步方法。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 创建一个简单的组件,然后从 JavaScript 中调用该组件

请注意,即使异步方法不支持取消或进度报告,也可以使用 AsyncInfo 类的方法。如果使用 Visual Basic lambda 函数或 C# 匿名方法,请不要为标记和 IProgress<T> 接口提供参数。如果使用 C# lambda 函数,可提供标记参数但请忽略它。当你改用 AsyncInfo.Run<TResult>(Func<CancellationToken, Task<TResult>>) 方法重载时,前面的示例(其中使用了 AsAsyncOperation<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> 接口没有参数的重载。

引发异常

可以引发包括在 .NET for Windows Store 应用程序 - 支持的 API 中的任何异常类型。不能在 Windows 运行时组件中声明您自己的公共异常类型,但可以声明并引发非公共类型。

如果组件不处理异常,则会在调用组件的代码中引发相应异常。异常向调用方的显示方式取决于调用语言支持 Windows 运行时的方式。

  • 在 JavaScript 中,异常会显示为对象,其中的异常消息将由堆栈跟踪替换。在 Visual Studio 中调试您的应用程序时,您可以看到调试器异常对话框中显示的标识为“WinRT 消息”的原始消息文本。您无法通过 JavaScript 代码访问原始消息文本。

    备注

    目前,堆栈跟踪包含托管异常类型,但我们不建议您分析跟踪来标识异常类型。而是使用本节后面介绍的 HRESULT 值。

  • 在 C++ 中,异常显示为平台异常。如果托管异常的 HResult 属性可映射到特定平台异常的 HRESULT,则使用特定异常;否则,将引发 Platform::COMException 异常。托管异常的消息文本对 C++ 代码不可用。如果引发了特定平台异常,则将显示该异常类型的默认消息文本;否则,不显示任何消息文本。请参见异常 (C++/CX)

  • 在 C# 或 Visual Basic 中,该异常是一般托管异常。

在从组件引发异常时,可通过引发其 HResult 属性值是特定于组件的非公共异常类型,来使 JavaScript 或 C++ 调用方更轻松地处理异常。HRESULT 可通过异常对象的 number 属性用于 JavaScript 调用方,并可通过 COMException::HResult 属性用于 C++ 调用方。

备注

对您的 HRESULT 使用负值。在 JavaScript 或 C++ 调用方中,正值被解释为成功,且不会引发异常。

声明和引发事件

在声明类型以保存事件数据时,请从 Object 中而不是 EventArgs 中派生,因为 EventArgs 不是 Windows 运行时类型。使用 EventHandler<TEventArgs> 作为事件类型,并使用事件参数类型作为泛型类型参数。像在 .NET Framework 应用程序中一样引发事件。

通过 JavaScript 或 C++ 使用 Windows 运行时组件时,事件遵循这些语言期望的 Windows 运行时事件模式。通过 C# 或 Visual Basic 使用组件时,事件显示为普通的 .NET Framework 事件。演练:用 C# 或 Visual Basic 创建一个简单的组件,然后从 JavaScript 中调用该组件中提供了一个示例。

如果实现自定义事件访问器(在 Visual Basic 中用 Custom 关键字声明事件),则必须在实现中遵循 Windows 运行时事件模式。请参见Windows 运行时组件中的自定义事件和事件访问器。请注意,在处理 C# 或 Visual Basic 代码中的事件时,事件仍显示为普通的 .NET Framework 事件。

请参见

概念

.NET for Windows Store 应用程序概述

.NET for Windows Store 应用程序 - 支持的 API

演练:用 C# 或 Visual Basic 创建一个简单的组件,然后从 JavaScript 中调用该组件

创建 Windows 运行时组件

Windows 运行时组件中的自定义事件和事件访问器

向 Windows 运行时组件传递数组

Windows 运行时类型的 .NET Framework 映射

诊断 Windows 运行时组件错误条件