WinRT 介绍
简介
最近我们发现越来越多的人对Windows Phone 8感兴趣,但是很遗憾,目前我们真的不能提供任何Windows Phone 8特定的消息。微软在适当的时候会做出announcement。现阶段我们只能告诉大家,正如在这篇blog中指出的那样,Windows Phone 8和Windows 8会使用同样的内核,会支持C++和DirectX,以及其它一些Windows 8 Metro style applications的功能。
本文将会对Windows 8 Metro后台使用的一个runtime WinRT进行介绍,本文提供的信息有部分可能会在Windows Phone 8中也有支持,但也有可能有些功能会没有。我们不做任何保证,只是单纯介绍Windows 8而已。
Metro,WinRT,和Windows Kernel的关系
首先大家必须搞清楚Metro,WinRT,和Windows Kernel的关系。大家听到最多的一个名称肯定就是Metro了。事实上,Metro指的是一种风格,而非运行时或者框架。Windows Phone 7的程序其实也是Metro style applications,只是使用的框架和运行时与Windows 8不尽相同而已。让我们来看一张简单的结构关系图,这张图转自于这篇MSDN blog:
从这幅画中大家可以看到,位于最底等的是Windows Kernel,也就是操作系统的核心。这个内核提供了操作系统最最基础的功能,例如进程管理,内存/虚拟内存分配,文件系统,网络,安全,和各种设备的驱动程序打交道,等等等等。同时它也提供了很多API。这些API可以在使用C++的desktop application中直接调用,但是不能直接被Metro程序调用,因为在Metro和Windows Kernel之间还隔着一层WinRT。
让我们撇开desktop applications不谈,WinRT这一层是一个runtime。它分为两大块,一块是application model,也就是为Metro应用程序提供的一套基础模型,等一下我们会具体介绍这个模型中有哪些内容。另一块我们可以归纳为building blocks,也就是一些很多框架都需要用到的组件,由于时间关系,今天我们不可能详细介绍每一个组件,等一下我们会简要叙述一些比较重要的组件。事实上啊,这些组件中间有很多都既可以在desktop程序中使用,也可以在Metro程序中使用,所以说其实把它们归在Windows Kernel一级也不为过。举个简单的例子,DirectX就可以在全部两种平台中使用,只是在Metro中通常你只能通过C++调用它而已。一个Metro程序如果使用C++编写,是可以直接在WinRT这一层上进行开发的。可是如果使用JavaScript或者.NET语言,就不能直接使用WinRT,而必须再通过一层framework。
在WinRT之上的是框架,Windows 8提供了两套框架:HTML和XAML。如前所述,如果你使用C++,你可以绕开框架这一层,或者也可以使用XAML框架。框架在运行时的基础上提供了很多功能,其中最显著的就是UI framework。WinRT是不关系UI的。你可以使用WinRT中的DirectX进行绘图,但是像button这样的控件,界面元素的外观,各种页面布局,等等,就必须自行处理了。无论是HTML还是XAML,框架都为你实现了这些很常用的功能。你不需要写代码,只要用HTML/CSS或者XAML的markup声明你要什么元素,框架就会自行帮你展现这些元素。事实上很多开发人员最先接触到的都是框架,而不是运行时或者操作系统内核。
除了HTML和XAML之外,还有很多框架,不过这些框架并不是Windows 8自身提供的。例如,使用JavaScript进行开发时,常常会用到jQuery这个框架。使用.NET进行开发时,常常会用到LINQ这个框架。很多框架都帮助我们实现了特定的功能,让我们的开发变得更为简单。这个世界上的框架是如此之多,让人看得眼花缭乱,有时候甚至不知道用哪个框架才好。我们这边只能给大家一个宽泛的建议:首先把HTML和XAML这种大的框架学好,在这个基础上,视你的程序需要,去使用相关的小的框架。如果找不到一个适合自己的程序的框架,你也完全可以不使用框架,自行实现所需要的功能。有些框架是可以混用的,但也有些不行。所以到最后,你可能还是需要自己去实现一些功能。如果你纠结于一个小的框架,万一将来换了一个场景,不能使用这个框架了,就又必须从头来过了。所以对于小的框架,不要花太多时间去调查。这个项目中需要用到就去用,但是不要喧宾夺主,认为程序开发必须依赖于某个小框架。
WinRT的内容
接下来我们把精力放在WinRT这个运行时之上。让我们来看看作为一个运行时,到底会提供哪些功能。
首先,如果操作系统内核已经实现的功能,运行时就不需要重复一次了。例如,WinRT不会关心任何与进程管理相关的事项。不过也有例外,例如安全性是一个非常宽泛的话题。操作系统内核有自己的安全机制,例如需要用户提供文字/图片密码才能登录系统,每个用户只能访问自己的文件,等等。而WinRT提供的安全性机制,则是为了限制应用程序不要做不该做的事。例如,一个使用WinRT的应用程序是不能修改系统设置的,不能未经用户许可访问他们的文件(但是一个不使用WinRT的desktop程序却可以)。
然后,有些功能更适合于由框架提供,例如UI framework,所以WinRT也不会实现任何这一类功能。
所以,运行时的作用是限制应用程序不准使用一般不该用的内核功能,以及为框架提供支持。总的来说,运行时提供的有以下功能:
- 组件加载:每个WinRT组件都需要由WinRT这个运行时加载才能运行。
- 基本数据类型:为了让多种语言共享同样的接口,WinRT必须提供基本的数据类型。
- 内存管理:WinRT使用reference count帮助应用程序管理内存。请注意操作系统内核为应用程序分配内存,而运行时则是帮助管理内存的。
- 安全机制:如上所述,WinRT的安全机制主要用来控制哪些操作系统提供的API可以被应用程序使用,哪些不能。
- 各种building blocks:为了方便框架的开发,WinRT提供了很多building blocks,通常应用程序不会(甚至有时候是不能)直接使用它们,它们的存在主要是为了支持框架的开发,或者让不使用框架而直接使用运行时的开发人员使用。在某些高级场合中,你需要绕开框架,直接使用运行时提供的组件。
接下来我们详细说明每一项功能。
组件的加载
在WinRT中,每个程序都被视为一个组件。有些组件是运行时本身提供的,例如FileOpenPicker提供了一个公用的接口让你的程序提供让用户选择文件的功能。若是不使用这个组件,你就必须申请权限才能访问某些用户的文件。运行时提供的组件通常可以在任何程序中使用。也有些组件是框架提供的,例如XAML框架提供了一个叫Button的组件,让一个XAML应用程序可以不需要自己写代码就提供一个能让用户点击的控件。XAML中的button不能用于HTML或者其它框架。类似的,HTML也提供了自己的button,不过这和XAML的button是两回事。最后,开发人员也可以开发自己的组件。事实上每个Metro应用程序就是一个组件。
一个组件单纯放在那里并没有用,它必须被加载。你可以能会觉得奇怪,XAML中的button明明是用C++写的,会什么在一个C#程序中也能使用它?这就要涉及到WinRT如何加载这个组件了。
每个WinRT组件都会在注册表中有注册,视组件类型的不同,它们的位置可能有所不同,但是加载过程都是一致的。首先,每个组件都有一个被称作ActivatableClassId的ID,它用来唯一代表一个组件。我们拿XAML中的button为例:它的位置在HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WindowsRuntime\ActivatableClassId\Windows.UI.Xaml.Controls.Button,其中Windows.UI.Xaml.Controls.Button就是它的ActivatableClassId,这和namespace + class name是一致的。
WinRT加载一个组件时,首先会到注册表中寻找这个组件的ActivatableClassId。如果你用Regedit观测刚才说的XAML button的ActivatableClassId,就会发现它有一个叫DllPath的属性,其值为C:\Windows\System32\Windows.UI.Xaml.dll。那么下一步,WinRT就会从这个路劲加载dll,并且调用相应的代码了。
当然,事实上具体的组件加载过程要比我们说的来的复杂,不过为了避免在一篇文章中说太多的概念让人摸不着头脑,我们就不再深入说明了。有兴趣的人可以看看BUILD上的这部教学电影,这部影片深入分析了一个WinRT组件的加载过程。
最后我们指出一点,在刚才的注册表中大家看到叫ActivationType的属性,它的值若是为0,例如我们的button的场景,就说明这个组件不能通过application launch contract,search contract,或者任何其它contract的方式加载,而只能在另外一个程序的代码中显示加载。若是ActivationType的值为1,就说明这个组件可以通过某个contract加载。例如,我们自己开发一个application(而不是component),其实也是一个组件,只是它实现了launch contract而已,所以可以通过点击开始页面上的tile加载。
WinRT组件的构成
接下来,我们来看看一个WinRT组件是怎么构成的。在Windows 8: 混用CSharp和C++创建DirectX程序中,我们已经看到了如何使用C++/CX语法创建一个自定义WinRT组件。但是,我们也指出了,CX语法为我们做了很多很多工作。假如你不使用C++/CX语法,而是使用标准C++开发一个自定义WinRT组件,你需要做什么呢?
简单说来就是,你需要实现IInspectable接口。复杂说来呢,你需要实现IInspectable接口中的GetIids,GetRuntimeClassName,以及GetTrustLevel方法,还有IInspectable继承的IUnknown接口中的QueryInterface,AddRef,以及Release方法。如果你用过古老的COM,就会知道IUnknown其实是一个COM接口,扮演着类似于.NET中的object的位置,任何一个COM组件都必须实现它。同样,每个WinRT组件也必须实现IUnknown以及IInspectable。它们也可以实现其它接口,你每实现一个接口,就需要在GetIids中返回这个接口。
不过,在一个XAML程序中,通常所有这些都是被隐藏掉的。如果你使用C#,你根本不可能取得IInspectable。就算你用C++/CX语法,当你通过ref new创建一个组件的实例时,也是不能使用IInspectable上的方法,例如GetRuntimeClassName的。不过请相信,事实上每个WinRT组件都实现了IInspectable。如果你还有所怀疑呢,可以试试看下面的代码:
Button^ btn = ref new Button(); IInspectable* inspectable = reinterpret_cast<IInspectable*>(btn); HSTRING className; HRESULT hr = inspectable->GetRuntimeClassName(&className); if (FAILED(hr)) { throw ref new Exception(hr); } hr = WindowsDeleteString(className); if (FAILED(hr)) { throw ref new Exception(hr); }
当你调试时会发现,className的值正是Windows.UI.Xaml.Controls.Button,这说明Button这个类确实实现了IInspectable接口中的GetRuntimeClassName方法,只是C++/CX语法把它隐藏起来了而已!
还请注意,当你不使用CX语法时,一定要检查每个方法返回的HRESULT,以避免错误堆积地越来越多最终造成冰冻三尺非一日之寒的后果……
可以看到,C++/CX以及.NET语言确实为我们做了很多事。没有它们,不仅仅创建一个组件非常麻烦,要自行实现至少两个接口6个方法,而且使用这个组件也很麻烦,要调用RoActivateInstance等等一大堆的函数!请不要责怪CX不是标准C++,而是一套方言……
应用程序的入口
说到这里,我们不妨也顺便介绍一下应用程序的入口,毕竟一个WinRT应用程序事实上也是一个WinRT组件。
刚才说过,应用程序都实现了Launch contract。你可以自HKEY_CURRENT_USER\Software\Classes\Extensions\ContractId找到所有Windows 8支持的contract。例如,这里面就有Windows.Launch,代表了Launch contract。在这个键下(HKEY_CURRENT_USER\Software\Classes\Extensions\ContractId\Windows.Launch\PackageId)列出了所有支持Launch contract的组件列表,这当然也包括你安装的全部Metro程序,可以通过点击开始页面上的tile启动(launch)。当你的应用程序启动时,WinRT就会根据注册表寻找对应的值。注意到每个组件都有一个Package ID。有了这个ID呢,WinRT就可以从HKEY_CURRENT_USER\Software\Classes\ActivatableClasses\Package找到对应的package啦。
在HKEY_CURRENT_USER\Software\Classes\ActivatableClasses\Package这个路劲下你会发现每个应用程序都有一个专门的键,其下的ActivatableClassId中至少会有一个叫App的键(可能还有其它,因为一个程序本身也可以实现更多的组件)。如果你仔细观察App,就会发现它也有一个ActivationType属性,不过这一次它的值是1,说明这个组件可以通过contract的方式启动(或者借用COM的说法,叫做server activation)。请注意所谓的server纯粹是借用了COM的说法,事实上根本不存在什么server,我们的程序是在客户端运行的!不过目前我们就喊它server吧……在Server这个节点下,你可以看到另一个节点是你的程序的package name(默认Visual Studio使用一个GUID做package name,但是你可以自己改成一个更友好的字符串),这个节点有一个属性叫做ExePath,这里就指出了你的程序的exe所在的位置。若是直接使用WinRT或者XAML开发程序,这个exe就是你自己的程序的exe,若是使用HTML,这个exe就是Windows 8自带的wwahost.exe。若是上面的文字让你觉得有点混乱,请打开Regedit,对照着文字的说明自行观察这些注册表信息。
在这之后,就进入你的程序了。为了更好地观察一个WinRT程序的入口,你可以用Visual C++的Direct3D App模版否建一个项目,否则XAML框架会帮你隐藏掉很多东西。在这个模版创建的程序中,你会找到熟悉的main函数:
[Platform::MTAThread] int main(Platform::Array<Platform::String^>^) { auto direct3DApplicationSource = ref new Direct3DApplicationSource(); CoreApplication::Run(direct3DApplicationSource); return 0; }
我们不再重复CX语法为我们省下的事情了,反正大家心里清楚,如果不用CX,这段代码会变得很复杂很复杂……
注意到main调用了Windows::ApplicationModel::Core::CoreApplication::Run,并且传入一个自己写的类名为Direct3DApplicationSource。这个类实现了Windows::ApplicationModel::Core::IFrameworkViewSource接口,这个接口中的CreateView方法的实现里又使用了一个实现了Windows::ApplicationModel::Core::IFrameworkView接口的类。
大家可能已经有点头晕了,因此我们也不再深入展开这方面的说明了。在这边只是指出一点,事实上大家使用XAML以及HTML这样的框架时,框架会为我们做这些事。事实上到最后无论是XAML还是HTML都逃不出使用CoreApplication,实现IFrameworkViewSource和IFrameworkView的命运!事实上无论是XAML还是HTML都是使用DirectX进行渲染的!事实上wwahost这个程序本身就是一个C++程序,只不过它的作用是host HTML而已!
内存管理
由于时间关系,我们就不说明WinRT的数据类型了,大家可以参考这里。
下一个需要说明的是WinRT的内存管理。事实上,既然每个WinRT组件都实现了IUnknown,就说明它们都必须实现AddRef和Release,也就是reference count。所谓的reference count,指的是用一个计数器来记录某个对象是否还活着。当一个对象刚刚创建的时候,它的reference count是1,每次你将它赋值给另外一个变量,reference count就会+1,每次某个变量的生命周期结束,reference count就会-1,当reference count的值变成0时,这个对象就会被销毁,因为已经没有任何变量在引用他了。
这和.NET的垃圾回收稍微有点像,不过AddRef和Release都需要你自己去调用。如果你忘记在给变量赋值时调用AddRef,或者忘记在变量生命周期结束时调用Release,就会出问题了……这就是为什么我们推荐大家使用smart pointer的原因,WRL中的ComPtr会自动调用AddRef和Release,我们不必操心。此外,若是使用CX语法,或者在.NET/JavaScript中调用组件,你也不用为这件事操心。CX自动封装了smart pointer,而.NET和JavaScript的垃圾回收会自动处理对象生命周期。
不过,我们还是来举一个例子吧:
void A() { MyComponent^ component = ref new MyComponent(); B(component); } void B(MyComponent^ component) { MyComponent^ anotherComponent = component; }
上面的代码中,MyComponent的一个实例在A这个方法中被创建。这时候这个对象的reference count为1。当B被调用时,这个对象作为参数被传入,于是reference count就变成了2。在B中,我们又创建了一个变量,并且将component赋值给它,于是reference count就变成3了。注意所有三个变量指向的都是同一个对象,我们使用的是by reference赋值。当B结束后,无论是参数还是临时变量都会被销毁掉,于是reference count的值会减掉2,又变成1了。当A的调用也完成后,A中的临时变量也会被销毁,于是reference count就变成了0。在这一刻,这个对象已经没有存在的价值了,它应该被从这个世界上销抹掉了,而这也正是WinRT会为我们做的事情。
由于我们使用了CX语法,我们根本就不需要手工调用AddRef和Release,CX都帮我们自动做好了!
使用一般的对象通常大家不需要太多地考虑内存管理,但是有些对象可能涉及到操作系统的资源,例如和文件相关的对象就可能会锁定某个文件,让其它进程无法使用,因此你一定要小心,像这样的对象不要让它们活得太久。你也可以显示调用Dispose方法(在JavaScript中是close)来提前将它销毁,但要小心在dispose之后可千万不要再去使用这个对象了啊,否则就会出问题了……
Building blocks
本文不会介绍WinRT的安全机制,大家可以通过搜索网络获得相关知识。
最后我们简单说一下WinRT中的building block组件,它们为框架提供了强大的支持。这些组件中最重要的有如下这些:
- DirectX:无论是XAML还是HTML,真的到了绘制图形时都必须使用DirectX,只是如果你使用了框架,就不能自己直接调用DirectX了,而必须使用框架提供的接口(当然,XAML中还是可以host DirectX内容的,正如Windows 8: 混用CSharp和C++创建DirectX程序所说的那样)。上一篇wiki对DirectX有更多的介绍,我们这边不再重复……
- Windows Imaging Component(WIC):这个组件负责位图的处理,当你在XAML中使用Image控件,或者在HTML中使用img控件,事实上在后台框架都写了很多代码,使用WIC来为图画解码,并且显示。这个组件没有被框架隐藏掉,你可以直接在Metro程序中使用它。当然,通常你不会用它来解码,更多的是用来编码或者操作像素,也就是做一些框架没有提供简便方法的工作(注意HTML的canvas也能够操作像素)。
- Meida Foundation:这个组件负责电影(或者更专业一点的说法是音频和视频)的处理,当你在XAML中使用MediaElement控件,或者在HTML中使用video/audio控件,事实上在后台框架都写了很多很多代码(比起使用WIC要多得多),使用Media Foundation来为电影解码,并且显示。你不能直接在XAML和HTML中使用Media Foundation,而必须自己写一个C++的WinRT组件。通常你只有在需要对电影进行编码时才会使用它,否则用MeidaElement和video/audio控件要简单无数倍……这也正应证了框架可以大幅简化我们的工作,但是框架没有提供的功能还是要由我们自己借助更底层的运行时一级实现这一观点。有关Media Foundation的使用,请参考我们之前的一篇wiki:Windows Phone上的声音录制与编码(四):使用Media Foundation编码音频。
- 文件系统API:WinRT封装了一部分Windows Kernel提供的文件系统API,它规定你的程序必须通过这些API来访问文件,而不允许直接使用Windows API。这些API做了很多安全检查,例如,未经用户许可不许访问文件系统(除了每个程序自己隔离的一个被称作local storage的文件夹),就算用户允许也只能访问用户自己的文件(例如Documents Library),而不能访问诸如Program Files和Windows这样的系统目录。这些限制都是很有必要的,否则任何一个程序都可能会把系统搞得乱七八糟……
其它还有很多很多组件,大家可以自行参考MSDN上的文档。
可以看到,其实很多组件都既可以用于desktop程序,又可以用于WinRT环境。WinRT选择了一些适用于现代程序开发的组件,例如DirectX和Media Foundation,而抛弃了一些古老的不适用于现代程序开发的组件,例如GDI和Direct Show。有些人可能会抱怨为什么几年前的技术不能再使用了,但是请理解,这是为了世界向前发展。
总结
本文简单介绍了WinRT这个运行时,目前我们还不清楚Windows Phone 8是否会支持WinRT,但是在Windows 8上,WinRT是一个核心的组成部分,它位于Windows kernel和XAML/HTML框架之间,Metro style applications是脱离不了它的。
通常我们开发程序时都会使用框架,而不会直接去访问运行时,正好向使用WPF/Silverlight时我们很少会去关心CLR的一些底层的概念。但是也有些高级场合中,我们还是必须绕开框架,直接使用运行时才行。所以对运行时有一定的了解还是非常必要的。