Windows 运行时 (WinRT) 类型系统

一般说明

除基本类型外,所有类型都必须包含在命名空间中。 类型在全局命名空间中无效。

Windows提供的类型均包含在 Windows.* 命名空间下。 未由Windows (提供的 WinRT 类型(包括 Microsoft) 其他部分提供的 WinRT 类型)必须位于除 Windows.*以外的命名空间中。

除接口外,所有 WinRT 类型都必须具有公开可见性。 WinRT 接口可以选择具有专用可见性。 所有其他用户定义的 WinRT 类型 (结构、类、枚举、委托、属性) 必须具有公开可见性。

WinRT 不支持嵌套类型。 没有 WinRT 类型可以包含另一种类型:不能将 WinRT 类型嵌套在另一种类型内。

并非所有 WinRT 类型系统的各个方面都可以作为第三方开发人员使用。

  • WinRT 支持接口和委托的参数化。 但是,在此版本中,WinRT 不支持由第三方定义参数化类型。 仅支持 Windows.* 命名空间中系统中包含的参数化类型。

  • WinRT 支持类组合作为运行时继承机制。 但是,在此版本中,WinRT 不支持根可组合类的定义。 仅支持Windows.* 命名空间中系统中包含的根可组合类。

WinRT 命名空间和类型名称是大小写保留的,但不区分大小写,类似于Windows文件系统和Windows注册表。 这意味着不能有命名空间或类型名称,这些名称只因大小写而异。 例如,不能同时具有 Foo.SomeTypefoo。AnotherType,也不能同时拥有 Foo.SomeTypeFoo.someType

WinRT 标识符必须符合以下语法。 请注意,仅支持 Unicode 3.0 及更早版本中定义的字符。

    identifier-or-keyword: 
        identifier-start-character   identifier-continuation-characters* 
    identifier-start-character: 
        letter-character
        underscore (U+005F) 
    identifier-continuation-characters: 
        identifier-continuation-character
        identifier-continuation-characters   identifier-continuation-character 
    identifier-continuation-character: 
        identifier-start-character 
        decimal-digit-character
        connecting-character
        combining-character
        formatting-character 
    letter-character: 
        A Unicode 3.0 character of classes Lu, Ll, Lt, Lm, Lo, or Nl 
    combining-character: 
        A Unicode 3.0 character of classes Mn or Mc 
    decimal-digit-character: 
        A Unicode 3.0 character of the class Nd 
    connecting-character: 
        A Unicode 3.0 character of the class Pc 
    formatting-character: 
        Zero Width Non-Joiner (U+200C)
        Zero Width Joiner (U+200D)

参数化类型

WinRT 支持接口和委托的类型参数化。 参数化类型允许定义一系列接口,这些接口可以在支持参数多态性的编程语言中以多态方式进行处理。

当在类型上下文中使用类型上下文中类型的参数列表(例如方法参数位置)指定参数化类型时,会发生参数化类型实例化。 例如, HRESULT foo(X<Y> x) 实例化 X 命名的参数化类型,该类型为 Y 作为其第一个参数,并且仅实例化类型参数。

为未参数的 WinRT 接口或委托分配 GUID 以唯一标识基础接口,这与同一对象上的其他接口不同。 参数化接口 (例如, IVector<T>) 或委托 (, EventHandler<T>) 改为分配参数化接口 ID (PIID) ,这是唯一标识此参数化类型的 GUID。 这不是 IID。 此 GUID 用于为参数化类型实例生成 IID,例如 IVector<int> () 。

参数化类型参数列表

允许这些 WinRT 类型显示在参数化类型的参数列表中。

  • WinRT 基本类型 (例如 布尔值、 Int32字符串Guid 等)
  • WinRT 枚举
  • WinRT 结构
  • WinRT 接口
  • WinRT 委托
  • WinRT 运行时类
  • 例如, (其他参数化类型实例化, IVector<IVector<int>>) 禁止任何其他类型出现在参数化类型参数列表中。 例如,数组。

参数化类型实例

参数化类型实例可以出现在非参数化类型可以显示的任何上下文中。 参数化接口实例可以在可以使用接口的任何位置使用,例如在运行时类实现的接口列表中。 参数化委托实例可以在可以使用委托的任何位置使用,例如在事件的定义中。

参数化类型参数可以显示:在参数化接口或委托定义中;或者,正常类型可以正常出现的任何位置。 如果仅显示接口的参数,则该参数限制为接口。 如果参数出现在任何类型可以显示的上下文中,则该参数不受限制;等等。 参数化类型实例的参数必须满足所有此类限制。

参数化类型的 Guid 生成

guid 生成算法必须满足这些要求。

  • 使用相同参数实例化两次参数化类型时,必须为两个实例化分配相同的接口或委托正文,以及相同的 IID。
  • 如果实例化了两个不同的参数化类型,即使使用相同的参数,也不可能统计上为它们分配相同的 IID。
  • 如果参数化类型被实例化两次,但具有不同参数,则两个实例化分配相同的 IID 的可能性必须是统计上的。

注意

不允许参数化类型与参数实例化相同,也不能作为参数 (列表中出现的任何其他参数化类型的参数化类型(即循环实例化) )实例化。 例如,如果 X = Foo<Y>违反了 Y = Foo<X>非循环。 但是,如果 X = Foo<Y> 存在 Y = Foo<int>,则保持非循环性。

实例化算法如下所示。

  1. 每个参数化类型由其作者分配参数化接口 ID,即缩写的 PIID。 请注意,PIID 不是 IID,也不会作为参数传递给 QueryInterface。 作者必须确保 PIID 对参数化类型是唯一的。
  2. 基类型的类型签名是 ASCII 八进制字符串, (请参阅下表) 。 例如, Int32 为“i4”。
  3. 不是参数化接口实例的接口的类型签名是以短划线形式以 ASCII 编码的 IID,由大括号分隔。 例如“{00000000-0000-0000-0000-000000000000}”。
  4. 不是参数化委托实例的委托的类型签名是字符串“delegate”,然后是 IID 作为接口。 接下来会显示详细的语法。
  5. 参数化类型的 guid 根据此语法进行计算。
    • 根据 UUID rfc 4122,计算signature_octets的 ver 5 sha-1 生成的哈希, 这使用单个 winrt pinterface/pintergroup guid 作为命名空间,如 rfc 4122/4.3 中所述,以及 pinterface/pintergroup 的签名及其实例化为名称字符串的参数。
    • pinterface 实例化分配此 guid,以及 4 中的签名。
    signature_octets => guid_to_octets(wrt_pinterface_namespace) string_to_utf8_octets(ptype_instance_signature)

        wrt_pinterface_namespace => "11f47ad5-7b73-42c0-abae-878b1e16adee"

        ptype_instance_signature => pinterface_instance_signature | pdelegate_instance_ signature

        pinterface_instance _signature => "pinterface(" piid_guid  ";" args ")"

        pdelegate_instance _signature => "pinterface(" piid_guid ";" args ")"

        piid_guid => guid

        args => arg | arg ";" args

        arg => type_signature

        type_signature => base_type_identifer | com_interface_signature | interface _signature | delegate_signature  | interface_group_signature | runtime_class_signature | struct_signature | enum_signature | pinterface_instance_signature | pdelegate_instance_signature
        com_interface_signature => "cinterface(IInspectable)"

        base_type_identifier is defined below

        interface_signature => guid

        interface_group_signature => "ig(" interface_group_name ";" default_interface ")"

        runtime_class_signature => "rc(" runtime_class_name ";" default_interface ")"

        default_interface => type_signature

        struct_signature => "struct(" struct_name ";" args ")"

        enum_signature => "enum(" enum_name ";" enum_underlying_type ")"

        enum_underlying_type => type_signature

        delegate_signature => "delegate(" guid ")"

        guid => "{" dashed_hex "}"

        dashed_hex is the format that uuidgen writes in when passed no arguments.

            dashed_hex => hex{8} "-" hex{4} "-" hex{4} "-" hex{4} "-" hex{12}

            hex => [0-9a-f]
  1. 将 p 类型实例化作为参数传递给其他 pinterface 时,步骤 3 计算的签名将用作语法元素“pinterface_instance_signature”或“pdelegate_instance_signature”中的类型签名。

这些名称用于显示基类型。

  • UInt8 映射到“u1”
  • Int32 映射到“i4”
  • UInt32 映射到“u4”
  • Int64 映射到“i8”
  • UInt64 映射到“u8”
  • 一映射到“f4”
  • 双精度 映射到“f8”
  • 布尔 值映射到“b1”
    • 请注意,若要获取 布尔 类型参数支持,需要添加 /Yc:wchar_t 才能将 wchar_t 启用为内置类型。
  • Char16 映射到“c2”
  • 字符串 映射到“string”
  • Guid 映射到“g16”

上述名称区分大小写。 除了 String 之外,类型名称使用单个字符来建议数据类型,后跟其大小(以字节为单位)。 选择这些名称:为简洁 (,以避免结构类型签名) 较大大小;与 WinRT 名称、RIDL 名称或任何类型的语言投影名称不一样出现混淆:仍然保持大致可读。

对于enum_type_signature,唯一有效的“underlying_type”值是 Int32UInt32 的值。

对于 struct_type_signature,args 是结构字段的type_signatures顺序列表。 这些类型可以是基类型或其他结构类型。

Struct_name和enum_name是命名空间限定的,使用句点“.”作为分隔符。 例如,“namespace X { struct A{ int; }; }” 变为“struct (X.A;i4) ”。

Default_interface必须是使用 IDL“[default]”属性在运行时类或接口组正文中指定为默认值的接口、p 接口实例、委托或 p 委托实例。

请注意,将忽略自定义属性;假设对封送没有影响。

版本控制

除基本类型之外的所有 WinRT 类型都必须具有版本属性。 语言投影使用版本属性信息来启用向后兼容性,以及用于精简方案。 第三方类型必须包含 Version 属性,但语言投影必须忽略它。 第三方 WinRT 组件专门打包在应用中,因此它们永远不能独立于应用本身更改版本。

“版本”属性可以选择应用于接口成员 (方法、属性和事件) 。 这适用于 C#/VB 和 C++/CX 中的高级类创作。 接口成员(甚至Windows系统接口成员)上的版本属性必须由语言投影在运行时忽略。

Version 属性包括无符号 32 位整数构造函数参数。 对于 Windows WinRT 类型,此值是首次定义关联类型构造Windows版本的 NTDDI 值。 对于第三方类型,此值的含义由类型作者决定。

Windows定义后,系统结构、委托和接口是不可变的。 在后续Windows版本中,它们可能永远不会修改。

Windows系统枚举和运行时类可累加版本控制。 枚举可能会在后续Windows版本中添加新枚举值。 类可以添加新的实现接口, (包括静态、激活工厂、合成工厂、可替代的、受保护的接口) 后续Windows版本中。 枚举和运行时类的各节中包括了有关累加版本控制的详细信息。

命名空间

命名空间是用于组织代码的命名范围,并避免命名冲突。 WinRT 类型系统中的所有命名类型 (枚举、结构、委托、接口和运行时类) 位于命名空间中。 命名空间可以包含其他命名空间。

基本类型

WinRT 类型系统包括一组核心的内置基元类型。

WinRT 类型 类型说明
Int16 16 位有符号整数
Int32 32 位有符号整数
Int64 64 位有符号整数
UInt8 8 位无符号整数
UInt16 16 位无符号整数
UInt32 32 位无符号整数
UInt64 64 位无符号整数
Single 32 位 IEEE 754 浮点数
Double 64 位 IEEE 754 浮点数
Char16 表示 UTF-16 代码单元的 16 位非数值
Boolean 8 位布尔值
字符串 用于表示文本的 Char16 的不可变序列
Guid 128 位标准 Guid

枚举

枚举类型是包含一组已命名常量的独特值类型。

每个枚举类型都有一个相应的整数类型,称为枚举类型的基础类型。 WinRT 中唯一的法律枚举基础类型是 Int32UInt32

具有 基础类型的 UInt32 的枚举必须携带 FlagsAttribute。 基础类型为 Int32 的枚举不得携带 FlagsAttribute。

枚举必须具有公开可见性。

枚举版本控制

枚举可累加版本控制。 给定枚举的后续版本可能会添加值 (也称为命名常量) 。 可能不会删除或更改预先存在的值。 枚举值(可选)携带 VersionAttribute 以区分何时将特定值添加到枚举类型。 不带 VersionAttribute 的枚举值被视为具有与封闭枚举类型相同的版本值。

结构

结构是包含一个或多个字段的记录类型。 结构始终通过值传递和返回。 结构字段只能是基元、枚举、结构、字符串和 IReference<T> (后两个字段类型) 唯一两个堆分配的字段类型。

结构必须具有公开可见性。

通常,结构必须至少有一个字段 (存在罕见的异常,例如表示元数据协定的类型和属性) 。 所有结构字段都必须是公共的。

结构不能是泛型的,也不能参数化。

接口

接口是一个协定,它由一组相关类型成员组成,其用法是定义的,但其实现不是。 接口定义指定接口的成员-方法、属性和事件。 没有与接口关联的实现。

非参数化接口必须具有唯一的接口 ID, (通过 GuidAttribute 指定的 IID) 。 参数化接口必须具有唯一的参数化接口 ID (也称为 PIID) 通过 GuidAttribute 指定的。 PIID 用于通过上面指定的算法为特定参数化接口实例生成 IID。

接口可能具有公共或专用可见性。 这反映了一些接口表示由多个 WinRT 类实现的共享协定,而其他接口表示由单个 WinRT 类实现的成员。 专用可见性接口必须指定它通过 ExclusiveToAttribute 独占的 WinRT 类。 专用接口只能由 ExclusiveToAttribute 中指定的 WinRT 类实现。

IInspectable 和 IUnknown

在接口方面,WinRT 没有继承的概念。 相反,有一个 接口可能需要另 一个接口的想法。 有关 MIDL 3.0 requires 关键字的详细信息,请参阅 MIDL 3.0 接口

所有 WinRT 接口都隐式需要 IInspectable;反过来,IInspectable 需要 IUnknown。 IUnknown 根据传统的 COM 使用情况定义三种方法:QueryInterface、AddRef 和 Release。 除了 IUnknown 方法之外,IInspectable 还定义了三种方法:GetIids、GetRuntimeClassName 和 GetTrustLevel。 这三种方法允许对象的客户端检索有关该对象的信息。 具体而言,IInspectable.GetRuntimeClassName 使对象的客户端能够检索可在元数据中解析的 WinRT 类型名,以启用语言投影。

接口 需要

如前所述,接口可以指定它 需要 一个或多个其他接口,这些接口必须在任何实现有关接口的对象上实现。 例如,如果 IButton 需要 IControl,则实现 IButton 的任何类也需要实现 IControl

但 WinRT 类型系统和 ABI 都没有接口继承的概念。 因此,通过实现从现有接口继承的新接口来添加新功能的想法 (例如 ,IFoo2 继承自 IFoo) 没有任何意义。 的确,WinRT 语言投影可以使用继承关系来简化该特定语言的消耗,运行时类可以使用继承,但 中间层 3.0 创作 () 中不存在接口继承。

参数化接口

接口支持类型参数化。 参数化接口定义除了接口成员列表和所需接口之外,还指定类型参数列表。 参数化接口的必需接口可以共享同一类型参数列表,以便使用单个类型参数来指定接口的参数化实例及其需要 (的接口, IVector<T> requires IIterable<T> 例如) 。 参数化接口的任何成员 (的签名、方法、属性或事件) 可以引用参数化接口的类型参数列表中的类型 (,例如 IVector<T>.SetAt([in] UInt32 index, [in] T value)) 。

第三方无法定义新的参数化接口。 仅支持系统定义的参数化接口。

委托

委托是充当类型安全函数指针的 WinRT 类型。 委托本质上是一个简单的 WinRT 对象,该对象公开从 IUnknown 继承的单个接口,并定义名为 Invoke 的单个方法。 调用委托反过来会调用它引用的方法。 委托通常 (,但不专门) 用于定义 WinRT 事件。

WinRT 委托是一种命名类型,并定义方法签名。 委托方法签名遵循与接口方法相同的参数规则。 Invoke 方法的签名和参数名称必须与委托的定义匹配。

与接口一样,非参数化委托必须具有唯一的接口 ID, (通过 GuidAttribute 指定的 IID) 。 委托的 IID 用作用于实现委托的单方法接口的 IID。 参数化委托必须具有唯一的参数化接口 ID (也称为 PIID) 通过 GuidAttribute 指定的。 PIID 用于通过上面指定的算法为特定参数化委托实例生成 IID。

委托必须具有公开可见性。

IUnknown

请注意,与 WinRT 接口不同,委托实现 IUnknown,但不实现 IInspectable。 这意味着它们无法在运行时检查类型信息。

参数化委托

委托支持类型参数化。 参数化委托定义除了如上所述的传统方法签名外,还指定类型参数列表。 在方法签名中,任何参数都可以指定为参数化委托的类型参数列表中的类型之一。

第三方无法定义新的参数化委托。 仅支持系统定义的参数化委托。

接口成员

WinRT 接口支持三种类型的成员:方法、属性和事件。 接口可能没有数据字段。

方法

WinRT 接口支持采用零个或多个参数的方法,并返回 一个 HRESULT ,指示方法调用的成功或失败。 方法可以选择性地指示要投影为基于异常的语言中的返回值的单一输出参数。 如果指定,则返回值 out 参数必须是方法签名中的最后一个参数。

方法必须具有公开可见性。

方法不能使用变量数。 方法可能没有可选参数,也不具有默认值的参数。

方法可能未参数化。 参数化接口的参数化委托和方法可以使用方法签名中包含类型的类型参数。

参数

以下 () 描述的数组长度参数以外的所有方法参数都必须具有名称和类型。 请注意,返回值必须指定名称,就像参数一样。 方法参数名称(包括返回类型名称)在方法范围内必须是唯一的。

只有参数化委托的参数和参数化接口的成员可以为参数类型指定参数化类型。 方法可能不会单独参数化。 参数始终可以指定参数化类型实例 (,例如, IVector<int>) 作为参数类型。

所有方法参数都必须以独占方式传入或输出参数。 不支持 In/out 参数。

虽然 WinRT 接口上的方法必须返回 HRESULT,但方法可以选择指示其最终输出参数在将该方法投影到基于异常的语言时用作返回值。 此类参数在用于声明这些参数的 MIDL 语法之后称为 [out, retval] 参数。 指定 [out, retval] 参数时,它必须是方法签名中的最后一个参数。

除了 [out, retval] 需要显示在参数列表末尾之外,没有其他输出参数的排序要求。

数组参数

WinRT 方法支持符合性数组参数。 数组永远不能用作参数。 它们不能是独立的命名类型,不能用作结构字段类型。 数组参数可用作 in参数 outretval 参数。

WinRT 支持大多数 WinRT 类型的数组参数,包括字符串和 guid) 、结构、枚举、委托、接口和运行时类 (的基本类型。 不允许其他数组的数组。

由于它们符合性,因此数组参数必须始终在参数列表中紧跟数组大小的参数。 数组大小参数必须是 UInt32。 数组大小参数没有名称。

WinRT 支持三种不同的数组传递样式。

  • PassArray。 当调用方向方法提供数组时,将使用此样式。 在此样式中,数组大小参数和数组参数都是 in 参数。
  • FillArray。 当调用方为要填充的方法提供数组(最大数组大小)时,将使用此样式。 在此样式中,数组大小参数是参数 in ,而数组参数是参数 out 。 使用 FillArray 样式时,数组参数可以选择将另一个参数指定为数组长度参数。 详细信息如下所示。
  • ReceiveArray。 当调用方收到方法分配的数组时,将使用此样式。 在此样式中,数组大小参数和数组参数都是 out 参数。 此外,数组参数由引用 (传递,即 ArrayType**,而不是 ArrayType*) 。

注意

out数组大小参数的组合,但in数组参数在 WinRT 中无效。

当数组参数用作 [out, retval] 参数时,数组长度参数必须是参数 out ,也就是说,只有 ReceiveArray 样式对 retval 数组是合法的。

数组长度参数

FillArray 样式数组参数可以选择将另一个参数指定为数组长度参数。 如果所需的数组大小参数指定调用方提供的数组中的最大元素数,则数组长度参数指定被调用方实际填充的元素数。

使用数组参数上的 LengthIs 属性指定数组长度参数。

方法重载

在单个接口的范围内,多个方法可能具有相同的名称。 接口上具有相同名称的方法必须具有唯一签名。 属性和事件无法重载。

WinRT 支持对参数类型进行重载,但支持对输入参数的数量进行重载,也称为方法 的 arity。 为此,支持动态弱类型语言 ((如 JavaScript) )。

当接口具有同名和输入参数数的多个方法时,必须将其中一种方法标记为默认值。 对于具有相同名称和输入参数数的所有重载方法中,只有标记为默认值的方法将由动态弱类型语言投影。 如果只有给定名称和输入参数数的单个重载方法,则将其标记为默认值有效,但不是必需的。

为了确定方法的 arity,数组参数及其所需的长度参数被视为单个参数。 PassArray 和 FillArray 样式被视为单个输入参数,而 ReceiveArray 样式被视为单个输出参数。

当接口上的多个方法具有相同的名称时,每个碰撞方法的唯一名称必须存储在附加到该方法的 OverloadAttribute 中。 默认重载方法携带 DefaultOverloadAttribute。

运算符重载

WinRT 不支持运算符重载。 不能使用特殊运算符名称(如 ECMA 335 CLI 规范、分区 I 第 10.3 节中指定的op_Addition)命名方法。

属性

属性是 get/set 方法的配对,其中包含匹配的名称和类型,这些方法在一些语言投影中显示为字段而不是方法。

属性及其 get/set 方法必须具有公开可见性。

属性必须具有方法 get 。 属性 getter 方法没有参数,并返回属性类型的值。 只有方法 get 的属性称为只读属性。

属性可能具有一个 set 方法。 属性 setter 方法具有属性类型的单个参数,并返回 void。 具有和getset方法的属性称为读/写属性。

属性可能未参数化。 参数化接口中的属性可以使用包含类型的类型参数作为属性类型。

事件

事件是具有匹配名称和委托类型的添加/删除侦听器方法的配对。 事件是接口在发生重要事件时通知相关方的机制。

事件及其添加/删除侦听器方法必须具有公开可见性。

事件add侦听器方法具有事件委托类型的单个参数,并返回Windows。Foundation.EventRegistrationToken。 事件remove侦听器方法具有Windows的单个参数。Foundation.EventRegistrationToken 类型,并返回 void。

事件可能未参数化。 参数化接口中的事件可以使用包含类型的类型参数作为事件委托类型。

运行时类

WinRT 允许定义类。 类必须实现一个或多个接口。 类不能直接 (实现类型成员,也就是说,它不能定义自己的方法、属性或事件) 。 类必须提供它实现的所有接口的所有成员的实现。

下面详细介绍了几种不同类型的接口。

  • 成员接口 (包括受保护接口和可重写接口)
  • 静态接口
  • 激活工厂接口
  • 合成工厂接口

无法参数化运行时类。 运行时类可以实现参数化接口实例 (,即参数化接口,其类型参数指定的所有类型参数) 通常接受非参数化接口。

运行时类必须具有公共可见性。

运行时类只能实现不是独占 (的接口,即不要) 携带 exclusiveTo 属性或属于相关运行时类的独占接口。 运行时类可能无法实现与其他运行时类独占的接口。 此规则有一个例外-可组合类可以实现其派生链中标记为可重写的类的独占接口。 有关要遵循的可重写接口的详细信息。

成员接口

运行时类可以实现零个或多个成员接口。 成员接口使类能够公开与类实例关联的功能。 运行时类指定它实现的成员接口的列表。 运行时类实现的成员接口列表中的条目可以选择携带版本信息。 有关要遵循的运行时类版本控制的详细信息。

成员接口直接在运行时类的实例上实现。

实现一个或多个成员接口的运行时类必须指定一个成员接口作为默认接口。 实现零成员接口的运行时类不指定默认接口。

静态接口

WinRT 类可以指定零个或多个静态接口。 静态接口使类能够公开与类本身关联的功能,而不是与类的特定实例关联的功能。

类必须至少指定一个成员或静态接口。 没有成员且没有静态接口的类无效。

静态接口是通过与运行时类关联的 StaticAttribute 指定的。 StaticAttribute 携带静态接口引用以及版本信息。 有关要遵循的运行时类版本控制的详细信息。

虽然静态接口声明为运行时类的一部分,但它们实际上不是在类实例本身上实现的。 而是在类的激活工厂上实现它们。 要关注的激活工厂的详细信息。

激活

运行时类(可选)支持激活-系统能够生成指定类的实例。 类必须实现至少一个成员接口才能支持激活。

WinRT 定义了三种激活机制:) 没有构造函数参数的直接激活 (、具有一个或多个构造函数) 参数的工厂激活 (以及组合激活。 不可组合类可能支持直接和/或工厂激活。 可组合类仅支持可组合激活。 要遵循的合成和可组合激活的详细信息。

通过对类的激活工厂调用 IActivationFactory.ActivateInstance 方法来激活支持直接激活的类。 此方法不采用任何参数,并返回运行时类的新激活实例。 要关注的激活工厂的详细信息。

支持工厂激活的类定义了一个或多个工厂接口,每个接口又定义一个或多个工厂方法。 这些工厂接口在类的激活工厂上实现。

工厂方法采用一个或多个 in 参数,并且必须返回运行时类的新激活实例。 不允许使用新激活的类实例以外的其他 out 参数。 工厂方法必须采用一个或多个参数 - 不允许无参数工厂激活。 直接激活必须用于无参数激活。

支持直接激活或工厂激活的类使用 ActivationableAttribute 进行标记。 ActivatableAttribute 携带版本信息, (运行时类版本控制的详细信息,以遵循) 和对工厂接口的可选引用。 可以使用多个 ActivationableAttributes 标记类-最多可以标记一个用于默认激活,另外一个用于类激活工厂实现的每个工厂接口。 使用 ActivatableAttribute 标记的类可能不会也标有 ComposableAttribute。 要关注的合成的详细信息。

组合

运行时类(可选)支持组合- 可将多个类实例组合到看似来自外部的单个对象的功能。 WinRT 使用合成作为运行时类继承的形式。

WinRT 类可以选择性地组合单个可组合基类,这反过来又可能撰写单个可组合基类等。为了编写可组合基类,类本身不需要可组合。 类只能使用可组合类作为基类进行组合。 组合类不需要编写另一个可组合类 (,即可能是层次结构) 的根。 不允许使用合成 ((如 A composes B)的圆形图形,该图形构成 A) 。

在运行时,组合类是 WinRT 对象的聚合,一个用于组合链中的每个对象。 这些聚合对象将标识和生存期委托给合成链中最初激活的对象 (称为控制对象) 。 链中的每个对象都保存一个非委派 IInspectable 指针,指向它撰写的类,以便对组合基类接口(包括受保护接口上的方法)调用方法。 链中的每个对象都有一个指向控制类的指针,用于委派生存期和标识,以及调用可重写接口上的方法。 有关要遵循的受保护和可重写接口的详细信息。

让我们以 Button 撰写控件的示例为例,该控件又组合 UIElement。 在该示例中, Button 实例聚合 控件 实例,而该实例又聚合 UIElement 实例。 这三个对象都具有对 Button 对象的引用,用于控制生存期和标识,以及查询可重写接口。 每个对象都有一个 IInspectable 指针,指向它组成 (按钮 包含指向 控件的指针; 控件 包含指向 UIElement) 的指针,以便能够对组合类(包括受保护的接口)实现的接口调用方法。

除非接口在可组合类中标记为可重写,否则类可能不会在它撰写的类上实现定义的任何接口,也不能实现组合链中的任何类。 有关要遵循的可重写接口的详细信息。

必须用一个或多个 ComposableAttributes 标记可组合类。 ComposableAttribute 承载对合成工厂接口的引用(无论合成工厂接口上的工厂方法是否可用于控制对象激活)以及版本信息。 有关组合工厂接口和要遵循的版本控制的详细信息。 一个类可以使用多个 ComposableAttributes 进行标记,每个合成工厂接口都由该类的激活工厂实现。

JavaScript 语言投影不支持类组合。 因此,撰写可组合类和撰写可组合类的类应使用 WebHostHiddenAttribute 标记,指示 JavaScript 不应尝试投影这些类型。

第三方只能定义构成其他可组合类的类。 不能定义自己的可组合根类。

可组合激活

可组合类必须定义一个或多个合成工厂接口,进而实现一个或多个合成工厂方法。 合成工厂接口在类的激活工厂上实现。 要遵循的激活工厂的详细信息。

合成工厂接口用于创建类的可组合实例。 可组合工厂接口声明零个或多个可组合工厂方法,这些方法可用于激活类的实例以进行组合。 请注意,具有具有零工厂方法的可组合工厂接口是合法的。 这意味着该类可用于合成,但第三方可能无法直接撰写该类—创建实例的方法 () 仅供内部使用。

可组合类声明给定合成工厂接口上的工厂方法是否可用于直接激活类作为控制对象。 标记为公共的可组合工厂接口可用于直接激活类作为控制对象,并间接激活类作为组合对象。 标记为受保护的可组合工厂接口只能用于间接激活类作为组合对象。 可组合类始终可以激活为组合对象。

合成工厂接口必须是 exclusiveto 它实现的运行时类。

与激活工厂方法一样,合成工厂方法必须返回可组合类的实例。 此外,合成工厂方法还有两个附加参数:控制 IInspectable* [in] 参数,以及非委派 IInspectable** [out] 参数。 组合工厂方法可能选择性地具有其他 in 参数。 如果指定,则必须先在方法签名的开头,再在前面列出的受授权参数之前进行其他参数。 合成工厂方法可能没有除非委派 IInspectable** 和返回值参数之外的任何其他out参数。

当为组合类激活 ((例如,在激活 Button 实例时为 ControlUIElement)激活) 时,通过控制 IInspectable* [in] 参数传入控件标识和生存期的对象指针。 可组合工厂方法将新激活的实例作为返回值返回。 此实例将所有标识和生存期管理功能委托给提供的控制 IInspectable* 。 此外,可组合工厂方法返回指向非委派 IInspectable* 的指针,该指针可用于调用撰写类上的方法。

例如,将可组合类激活为控制类 (时,上一个示例中的 Button) ,将使用相同的可组合工厂方法用于合成。 直接激活可组合类时,将为控制 *IInspectable* 参数传递 null。 这是作为控制类激活的可组合类的指示器。 当控制类创建它撰写的类的实例时,它会将引用传递给自身作为控制 IInspectable* 参数。 可组合工厂方法将控制类实例作为返回值返回。 激活控制组合类时,客户端代码会忽略非委派 IInspectable** [out] 参数。

在前面的 Button ->Control ->UIElement 示例的基础上,按钮类将通过调用其合成工厂方法之一并为外部参数传递 null 来激活。 按钮 将反过来激活 控件 实例,并将引用传递给自身作为外部参数。 控件 将反过来激活 UIElement 实例,并传递作为外部参数接收的外部引用。 UIElement 工厂方法将返回到控制实例参数中新创建的 UIElement,以及对内部参数中 UIElement 的非委派 IInspectable 的引用。 控件工厂方法将返回到按钮,即实例参数中新创建的控件,以及对内部参数中控件的非委派 IInspectable 的引用。 Button 合成工厂将返回到调用代码,即实例参数中新创建的 Button,以及内部参数的 null。

有时可以激活类进行合成,有时激活为控制类。 例如,如果 RadioButton 撰写按钮,则在激活 RadioButton 时,按钮将激活合成;但在直接激活 Button 时激活为控制类。 在任一情况下,按钮撰写的 Control 类都将激活组合。

受保护的接口

可组合类可以声明要保护的零个或多个成员接口。 不可组合类可能无法声明要保护的成员接口。 只有组成可组合类的类中的代码 (直接或间接) 可以查询和使用可组合类声明为受保护的接口。 组合链外部的代码可能无法查询或使用可组合类声明为受保护的接口。

例如,如果 UIElement 声明受保护的接口 IUIElementProtected,则只有构成 UIElement 的类(包括直接 (控件) 和间接 (按钮) 组合)可以查询和使用 IUIElementProtected 接口。

可重写接口

可组合类可以声明其成员接口的零个或多个可重写。 可重写接口只能查询和在合成链中使用,这类似于之前详细介绍的访问受保护接口的规则。 但是,如果受保护的接口只能由最初声明它的类实现,则重写接口可由组成实现可重写接口的类重新实现。

在运行时,任何利用可重写接口的可组合类代码都必须通过用于标识和生存期委派的控制 IInspectable* 指针为接口查询Interface。 此指针返回合成链中最早的可重写接口的实现 (,即最接近控制类实例) 。 希望访问它撰写的类的可重写接口的类可以通过非委派引用将其包含到其组合类的类进行访问。

让我们以 UIElement 声明可重写接口 IUIElementOverridable 的示例。 在这种情况下,允许派生自 UIElement 的类(包括直接 (控件) 和间接 (按钮) 派生)。 如果 UIElement 中的代码需要访问 IUIElementOverridable 中的功能,则 UIElement 会查询控制 IInspectable 以获取合成链中最早的实现。 如果 ControlButton 都实现了 IUIElementOverridable,则在查询控制 IInspectable 时,将返回 Button 实现。 如果 Button 想要访问其组合类功能,则可以使用从合成工厂方法返回的非委派 IInspectable 查询该接口的基类。

激活工厂

运行时类(可选)具有激活工厂。 如果类可激活、可组合或具有静态接口,则运行时类必须具有激活工厂。 类的激活工厂可以通过 RoGetActivationFactory Win32 函数在运行时从系统检索。

激活工厂必须实现 IActivationFactory 接口。 但是,仅支持直接激活的类提供 IActivationFactory 的单方法 ActivateInstance 的实现。 不支持直接激活的类必须从 IActivationFactory.ActivateInstance 返回 L E_NOTIMPL。

激活工厂必须实现运行时类上定义的所有激活工厂接口、组合工厂接口和静态接口。

不能保证语言投影在工厂生存期内维护单个激活工厂实例。 需要保存长期信息以供静态成员访问的 WinRT 类作者需要将其存储在激活工厂外部的某个位置。

基于类的投影

虽然 WinRT 主要是基于接口的编程模型,但运行时类提供了一种基于类的编程模型,该模型与现代、主流、面向对象的 (OO) 编程语言保持一致。 语言投影应将运行时类投影为单个实体,而不是开发人员必须单独处理的接口包。

为了实现此类基于类的模型,语言投影应将类成员接口中的类型成员投影为直接类成员。 语言投影应将类的静态接口中的类型成员投影为静态类成员。 最后,语言投影应投影激活方法, (直接激活,以及工厂和可组合工厂接口) 作为类构造函数的接口。

为了帮助进行基于类的投影,运行时类的元数据为它们实现的每个接口的所有方法、属性和事件指定一个类成员。 每个类成员都显式绑定到最初定义的接口成员。 这允许语言投影将类公开为单个实体,并代表开发人员处理封面下的所有接口查询和引用计数。

默认情况下,每个实现的接口成员都投影为类成员。 但是,由于运行时类可以在一段时间内实现多个独立接口和版本 (版本控制详细信息来遵循) ,因此可以在单个运行时类实现的不同接口上定义的成员发生名称冲突。

发生冲突时,无法进行默认类成员投影。 如果在单独版本中添加的接口之间发生冲突,则最早版本中的碰撞成员投影为类成员。 在同一版本中添加的接口之间发生冲突时,不会将冲突成员投影为类成员。 请注意,只要所有版本都不同 允许具有碰撞名称的方法,如 方法重载中所述。

未投影为类成员的接口成员必须提供给开发人员。 通常,这是强制转换或动态查找运算符完成的,使开发人员能够指定要调用的特定接口和方法。

为了解决方法名称冲突,运行时类可以为它们实现的成员和静态接口上的方法指定备用名称。 语言投影使用此可选名称来提供对类实例中冲突方法名称的歧义访问。 虽然运行时类可以提供替代方法名称,但方法签名、参数和附加到该方法或其属性的任何属性仍必须与原始接口定义完全匹配。

由于直接激活、工厂方法和合成工厂方法投影为类构造函数,因此它们都投影在运行时类上,就像它们具有相同的名称一样。 所有工厂接口中的所有方法都必须具有唯一的签名,应优先使用基于 类型重载的重载,并且必须使用 DefaultOverloadAttribute 来消除相同 arity 的工厂方法的歧义。

类版本控制

运行时类可累加版本控制。 给定运行时类的后续版本可以指定所有类型的附加接口,下面提供了有关各个接口类型的进一步详细信息。 无法删除或更改由类指定的预先存在的接口,而不会中断向后兼容性。

成员接口版本控制

运行时类上的成员接口可累加版本控制。 给定运行时类的后续版本可能实现其他成员接口,即使该类以前从未实现成员接口也是如此。 给定的可组合运行时类的后续版本可能实现其他受保护和可重写接口。

运行时类实现的接口可以选择携带 VersionAttribute,以区分何时将特定接口添加到运行时类类型。 不带 VersionAttribute 的接口实现值被视为具有与封闭运行时类类型相同的版本值。

静态接口版本控制

运行时类上的静态接口可累加版本控制。 给定运行时类的后续版本可能实现其他静态接口,即使该类以前从未实现静态接口也是如此。

StaticAttribute 包括版本号的 UInt32 参数,该参数定义添加该激活支持的Windows版本。

激活版本控制

对运行时类的激活支持可累加版本控制。 给定运行时类的后续版本可能实现其他激活机制,即使该类从未实现激活机制。 请注意,可组合类不可激活,因此可能无法添加激活支持。

请注意,支持直接激活的类只能添加新的工厂激活接口。 以前仅支持工厂激活的类可能会添加直接激活支持以及新的工厂激活接口。

ActivatableAttribute 包含版本号的 UInt32 参数。 ActivationableAttribute 的版本号定义添加该激活支持的Windows版本。

合成版本控制

对运行时类的合成支持可累加版本控制。 给定的可组合运行时类的后续版本可能实现其他合成机制,前提是该类在创建时定义为可组合。 可组合类可能无法添加激活支持。

ComposableAttribute 包含版本号的 UInt32 参数。 ComposableAttribute 的版本号定义添加该组合支持的Windows版本。

自定义特性

WinRT 支持自定义元数据属性的定义。 WinRT 类型系统中的所有构造都可以携带自定义元数据属性。 这包括所有命名类型 (枚举、结构、委托、接口、类等) 以及类型构造中包含的单个元素 (,如方法、参数等) 。

自定义属性命名为其他 WinRT 类型。 但是,它们不可激活。 它们纯粹是数据构造。

自定义属性定义位置参数或命名字段的数据架构。 自定义属性不能同时使用位置参数和命名字段,必须选择一个或另一个字段。 自定义属性的参数和字段的类型仅限于 WinRT 基本类型、枚举和其他 WinRT 类型的引用。 不允许使用其他参数或字段类型。

使用位置参数的自定义属性必须定义一个或多个有效的位置参数集。 每个集必须指定零个或多个位置参数。 自定义属性的实例必须指定一组位置参数以及所选集中每个位置参数的数据。

使用命名字段的自定义属性指定具有名称和类型的零字段。 自定义属性的实例必须为想要指定的字段指定名称/值对。 实例可以为所有值、某些值对或无名称/值对指定值。

属性既没有位置参数也没有命名字段,这一点有效。

自定义属性必须具有公开可见性。

属性可以指定可以通过 AttributeUsageAttribute 与之关联的 WinRT 类型构造的类型。

第三方无法定义自定义属性。 仅支持系统定义的自定义属性。