MIDL是Microsoft Interface Definition Language的简称,是微软用来描述WinRT组件或者其他面向对象组件的接口描述语言,但其功能十分强大,已经接近面向对象的编程语言了。MIDL3.0和2.0相比,语法更加简洁了。

Windows 10 SDK 10.0.17134.0 (Windows 10, version 1803) 中带有midl.exe 8.01.0622,可以支持MIDL3.0,支持/winrt选项。

MIDL文件通常以.idl结尾,在%WindowsSdkDir%Include<WindowsTargetPlatformVersion>\winrt目录可以看到用于WinRT的MIDL文件。midl.exe处理过的.idl文件产生的结果是WinRT的元文件,也就是.winmd文件。这样元文件可以被不同的语言(如C++,C#,Javascript等)录用。

MIDL里面既可以使用WinRT类型,也可以使用COM指针。前者比较通用,而后者只能被C++使用。不过可以通过C++把COM对象包装成一个Windows Runtime component,来供其他语言录用。

MIDL3.0有点类似C#,一个.idl里面包含一个namespace,其中有各种子namespace以及各种类型:

  • 类型包括class, interface, structure, 和enumeration
  • 类型的成员包括field, method,property,事件,等待

如果想使用另一个.idl中定义的类型,可以import那个.idl。如果不能直接访问.idl文件,可以在midl.exe的命令行上使用/reference开关来引用相应的.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);
    }
}

可以从命令行编译上面的.idl文件:

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

可以把多个生成的.winmd文件合成一个,参考Factoring runtime classes into Midl files (.idl)

MIDL中的namespace可以直接使用点号进行嵌套,如下面例子中的Windows.Foundation

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

MIDL中依然对类型区分值和引用:

  • 值类型:基础类型(整型、浮点、字符串等类型),枚举类型,struct和nullable
  • 引用类型:runtimeclass ,interface,delegate

关于类型的一些注意点:

  • struct不支持继承,不需要在堆上分配,成员只能是值类型。主要用于紧凑,不多变的数据类型。
  • 所有interface类型都是从IInspectable派生出来的,Object也映射到IInspectable
  • runtimeclass 默认从Object继承,成员只能是properties, method和事件。默认情况下,runtimeclass是sealed(除非制定成unsealed ),不能被继承。成员默认是public类型的。支持使用static指定类级别的成员。如果runtimeclass是从某个base class派生的,那么这个runtimeclass称为composable class,其根类型必须是某个Windows.* 命名空间中的类型。
  • 没有构造函数的runtimeclass无法直接实例化,只能通过某些工厂方法来实例化。
  • delegate类似函数指针,不过其本身却是一个对象
  • 对于enum来说,所定义的每个enum都是不同的类型,但是这些类型的隐含类型要么是Int32,要么是Uint32。在一个enum前面加上[flags]属性,其隐含类型就变成了Uint32。enum的成员可以使用静态变量表达式赋值。如果不赋值,首个成员默认为0,后面的成员依次加一。
  • MIDL还支持单维数组,比如Int32[]是一个单维数组,元素类型为Int32
  • nullable类型由Windows.Foundation.IReference<T>提供支持。比如Windows.Foundation.IReference<Int32>是一个nullable类型,能包含任意Int32值,以及null。

属格(property)跟属性或者字段类似,但是不指定存储位置,并且需要指定get/set访问符(默认可以同时get/set)。get是必须的,set是可选的,但是增删set属性会导致二进制不兼容。可以另起一行,重新指定:

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

另外需要注意的是Color SurfaceColor { get; set; };Color SurfaceColor { set; get; };是不兼容的。通常我们在接口中只暴露函数,而不直接数据,属格应该也是由函数支持的,可能对于这两种写法底下支持此属格的函数不一致。

runtimeclass的另一种成员是方法,方法由名字和调帖(参数,返回值)构成。方法默认都是公开的,但是支持override和protected两种修饰符。前者用于重载一个方法,后者用于限制方法的使用范围。方法可以重载(overloading),也就是可以同名不同参(参数个数必须不同)。参数可以有四种:值参,静态引用参,输出参,和数组参。下面是个例子:

runtimeclass Test
{
    static void Swap(const ref Matrix4X4 x, const ref Matrix4X4 y);
    static void Divide(Int32 x, Int32 y, out Int32 result, out Int32 remainder);
    void PassArray (Int32[] values);
    void FillArray (ref Int32[] values);
    void ReceiveArray (out Int32[] values);
}

默认情况下,runtimeclass的所有方法都是虚方法,在调用的时候决定与对象实例的绑定。

runtimeclass的成员还包括event,一个event可以与多个delegate绑定,并为这些delegate提供通知。event其实对应着两个方法,一个方法添加delegate,另一个方法删除delegate。下面是一个例子:

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

其中Windows.UI.Xaml.WindowSizeChangedEventHandler是一个delegate类型,由系统定义的。不过你业可以创建自己的delegate类型:

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

Attributes(属性)可以给MIDL中的类型,成员以及其他实体增加标注。这些属性有点像标注。下面是一些例子:

attribute HelpAttribute
{
    HelpAttribute(String classUri);
    String ClassUri { get; };
    String MemberTopic { get; set; };
}

[Help("https://docs.contoso.com/.../Widget")]
runtimeclass Widget
{
    [Help("https://docs.contoso.com/.../Widget", MemberTopic="Display")]
    void Display(String text);
}

runtimeclass Widget
{
// 将属性应用于多个成员
    [Custom()]
    {
        void Display(String text);
        void Print();
        Single Rate;
    }
}

MIDL同时也支持对类型进行参数化(像C++的模板):

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

注意,数组不能作为类型的参数。> >中间必须由空格。

(完)

更新

2019-08-10

Synthesizing interfaces (MIDL 3.0)

A Windows Runtime class operates in terms of interfaces.

RuntimeClass是一个由MIDL描述的抽象的概念。在代码实现的时候由两个类型来支撑,一个是实现类(implementation type),登告该RuntimeClass的组件必须具备该RuntimeClass的实现类;另一个是投射类(projected type),录用该RuntimeClass的组件必须具备投射类。

MIDL编辑器和自动从RuntimeClass中合成(Synthesis)相应的接口:

  • 一个runtimeclass有构造函数才能被激活,具备相应的工厂类可以实例化其投射类。这个工厂类实现了IActivationFactory接口。
    • 如果runtimeclass拥有默认构造函数,那么会被描述为Null类型的[Activatable]
    • 如果具有带参数的构造函数,那么会生成名为I<className>Factory形式的接口(如果接口被占用,就在后面加上数字做区别)。I<className>Factory会被加上[exclusiveto(<className>)]属性;而该runtimeclass会被加上[activatable(I<className>Factory)]属性。

例子:

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

等价于

[exclusiveto(Area)]
interface IAreaFactory
{
    Area(Int32 width, Int32 height);
}

[activatable()]
[activatable(IAreaFactory)]
runtimeclass Area
{
    ...
}

类的成员也会被提取到I<className>的接口中

runtimeclass Area : Windows.Foundation.IStringable
{
    Int32 Height;
    Int32 Width;
}

等价于

[exclusiveto(Area)]
interface IArea
{
    Int32 Height;
    Int32 Width;
}

runtimeclass Area : IArea, Windows.Foundation.IStringable
{
}

对于静态成员的话,也会被提取到接口中:

runtimeclass Area : Windows.Foundation.IStringable
{
    static Int32 NumberOfAreas { get; };
}

等价于:

[exclusiveto(Area)]
interface IAreaStatics
{
    Int32 NumberOfAreas { get; };
}

[static(IAreaStatics)]
runtimeclass Area : Windows.Foundation.IStringable
{
}

对于protected和overridable的成员,也是如上操作。

简单地理解,runtimeclass的各种成员其实都对应某些函数,然后接口就是这些函数的集合。感觉有点像objective-C的工作机制。

(更新完)