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();
注意,数组不能作为类型的参数。> >
中间必须由空格。
- Introduction to Microsoft Interface Definition Language 3.0
- Factoring runtime classes into Midl files (.idl)
(完)
更新
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拥有默认构造函数,那么会被描述为Null类型的
例子:
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的工作机制。
(更新完)