Microsoft接口定义语言 3.0 简介

Microsoft接口定义语言 (MIDL) 3.0 是一种简化的现代语法,用于在接口定义语言(IDL)文件(.idl 文件)中定义 Windows 运行时类型。 对于使用 C、C++、C# 和/或 Java 的任何人,这种新语法将感到熟悉。 MIDL 3.0 是定义 C++/WinRT 运行时类的特别方便方法,比以前版本的 IDL 要简洁得多(在长度上减少三分之二的设计,并使用合理的默认值来减少使用属性修饰的需求)。

以下是 MIDL 3.0 的外观;此示例演示了你可能使用的大多数语言语法元素。

// Photo.idl
namespace PhotoEditor
{
    delegate void RecognitionHandler(Boolean arg); // delegate type, for an event.

    runtimeclass Photo : Windows.UI.Xaml.Data.INotifyPropertyChanged // interface.
    {
        Photo(); // constructors.
        Photo(Windows.Storage.StorageFile imageFile);

        String ImageName{ get; }; // read-only property.
        Single SepiaIntensity; // read-write property.

        Windows.Foundation.IAsyncAction StartRecognitionAsync(); // (asynchronous) method.

        event RecognitionHandler ImageRecognized; // event.
    }
}

请注意,MIDL 3.0 的语法专门用于定义 类型的 。 你将使用不同的编程语言来 实现这些类型。 若要使用 MIDL 3.0,需要 Windows SDK 版本 10.0.17134.0(Windows 10 版本 1803)(midl.exe 版本 8.01.0622 或更高版本,用于 /winrt 开关)。

注意

另请参阅 Windows 运行时合并引用(Windows 运行时类型系统,以及 Windows 元数据文件)。

MIDL 1.0、2.0 和 3.0

接口定义语言 (IDL) 始于分布式计算环境/远程过程调用 (DCE/RPC) 系统。 原始 MIDL 1.0 是 DCE/RPC IDL,具有用于定义 COM 接口和 coclass 的增强功能。

随后在 Microsoft 内开发更新的 MIDL 2.0 语法(也称为 MIDLRT),为 Windows 平台声明 Windows 运行时 API。 如果你在 Windows SDK 文件夹中 %WindowsSdkDir%Include<WindowsTargetPlatformVersion>\winrt,你将看到使用 MIDL 2.0 语法编写的 .idl 文件的示例。 这些是内置 Windows 运行时 API,以应用程序二进制接口 (ABI) 形式声明。 这些文件主要用于工具使用-你不会以这种形式创作和使用这些 API(除非编写非常低级别的代码)。

另请参阅 从经典 MIDLRT转换到 MIDL 3.0。

MIDL 3.0 是一种更简单、更现代的语法,其用途是声明 Windows 运行时 API。 可以在项目中使用它,特别是定义 C++/WinRT 运行时类。 用于内置 Windows 运行时 API C++/WinRT 的标头是 SDK 的一部分,位于文件夹 %WindowsSdkDir%Include<WindowsTargetPlatformVersion>\cppwinrt\winrt中。

MIDL 3.0 的用例

一般情况下,所有 Windows 运行时 API 都设计为适用于所有 Windows 运行时语言投影。 这部分是通过选择独占方式将 Windows 运行时类型传入和传出 Windows 运行时 API 来完成的。 虽然它是一个有效的设计决策,用于将原始 COM 接口传入和传出 Windows 运行时 API,但这样做会将该特定 Windows 运行时 API 的使用者限制为C++应用程序。 在互操作方案中可以看到该技术,例如,在 Direct3D 和 XAML 之间进行互操作时。 由于 Direct3D 位于图片中,因此方案必须缩小到C++应用程序。 因此,需要 COM 接口的 API 不会对固有内容施加任何额外的限制。 例如,C++应用程序可以获取 IDXGISwapChain 接口指针,然后将其传递给 ISwapChainPanelNative::SetSwapChain 方法。 例如,C# 应用程序无法获取 IDXGISwapChain 开头,因此无法出于此原因使用该方法。 这些与互操作相关的异常位于互操作标头中,例如 windows.ui.xaml.media.dxinterop.h

如果 COM 组件的特性或功能,而你希望将其公开到除 C++ 之外的 Windows 运行时语言投影,则可以创建直接创建和使用 COM 组件(例如 DirectX)的 C++ Windows 运行时组件(WRC),并公开其部分特性和功能的复制(采用和返回 Windows 运行时 API 图面的形式)仅运行时类型。 然后,可以从 任何 Windows 运行时语言投影中编写的应用程序使用该 WRC。

定义结构和从命令行调用 midl.exe

MIDL 3.0 定义中的关键组织概念是命名空间、类型和成员。 MIDL 3.0 源文件(.idl 文件)至少包含一个命名空间,其中包含类型和/或从属命名空间。 每个类型都包含零个或多个成员。

  • 类、接口、结构和枚举是类型。
  • 方法、属性、事件和字段是成员的示例。

编译 MIDL 3.0 源文件时,编译器(midl.exe)会发出 Windows 运行时元数据文件(通常是 .winmd 文件)。

// Bookstore.idl
namespace Bookstore
{
    runtimeclass BookSku : Windows.UI.Xaml.Data.INotifyPropertyChanged
    {
        BookSku();
        BookSku(Single price, String authorName, String coverImagePath, String title);

        Single Price;

        String AuthorName{ get; };
        Windows.UI.Xaml.Media.ImageSource CoverImage{ get; };
        String CoverImagePath{ get; };
        String Title{ get; };

        Boolean Equals(BookSku other);
        void ApplyDiscount(Single percentOff);
    }
}

由于 Windows 运行时类型的命名空间成为类型名称的一部分,上面的示例定义了名为 bookstore.BookSku的运行时类。 没有与语言无关的方式来表达 BookSku,而无需同时表达命名空间。

此类实现 Windows.UI.Xaml.Data.INotifyPropertyChanged 接口。 该类包含多个成员:两个构造函数、一个读写属性(Price)、一些只读属性(通过 TitleAuthorName),以及两个 方法,名为 equalsApplyDiscount。 请注意, 类型的使用,而不是 浮点数字符串 有大写“S”。

提示

Visual Studio 通过 C++/WinRT Visual Studio 扩展(VSIX)提供编译 MIDL 3.0 的最佳体验。 请参阅 Visual Studio 对 C++/WinRT 的支持,以及 VSIX

但你也可以从命令行编译 MIDL 3.0。 如果此示例的源代码存储在名为 Bookstore.idl的文件中,则可以发出以下命令。 如果需要,可以更新命令中使用的 SDK 版本号(即 10.0.17134.0)。

midl /winrt /metadata_dir "%WindowsSdkDir%References\10.0.17134.0\windows.foundation.foundationcontract\3.0.0.0" /h "nul" /nomidl /reference "%WindowsSdkDir%References\10.0.17134.0\Windows.Foundation.FoundationContract\3.0.0.0\Windows.Foundation.FoundationContract.winmd" /reference "%WindowsSdkDir%References\10.0.17134.0\Windows.Foundation.UniversalApiContract\6.0.0.0\Windows.Foundation.UniversalApiContract.winmd" /reference "%WindowsSdkDir%\References\10.0.17134.0\Windows.Networking.Connectivity.WwanContract\2.0.0.0\Windows.Networking.Connectivity.WwanContract.winmd" Bookstore.idl

midl.exe 工具编译示例并生成名为 Bookstore.winmd 的元数据文件(默认情况下,使用 .idl 文件的名称)。

提示

如果使用多个 IDL 文件(有关此建议,请参阅 将运行时类分解为 Midl 文件(.idl)),然后将生成的所有 .winmd 文件合并到与根命名空间同名的单个文件中。 最终 .winmd 文件将是 API 使用者将引用的文件。

在这种情况下,BookSkuBookstore 命名空间中唯一的运行时类,因此我们保存了一个步骤,并只为命名空间命名了 .idl 文件。

顺便说一句,可以使用 where 命令来找出安装 midl.exe 的位置。

where midl

如果要使用一个 .idl 文件中定义的类型,该类型来自其他 .idl 文件,则使用 import 指令。 有关更多详细信息和代码示例,请参阅 XAML 控件;绑定到 C++/WinRT 属性。 当然,如果使用的是内置组件或第三方组件,则无法访问 .idl 文件。 例如,你可能想要使用 Win2D Windows 运行时 API 进行即时模式的 2D 图形呈现。 上述命令使用 /reference 开关引用 Windows 运行时元数据(.winmd)文件。 在下一个示例中,我们将再次使用该开关,并想象 Bookstore.winmd,但不 Bookstore.idl的方案。

// MVVMApp.idl
namespace MVVMApp
{
    runtimeclass ViewModel
    {
        ViewModel();
        Bookstore.BookSku BookSku{ get; };
    }
}

如果上述示例的源代码存储在名为 MVVMApp.idl的文件中,则可以发出以下命令来引用 Bookstore.winmd

midl /winrt /metadata_dir "%WindowsSdkDir%References\10.0.17134.0\windows.foundation.foundationcontract\3.0.0.0" /h "nul" /nomidl /reference "%WindowsSdkDir%References\10.0.17134.0\Windows.Foundation.FoundationContract\3.0.0.0\Windows.Foundation.FoundationContract.winmd" /reference "%WindowsSdkDir%References\10.0.17134.0\Windows.Foundation.UniversalApiContract\6.0.0.0\Windows.Foundation.UniversalApiContract.winmd" /reference "%WindowsSdkDir%\References\10.0.17134.0\Windows.Networking.Connectivity.WwanContract\2.0.0.0\Windows.Networking.Connectivity.WwanContract.winmd" /reference Bookstore.winmd MVVMApp.idl

命名空间

需要命名空间。 它为命名空间块范围内定义的所有类型的名称加上命名空间名称的前缀。 命名空间还可以包含从属命名空间声明。 从属命名空间范围中定义的类型的名称具有包含的所有命名空间名称的前缀。

以下示例是声明同一 Windows.Foundation.Uri 类的两种方法(如你所看到的,句点分隔嵌套命名空间的级别)。

namespace Windows.Foundation
{
    runtimeclass Uri : IStringable
    {
        ...
    }
}
namespace Windows
{
    namespace Foundation
    {
        runtimeclass Uri : IStringable
        {
            ...
        }
    }
}

下面是另一个示例,显示以嵌套方式声明命名空间及其类型是合法的。

namespace RootNs.SubNs1
{
    runtimeclass MySubNs1Class
    {
        void DoWork();
    }

    namespace SubNs2
    {
        runtimeclass MySubNs2Class
        {
            void DoWork();
        }
    }
}

但更常见的做法是关闭以前的命名空间,并打开一个新命名空间,如下所示。

namespace RootNs.SubNs1
{
    runtimeclass MySubNs1Class
    {
        void DoWork();
    }
}

namespace RootNs.SubNs1.SubNs2
{
    runtimeclass MySubNs2Class
    {
        void DoWork();
    }
}

类型

MIDL 3.0 中有两种类型的数据类型:值类型和引用类型。 值类型的变量直接包含其数据。 引用类型的变量存储对其数据的引用(此类变量也称为 对象)。

两个引用类型变量可以引用同一对象。 因此,对一个变量的操作会影响另一个变量引用的对象。 使用值类型时,变量各自具有其自己的数据副本,并且无法对一个变量执行操作来影响另一个数据。

MIDL 3.0 的值类型进一步分为简单类型、枚举类型、结构类型和可为 null 的类型。

MIDL 3.0 的引用类型进一步分为类类型、接口类型和委托类型。

下面是 MIDL 3.0 的类型系统的概述。 与以前版本的 MIDL 不同,不能对这些类型使用别名。

类别 描述
值类型 简单类型 带符号整型:Int16Int32Int64
无符号整型:UInt8UInt16UInt32UInt64
Unicode 字符:Char(表示 UTF-16LE;16 位 Unicode 代码单元)
Unicode 字符串:字符串
IEEE 浮点:
布尔值:布尔
128 位 UUID:Guid
枚举类型 表单的用户定义类型 枚举 E {...}
结构类型 表单的用户定义类型 结构 S {...}
可以为 Null 的类型 所有其他值类型的扩展,null
引用类型 类类型 所有其他类型的终极基类:Object
runtimeclass C {...} 表单的用户定义类型
接口类型 表单的用户定义类型 接口 I {...}
委托类型 表单的用户定义类型 委托 <returnType> D(...)

七种整型类型支持 8 位无符号数据;带符号或无符号形式的 16 位、32 位和 64 位值。

两种浮点类型(Double)分别使用 32 位单精度和 64 位双精度 IEEE 754 格式表示数据。

MIDL 3.0 的 布尔 类型表示布尔值;truefalse

MIDL 3.0 中的字符和字符串包含 Unicode 字符。 Char 类型表示 UTF-16LE 代码单元;和 字符串 类型表示 UTF-16LE 代码单元序列。

下表汇总了 MIDL 3.0 的数字类型。

类别 类型 范围/精度
带符号整型 16 Int16 –32,768...32,767
32 Int32 –2,147,483,648...2,147,483,647
64 Int64 –9,223,372,036,854,775,808...9,223,372,036,854,775,807
无符号整型 8 UInt8 0...255
16 UInt16 0...65,535
32 UInt32 0...4,294,967,295
64 UInt64 0...18,446,744,073,709,551,615
浮点 32 单个 1.5 × 10•45 到 3.4 × 1038,7 位精度
64 5.0 × 10 到 1.7 × 10308,15 位精度

MIDL 3.0 源文件使用类型定义创建新类型。 类型定义指定新类型的名称和成员。 这些 MIDL 3.0 类型类别是用户定义的。

  • 特性类型,
  • 结构类型,
  • 接口类型,
  • runtimeclass 类型,
  • 委托类型,以及
  • 枚举类型。

属性 类型定义可应用于其他类型定义的 Windows 运行时属性。 特性提供有关应用属性的类型的元数据。

结构 类型定义包含数据成员(字段)的 Windows 运行时结构。 结构是值类型,不需要堆分配。 结构类型的数据成员必须是值类型或可以为 null 的类型。 结构类型不支持继承。

接口 类型定义一个 Windows 运行时接口,该接口是一组命名的函数成员。 接口可以指定接口的实现还必须实现一个或多个指定的附加(必需)接口。 每个接口类型都直接派生自 Windows 运行时 IInspectable 接口。

运行时类 类型定义 Windows 运行时类(运行时类)。 运行时类包含可以是属性、方法和事件的成员。

委托 类型定义 Windows 运行时委托,该委托表示对具有特定参数列表和返回类型的方法的引用。 委托可以将方法视为可作为参数传递的实体。 委托类似于某些其他语言中发现的函数指针的概念。 与函数指针不同,委托面向对象,类型安全。

枚举 类型是具有命名常量的不同类型。 每个枚举类型都有一个隐式基础类型;Int32UInt32。 枚举类型的值集与基础类型的值集相同。

MIDL 3.0 支持三个附加类型类别。

  • 单维数组类型,
  • 可以为 null 的值类型,以及
  • 对象 类型。

在使用它之前,无需声明一维数组。 而是按照带方括号的类型名称构造数组类型。 例如,Int32[]Int32的单维数组。

同样,可为 null 的 值类型也不必定义,然后才能使用它们。 对于每个不可为 null 的值类型 T字符串除外),有一个相应的可为 null 类型 Windows.Foundation.IReference<T>,可以保留附加值 null。 例如,Windows.Foundation.IReference<Int32> 是可以容纳任何 32 位整数或值 null的类型。 另请参阅 IReference<T>

最后,MIDL 3.0 支持 对象 类型,该类型映射到 Windows 运行时 IInspectable 接口。 接口runtimeclass 引用类型从概念上派生自 对象 类型;委托 不。

枚举值中的表达式

使用 MIDL 3.0,只能在枚举类型的命名常量的值定义中使用 表达式;换句话说,在枚举初始值设定项中。

表达式由 操作数运算符构造。 表达式中的运算符指示要应用于操作数的操作。 运算符的示例包括 +、-、*、/和 new。 操作数的示例包括文本、字段、局部变量和表达式。

当表达式包含多个运算符时,运算符 优先级 控制各个运算符的计算顺序。 例如,表达式 x + y * z 的计算结果为 x + (y * z),因为 * 运算符的优先级高于 + 运算符。 逻辑操作的优先级低于按位运算。

下表汇总了 MIDL 3.0 的运算符,按优先级从高到低的顺序列出运算符类别。 同一类别中的运算符具有相等的优先级。

类别 表达 描述
主要 x++ 递增后
x-- 递减后
+x 身份
-x 否定
!x 逻辑求反
~x 按位求反
++x 预增量
--x 减量前
乘法 x * y 乘法
x / y 划分
x % y 剩余
添加剂 x + y 加法、字符串串联、委托组合
x – y 减法、委托删除
转变 x << y 向左移动
x >> y 向右移动
按位 AND x & y 整数按位 AND
按位 XOR x ^ y 整数位 XOR
按位 OR x |y 整数按位 OR
逻辑 AND x && y 布尔逻辑 AND
逻辑 OR x ||y 布尔逻辑 OR

(或运行时类)是 MIDL 3.0 类型中最基本的类。 类是单个单元中方法、属性和事件的聚合的定义。 类支持 继承多态性机制,派生类 可以扩展和专门 基类

使用类定义定义定义新类类型。 类定义以一个标头开头,该标头指定 runtimeclass 关键字、类的名称、基类(如果给定)和类实现的接口。 标头后跟类正文,由分隔符 { 和 }之间写入的成员声明列表组成。

下面是名为 Area的简单类的定义。

runtimeclass Area
{
    Area(Int32 width, Int32 height);

    Int32 Height;
    Int32 Width;

    static Int32 NumberOfAreas { get; };
}

这定义了一个名为 Area的新 Windows 运行时类,该类的构造函数采用两个 Int32 参数、两个 Int32 名为 height 和 Width的读写属性,以及名为 numberOfAreas的静态只读 属性。

默认情况下,运行时类是密封的,并且不允许从它派生。 请参阅 基类

若要将 XAML 绑定到视图模型,需要在 MIDL 中定义视图模型运行时类。 请参阅 XAML 控件;有关更多详细信息,请绑定到 C++/WinRT 属性

你可以声明类不支持任何实例(因此必须仅包含静态成员),方法是使用 static 关键字为运行时类定义添加前缀。 将非静态成员添加到类后会导致编译错误。

static runtimeclass Area
{
    static Int32 NumberOfAreas { get; };
}

静态类不同于空类。 另请参阅 空类

可以通过使用 partial 关键字为运行时类定义添加前缀来指示类定义不完整。 编译器遇到的所有分部类定义都合并为单个运行时类。 此功能主要用于 XAML 创作方案,其中某些分部类是计算机生成的。

修饰语 意义
静态的 类没有实例。 因此,仅允许静态成员。
部分 类定义不完整。

有关高级修饰符,请参阅 合成和激活

成员访问修饰符

由于 MIDL 3.0 是描述 Windows 运行时类型公共表面的定义语言,因此无需显式语法来声明成员的公共可访问性。 所有成员都是隐式公开的。 这就是 MIDL 3.0 不需要也不允许(有效冗余)public 关键字的原因。

基类

类定义可以通过遵循类名称和类型参数以及冒号和基类的名称来指定基类。 省略基类规范与从类型 对象 派生相同(换句话说,从 IInspectable派生)。

注意

视图模型类(事实上,在应用程序中定义的任何运行时类)不需要派生自基类。

在应用程序中定义的任何运行时类 派生自基类称为 可组合 类。 可组合类存在约束。 要使应用程序通过 Visual Studio 和 Microsoft 应用商店用于验证提交的 Windows 应用认证工具包 测试(因此,要使应用程序成功引入到 Microsoft 应用商店),可组合类最终必须派生自 Windows 基类。 这意味着继承层次结构的根目录的类必须是源自 Windows.* 命名空间的类型。

请参阅 XAML 控件;有关更多详细信息,请绑定到 C++/WinRT 属性

在下一个示例中, 的基类 区域区域 的基类 Windows.UI.Xaml.DependencyObject

unsealed runtimeclass Area : Windows.UI.Xaml.DependencyObject
{
    Area(Int32 width, Int32 height);
    Int32 Height;
    Int32 Width;
}

runtimeclass Volume : Area
{
    Volume(Int32 width, Int32 height, Int32 depth);
    Int32 Depth;
}

注意

此处,区域 在同一源文件中定义。 有关利弊的讨论,请参阅 将运行时类分解为 Midl 文件 (.idl)

类继承其基类的成员。 继承意味着类隐式包含其基类的所有成员,基类的构造函数除外。 派生类可以向其继承的成员添加新成员,但无法删除继承成员的定义。

在上一示例中,Volume区域继承 高度Width 属性。 因此,每个 实例都包含三个属性:HeightWidthDepth

通常,类型解析规则要求在引用时完全限定类型名称。 异常是在与当前类型相同的命名空间中定义类型时。 如果 区域 位于同一命名空间中,则上面的示例将按编写方式。

实现的接口

类定义还可以指定类实现的接口列表。 将接口指定为遵循(可选)基类的接口的逗号分隔列表。

在下面的示例中,Area 类实现 IStringable 接口;和 Volume 类实现 IStringable 和假设 IEquatable 接口。

unsealed runtimeclass Area : Windows.Foundation.IStringable
{
    Area(Int32 width, Int32 height);
    Int32 Height;
    Int32 Width;
}

runtimeclass Volume : Area, Windows.Foundation.IStringable, IEquatable
{
    Volume(Int32 width, Int32 height, Int32 depth);
    Int32 Depth;
}

在 MIDL 中,不会在类上声明接口的成员。 当然,你必须在实际实现上声明和定义它们。

成员

类的成员 静态成员实例成员。 静态成员属于类。 实例成员属于对象(即类的实例)。

此表显示类可以包含的成员类型。

成员类型 描述
构造 函数 初始化类实例或初始化类本身所需的操作
性能 与读取和写入类实例或类本身的命名属性关联的操作
方法 可由类实例或类本身执行的计算和操作
事件 可由类实例引发的通知

构造 函数

MIDL 3.0 支持实例构造函数的声明。 实例构造函数 是实现初始化类实例所需的操作的方法。 构造函数可能不是静态的。

构造函数声明为实例方法(但不具有返回类型),并且与包含类同名。

实例构造函数可以重载。 例如,下面的 Test 类声明三个实例构造函数;一个没有参数(默认 构造函数),一个采用 Int32 参数,一个参数采用两个 参数(参数化 构造函数)。

runtimeclass Test
{
    Test();
    Test(Int32 x);
    Test(Double x, Double y);
}

有关参数列表语法的详细信息,请参阅下面的 方法

将继承实例属性、方法和事件。 实例构造函数不继承(但有一个例外),类没有在类中实际声明的构造函数以外的实例构造函数。 如果没有为类提供实例构造函数,则无法直接实例化该类。 对于此类,通常在其他位置有一个返回类实例的工厂方法。

异常是未密封的类。 未密封的类可以具有一个或多个受保护的构造函数。

性能

属性 在概念上类似于字段(例如 C# 字段;或 MIDL 3.0 结构的字段)。 属性和字段都是具有名称和关联类型的成员。 但是,与字段不同,属性不表示存储位置。 相反,属性具有 访问器,用于指定要在读取或写入属性时执行的函数。

属性声明为结构字段,但声明以在分隔符 { 和 }之间写入的 get 关键字和/或 set 关键字结尾,以分号结尾。

具有 get 关键字和 set 关键字的属性是 读写属性。 只有 get 关键字的属性是 只读属性。 Windows 运行时不支持仅写属性。

例如,前面看到的 Area类包含两个名为 heightWidth的两个读写属性。

unsealed runtimeclass Area
{
    Int32 Height { get; set; };
    Int32 Width; // get and set are implied if both are omitted.
}

Width 的声明 省略大括号和 getset 关键字。 遗漏意味着该属性是读写的,在语义上与按该顺序提供 getset 关键字相同,即get,后跟 set

此外,只能指定 get 关键字以指示该属性为只读。

// Read-only instance property returning mutable collection.
Windows.Foundation.Collections.IVector<Windows.UI.Color> Colors { get; };

Windows 运行时不支持仅写属性。 但是,只能指定 set 关键字,以将现有只读属性修改为读写属性。 以此版本的 Area 为例。

unsealed runtimeclass Area
{
    ...
    Color SurfaceColor { get; };
}

如果以后想要使 SurfaceColor 属性读写,并且无需与 Area 的先前定义保持二进制兼容性(例如,Area 类是每次重新编译的应用程序中的一种类型),则只需将 set 关键字添加到现有的 SurfaceColor 声明中即可。

unsealed runtimeclass Area
{
    ...
    Color SurfaceColor { get; set; };
}

另一方面,如果 确实需要二进制稳定性(例如,Area 类是寄送给客户的库中的组件),则不能将 set 关键字添加到现有属性声明中。 这样做会将二进制接口更改为类。

在这种情况下,请将属性 set 关键字添加到类末尾的属性的其他定义,如下所示。

unsealed runtimeclass Area
{
    ...
    Color SurfaceColor { get; };
    ...
    Color SurfaceColor { set; };
}

编译器为仅写属性生成错误。 但这不是这里正在做的事情。 由于上述属性声明为只读,所以添加 set 关键字不会声明只写属性,而是声明读写属性。

属性的 Windows 运行时实现是接口上的一两个访问器方法。 属性声明中 get 和 set 关键字的顺序决定了支持接口中 get 和 set 访问器方法的顺序。

get 访问器对应于具有属性类型的返回值(属性 getter)的无参数方法。

访问器对应于一个方法,该方法 名为 ,并且没有返回类型—属性 setter。

因此,这两个声明生成不同的二进制接口。

Color SurfaceColor { get; set; };
Color SurfaceColor { set; get; };
静态和实例属性

与方法类似,MIDL 3.0 支持实例属性和静态属性。 静态属性使用前缀 static 修饰符进行声明,并且未声明实例属性。

方法

方法 是实现可由类实例或类本身执行的计算或操作的成员。 可通过类访问 静态方法实例方法 通过类的实例访问。

方法具有参数列表(可能为空),这些参数表示传递给该方法的值或变量引用。 方法还具有 返回类型,它指定方法计算和返回的值的类型。 如果未返回值,则方法的返回类型 void

// Instance method with no return value.
void AddData(String data);

// Instance method *with* a return value.
Int32 GetDataSize();

// Instance method accepting/returning a runtime class.
// Notice that you don't say "&" nor "*" for reference types.
BasicClass MergeWith(BasicClass other);

// Asynchronous instance methods.
Windows.Foundation.IAsyncAction UpdateAsync();
Windows.Foundation.IAsyncOperation<Boolean> TrySaveAsync();

// Instance method that returns a value through a parameter.
Boolean TryParseInt16(String input, out Int16 value);

// Instance method that receives a reference to a value type.
Double CalculateArea(ref const Windows.Foundation.Rect value);

// Instance method accepting or returning a conformant array.
void SetBytes(UInt8[] bytes);
UInt8[] GetBytes();

// instance method that writes to a caller-provided conformant array
void ReadBytes(ref UInt8[] bytes);

方法的 签名 在声明该方法的类中必须是唯一的。 方法的签名包括方法的名称、其参数的类型以及/或其参数的数目。 方法的签名不包括返回类型。

方法可见性修饰符

当方法存在于派生类中时,方法 可能有两个可选可见性修饰符之一。

可重写的 修饰符指出,此方法可由属于子类的方法(同名和签名)重写。

受保护的 修饰符指出,此方法只能由后续派生类中的成员访问。

方法重载

方法 重载 允许同一类中的多个方法具有相同的名称,前提是其参数在数字上不同(换句话说,方法具有不同的 arity)。

runtimeclass Test
{
    static void F();
    static void F(Double x);
    static void F(Double x, Double y);
}

注意

具有相同名称的所有方法都应具有不同的 arity。 这是因为弱类型编程语言不支持按类型重载。

参数

参数 用于将值或变量引用传递给方法。 参数 描述具有类型和名称的槽,以及一些修饰关键字(可选)。 参数 是从方法的调用方传递到被调用方的实际值。

方法的参数从调用方法时指定的特定 参数 获取其值。 调用方和被调用方之间的参数传递方式取决于参数的类型。 默认情况下,所有参数 输入参数,即仅从调用方封送给被调用方。 可以添加修饰关键字 refref constout 来修改调用方和被调用方之间的封送的默认方向,并创建 输出参数。 不过,并非所有关键字都对所有参数类型有效;下面详细介绍了有效的组合。

重要

公共语言运行时(CLR)具有概念和修饰符关键字,这些关键字似乎类似于本节中所述的关键字。 但是,在实践中,这些修饰符的效果与 Windows 运行时的设计和功能无关。

值类型隐式 输入参数,默认情况下,参数的副本从调用方传递到被调用方。 值参数可以使用 关键字转换为 输出参数 ;在这种情况下,参数仅从被调用方封送回调用方。

runtimeclass Test
{
    static void Divide(Int32 x, Int32 y, out Int32 result, out Int32 remainder);
}

作为特殊的性能优化,结构类型(且没有其他类型)通常按值作为完整副本传递,可以通过指向不可变结构的指针传递。 这是使用 ref constconst ref) 关键字实现的,该关键字将结构参数标记为输入参数,但指示封送器将指针传递给结构的存储,而不是传递结构的完整副本。 但请注意,结构是不可变的;指针在概念上是 指针。 没有涉及拳击。 例如,接受与 Matrix4x4一样大的值时,这是一个实际选择。

runtimeclass Test
{
    static Boolean IsIdentity(ref const Windows.Foundation.Numerics.Matrix4x4 m);
}

引用类型也是隐式输入参数,这意味着调用方负责分配对象并将引用作为参数传递;但是,由于该参数是对对象的引用,因此调用方在调用后会观察到对该对象的修改。 或者,可以使用 out 关键字将引用类型设置为输出参数。 在这种情况下,角色是反向的;被调用方是分配对象并将其返回给调用方。 同样,ref 关键字不能一般用于引用类型(请参阅下面的异常)。

runtimeclass Test
{
    static void CreateObjectWithConfig(Config config, out MyClass newObject);
}

下表汇总了值参数和引用参数封送关键字的行为:

行为 分配者 关键词 类型 言论
输入参数 访客 (无) 所有类型 默认行为
ref const 仅结构 性能优化
输出参数 被调用方 out 所有类型

Windows 运行时支持数组类型,其作为参数的行为稍有不同。 数组 是一种数据结构,其中包含一些按顺序存储并通过索引访问的变量。 数组中包含的变量(也称为数组 元素)都是同一类型,此类型称为数组 元素类型。

MIDL 3.0 支持 单维数组的声明

数组参数 是引用类型,就像所有引用类型一样,默认情况下是输入参数。 在这种情况下,调用方将数组分配给被调用方,该数组可以读取其元素,但不能修改它们(只读)。 这称为 传递数组 模式。 或者,可以通过将 ref 关键字添加到参数来使用 填充数组 模式;在该设置中,数组仍由调用方分配,但在概念上是输出参数,即被调用方将填充数组元素的值。 最后,最后一个模式是 接收数组,其中(如所有输出引用参数),被调用方在返回给调用方之前分配和初始化参数。

runtimeclass Test
{
    // Pass array pattern: read-only array from caller to callee
    void PassArray(Int32[] values);

    // Fill array pattern: caller allocates array for callee to fill
    void FillArray(ref Int32[] values);

    // Receive array pattern: callee allocates and fill an array returned to caller
    void ReceiveArray(out Int32[] values);
}

下表汇总了数组及其元素的行为:

数组模式 关键词 分配者 被调用方访问的元素
“传递数组” (无) 访客 只读
“填充数组” ref 访客 仅写
“接收数组” out 被调用方 读写

有关将 C 样式数组参数(也称为符合性数组)与 C++/WinRT 配合使用的详细信息,请参阅 数组参数

静态和实例方法

使用前缀 static 修饰符声明的方法是 静态方法。 静态方法无权访问特定实例,因此只能直接访问该类的其他静态成员。

在没有 static 修饰符的情况下声明的方法是 实例方法。 实例方法有权访问特定实例,并且可以访问类的静态成员和实例成员。

以下 Entity 类具有静态成员和实例成员。

runtimeclass Entity
{
    Int32 SerialNo { get; };
    static Int32 GetNextSerialNo();
    static void SetNextSerialNo(Int32 value);
}

每个 实体 实例都包含其自己的序列号(大概此处未显示一些其他信息)。 在内部,实体 构造函数(类似于实例方法)使用下一个可用的序列号初始化新实例。

SerialNo 属性提供对调用 属性获取 方法的实例的序列号的访问权限。

GetNextSerialNoSetNextSerialNo 静态方法可以访问 Entity 类的静态成员的内部 下一个可用序列号 静态成员。

可重写和保护的方法

Windows 运行时类型中的所有方法都是有效的虚拟方法。 调用虚拟方法时,运行时类型 进行调用的实例确定要调用的实际方法实现。

可以在派生类中 重写 方法。 当实例方法声明包含 overridable 修饰符时,该方法可由派生类重写。 派生类是否确实重写可重写的基类方法由实现确定;它不在元数据中。 如果派生类重新声明基类中的方法,则声明一个与派生类方法并置的新方法,而不是重写它。

当实例方法声明包含 protected 修饰符时,该方法仅对派生类可见。

事件

事件 声明是指定类是事件源的成员。 此类事件源向任何实现委托的收件人(具有特定签名的方法)提供通知。

使用 event 关键字声明事件,后跟委托类型名称(描述所需的方法签名),后跟事件的名称。 下面是一个示例事件,该事件使用平台中的现有委托类型。

runtimeclass Area
{
    ...
    event Windows.UI.Xaml.WindowSizeChangedEventHandler SizeChanged;
    ...
}

事件声明隐式将两个方法添加到类:添加 方法,客户端调用该方法将事件处理程序添加到源,删除 方法,客户端调用该方法删除以前添加的事件处理程序。 下面是更多示例。

// Instance event with no meaningful payload.
event Windows.Foundation.TypedEventHandler<BasicClass, Object> Changed;

// Instance event with event parameters.
event Windows.Foundation.TypedEventHandler<BasicClass, BasicClassSaveCompletedEventArgs> SaveCompleted;

// Static event with no meaningful payload.
static event Windows.Foundation.EventHandler<Object> ResetOccurred;

// Static event with event parameters.
static event Windows.Foundation.EventHandler<BasicClassDeviceAddedEventArgs> DeviceAdded;

按照约定,始终将两个参数传递给 Windows 运行时事件处理程序:发送方的标识和事件参数对象。 发送方是引发事件的对象,对于静态事件为 null。 如果事件没有有意义的有效负载,则事件参数是 对象 其值为 null。

代表

委托类型 指定具有特定参数列表和返回类型的方法。 事件的单个实例可以包含对其委托类型的实例的任意数量的引用。 声明类似于常规成员方法的声明,只是它存在于运行时类之外,并且其前缀为 delegate 关键字。

委托可以将方法视为可以分配给变量并作为参数传递的实体。 委托类似于某些其他语言中发现的函数指针的概念。 但是,与函数指针不同,委托面向对象且类型安全。

如果不想从平台使用 WindowSizeChangedEventHandler 委托类型,则可以定义自己的委托类型。

delegate void SizeChangedHandler(Object sender, Windows.UI.Core.WindowSizeChangedEventArgs args);

SizeChangedHandler 委托类型的实例可以引用采用两个参数的任何方法(ObjectWindowSizeChangedEventArgs),并返回 void。 在讨论 结构后,还可以将 WindowSizeChangedEventArgs 参数替换为自己的 事件参数 类型。

委托的一个有趣且有用的属性是,它不知道或关心它引用的方法的类;所有重要的是,引用的方法的参数和返回类型与委托相同。

可以选择使用 [uuid(...)]将委托声明特性化。

另请参阅返回 HRESULT的委托

结构

结构 是可以包含数据成员(字段)的数据结构。 但是,与类不同,结构是值类型。

结构对于具有值语义的小型数据结构特别有用。 坐标系中的复数或点是结构的良好示例。 使用结构而不是用于小型数据结构的类可能会对应用程序执行的内存分配数量产生很大差异。

让我们使用一个示例来对比类和结构。 下面是 Point 第一个版本的

runtimeclass Point
{
    Point(Int32 x, Int32 y);
    Int32 x;
    Int32 y;
}

此 C# 程序创建并初始化 Point的 100 个实例的数组。 通过将 Point 实现为类,将实例化 101 个单独的对象:一个用于数组对象本身;以及 100 个 元素中的一个。

class Test
{
    static Test()
    {
        Point[] points = new Point[100];
        for (Int32 i = 0; i < 100; ++i) points[i] = new Point(i, i);
    }
}

更高性能的替代方法是 结构而不是类。

struct Point
{
    Int32 x;
    Int32 y;
};

现在,仅实例化一个对象- 数组对象本身。 元素存储在数组内;处理器缓存能够用于强大效果的内存排列。

更改结构是二进制中断性变更。 因此,引入后,作为 Windows 本身的一部分实现的结构不会改变。

接口

接口 定义可由类实现的协定。 接口可以包含方法、属性和事件,就像类一样。

与类不同,接口不提供它定义的成员的实现。 它只指定任何实现接口的类必须提供的成员。

接口 需要 实现接口的类来实现其他接口。 在以下示例中,IComboBox 接口要求任何实现 IComboBox的类也实现 ITextBoxIListBox。 此外,实现 IComboBox 的类还必须实现 IControl。 这是因为 ITextBoxIListBox 都需要

interface IControl
{
    void Paint();
}

interface ITextBox requires IControl
{
    void SetText(String text);
}

interface IListBox requires IControl
{
    void SetItems(String[] items);
}

interface IComboBox requires ITextBox, IListBox
{
    ...
}

类可以实现零个或多个接口。 在下一个示例中,EditBox 类实现 IControlIDataBound

interface IDataBound
{
    void Bind(Binder b);
}

runtimeclass EditBox : IControl, IDataBound
{
}

对于 Windows 平台中的 Windows 运行时类型,如果使用这些类型的开发人员应实现该接口,则会定义接口。 定义接口的另一个用例是,当多个运行时类实现接口时,使用这些运行时类的开发人员将通过该通用接口一般访问不同类型的对象(从而多态)。

注意

考虑在 MIDL 3.0 中使用 requires 关键字。 这可能会导致混乱的设计,尤其是在考虑版本控制时。

枚举

枚举类型(或枚举类型或枚举)是具有一组命名常量的非重复值类型。 以下示例定义并使用一个名为 Color 的枚举类型,其中包含三个常量值:红色绿色蓝色

enum Color
{
    Red,
    Green,
    Blue, // Trailing comma is optional, but recommended to make future changes easier.
};

每个枚举类型都有一个对应的整型类型,称为枚举类型的 基础类型。 枚举的基础类型为 Int32UInt32

Windows 运行时支持两种类型的枚举:正常枚举 枚举,标志 枚举。 普通类型的枚举表示一组独占值;而标志类型之一表示一组布尔值。 若要为标志枚举启用按位运算符,MIDL 3.0 编译器将生成C++运算符重载。

标志枚举应用 [flags] 属性。 在这种情况下,枚举的基础类型 UInt32。 当 [flags] 属性不存在(普通枚举),枚举的基础类型 Int32。 无法将枚举声明为任何其他类型。

[flags]
enum SetOfBooleanValues
{
    None   = 0x00000000,
    Value1 = 0x00000001,
    Value2 = 0x00000002,
    Value3 = 0x00000004,
};

枚举类型的存储格式和可能值的范围由其基础类型确定。 枚举类型可以接受的值集不受其声明枚举成员的限制。

以下示例定义名为 Alignment的枚举类型,其基础类型为 Int32

enum Alignment
{
    Left = -1,
    Center = 0,
    Right = 1
};

与 C 和 C++ 一样,MIDL 3.0 枚举可以包含指定成员值的常量表达式(如上所示)。 每个枚举成员的常量值必须位于枚举的基础类型范围内。 如果枚举成员声明未显式指定值,则为该成员提供值零(如果它是枚举类型中的第一个成员),或前面为枚举成员加上一个的文本值。

以下示例定义一个名为 “权限”的枚举类型,其基础类型 为 UInt32

[flags]
enum Permissions
{
    None = 0x0000,
    Camera = 0x0001,
    Microphone = 0x0002
};

属性

MIDL 3.0 源代码中的类型、成员和其他实体支持控制其行为的某些方面的修饰符。 例如,使用 protected 访问修饰符控制方法的可访问性。 MIDL 3.0 通用化此功能,使用户定义的声明性信息类型可以附加到程序实体,并在运行时从元数据中检索。

程序通过定义和使用 属性来指定此附加声明性信息。

下一个示例定义一个 HelpAttribute 属性,该属性可以放置在程序实体上,以提供与其关联的文档的链接。 如你所看到的,属性本质上是一种结构类型,因此它没有构造函数,并且仅包含数据成员。

[attributeusage(target_runtimeclass, target_event, target_method, target_property)]
attribute HelpAttribute
{
    String ClassUri;
    String MemberTopic;
}

可以通过在关联声明前的方括号内指定其名称以及任何参数来应用特性。 如果属性的名称以 Attribute 结尾,则在引用属性时可以省略该名称的该部分。 例如,可以使用 HelpAttribute 属性, 如下所示。

[Help("https://docs.contoso.com/.../BookSku", "BookSku class")]
runtimeclass BookSku : Windows.UI.Xaml.Data.INotifyPropertyChanged
{
    [Help("https://docs.contoso.com/.../BookSku_Title", "Title method")]
    String Title;
}

可以使用属性后面的范围块将同一属性应用于多个声明。 也就是说,属性紧接着是应用属性的声明周围的大括号。

runtimeclass Widget
{
    [Help("https://docs.contoso.com/.../Widget", "Widget members")]
    {
        void Display(String text);
        void Print();
        Single Rate;
    }
}

作为 Windows 本身的一部分实现的属性通常位于 Windows.Foundation 命名空间中。

如第一个示例所示,对属性定义使用 [attributeusage(<target>)] 属性。 有效目标值为 target_alltarget_delegatetarget_enumtarget_eventtarget_fieldtarget_interfacetarget_methodtarget_parametertarget_propertytarget_runtimeclasstarget_struct。 可以在括号中包含多个目标,用逗号分隔。

可应用于属性的其他属性是 [allowmultiple][attributename("<name>")]

参数化类型

以下示例生成 错误MIDL2025:[msg]语法错误 [context]:预期 > 或附近>>“

Windows.Foundation.IAsyncOperation<Windows.Foundation.Collections.IVector<String>> RetrieveCollectionAsync();

相反,在两个 > 字符之间插入一个空格,以便模板结束字符对不会错误地解释为右移运算符。

Windows.Foundation.IAsyncOperation<Windows.Foundation.Collections.IVector<String> > RetrieveCollectionAsync();

以下示例生成 错误MIDL2025:[msg]语法错误 [context]:预期 > 或“[”附近。 这是因为将数组用作参数化接口的参数类型参数无效。

Windows.Foundation.IAsyncOperation<Int32[]> RetrieveArrayAsync();

有关解决方案,请参阅 异步返回数组