Windows 运行时

使用 Windows 运行时改变应用程序开发的面貌

Jason Olson

 

通过您选择的编程语言开发 Windows 应用程序和使用新的 Windows 功能并不总是简单明了。为使客户在 Windows 8 上具有最佳体验,以及使开发人员能够在 Windows 上创建最佳的应用程序,需要投入资金来改善 Windows 上的端到端开发体验。而这些投资的核心就是 Windows 运行时 (WinRT)。

Windows 运行时是重塑针对 Windows 的开发人员体验的一部分。它是一种新型的 Windows API 外围应用,用于在 Windows 8 上创建新的 Windows 应用商店应用程序。该 Windows 运行时是从头开始全新设计的,以便同等地支持若干主要的编程语言(C#/Visual Basic、JavaScript 和 C++),允许开发人员利用其现有的技能和资产,提供一致且周密设计的 API 外围应用,以及深入集成到开发人员工具链中。

Windows 运行时支持语言选择

现今有许多应用程序开发人员。他们涵盖使用 JavaScript/HTML/CSS 创建 Web 应用程序的开发人员,使用 C#/Visual Basic 创建具有 Microsoft .NET Framework 的应用程序的开发人员,以及使用 C++ 创建应用程序的本机开发人员。通过 Windows 运行时,能够以上述任何编程语言编写 Windows 应用商店应用程序,使上述所有开发人员都可以开发出很棒的 Windows 应用程序,如图 1 中所示。

Windows 运行时支持使用多种语言编写新的 Windows 应用商店应用程序
图 1 Windows 运行时支持使用多种语言编写新的 Windows 应用商店应用程序

开发人员和消费者都可以从中受益。而对于通过开发 Windows 应用商店应用程序来赚钱的开发人员来说,这意味着巨大的商机。并且通过支持所有主要的 Microsoft 语言,众多开发人员都可以创建消费者想要购买和使用的很棒的 Windows 应用程序。

作为一名开发人员,您对于所选的特定编程语言已具备一定的技能和经验。您在开发应用程序时还具有许多现有资产(例如现有的代码、建筑物基础结构等)。您应该不需要学习全新的编程语言和工具集,即可轻松地在 Windows 8 上开发 Windows 应用商店应用程序,因为 Microsoft 已经提供了丰富的编程语言和开发工具。

Windows 运行时简单自然且为人所熟悉的

在设计以前的 Windows 开发体验时,C 是主流的编程语言,基于异常的代码尚不普遍,并且要绑定到 Windows 操作系统可接受的单个语言。而在现在,流行的开发人员环境已经大不相同了。命名空间、集合、事件和异步等编程功能在当今不仅普遍,而且理所当然地为开发人员所采用。

让我们以网络摄像头为例,这是在 Windows 上目前使用的一种 相当普通的设备。下面是以前在 Windows 中声明网络摄像头 API 的代码部分:

HWND VFWAPI capCreateCaptureWindow(
  LPCTSTR lpszWindowName,
  DWORD dwStyle,
  int x,
  int y,
  nit nWidth,
  int nHeight,
  HWND hWnd,
  int nID
);

以前的 Windows API 主要在 Windows SDK 随附的本机头文件 (*.h) 中声明。 该 SDK 还包含了使用 API 的代码链接到的 .lib 文件。 Windows API 在 C/C++ 中作为本机 flat-C 功能或本机 COM 组件实现,并且打包到作为 Windows 的一部分随附的 .dll 文件中。 该编程模型在设计上主要考虑的是本机性能。

没有命名空间、流行的集合或真正的异常。 甚至对于典型的本机开发人员来说,开发体验也不理想,因为存在与 IntelliSense、代码浏览、命名空间缺乏有关的等等问题。

对于尝试使用 Windows API 的 .NET 开发人员来说,这一纯本机的平台也导致了实实在在的可用性问题,如图 2 中所示。

图 2 以前的纯本机的平台导致了实实在在的可用性问题

[DllImport("avicap32.dll", EntryPoint = "capCreateCaptureWindow")]
static extern int capCreateCaptureWindow(
  string lpszWindowName, int dwStyle,
  int X, int Y, int nWidth, int nHeight,
  System.IntPtr hwndParent, int nID);
[DllImport("avicap32.dll")]
static extern bool capGetDriverDescription(
  int wDriverIndex,
  [MarshalAs(UnmanagedType.LPTStr)] ref string lpszName,
  int cbName,
  [MarshalAs(UnmanagedType.LPTStr)] ref string lpszVer,
  int cbVer);

需要某个人编写很多代码来手动弥补您的代码和传统的 Windows API 之间的差距。 如果没人编写,就需要您自己来努力编写这个复杂的代码。 针对开发人员的许多资源都专门致力于弥补这个差距,包括 pinvoke. net、Vista Bridge 示例库 (code.msdn.microsoft.com/VistaBridge) 以及用于 Microsoft .NET Framework 的 Windows API 代码包 (code.msdn.microsoft.com/WindowsAPICodePack)。

现在,通过 Windows 8,您可以使用 Windows 随附的现成的 Windows API,采用与当前在您的应用程序中使用其他 API 完全相同的方式(例如在针对 C#/Visual Basic 开发人员的 .NET Framework 中)。 无需撰写代码来弥补您的代码和传统的 Windows API 之间的差距。 例如,在当今使用该网络摄像头要简单得多:

using Windows.Media.Capture;
MediaCapture captureMgr = new MediaCapture();
await captureMgr.InitializeAsync();
// Start capture preview; capturePreview
// is a CaptureElement defined in XAML
capturePreview.Source = captureMgr;
await captureMgr.StartPreviewAsync();

那么,为什么说使用 Windows 运行时对开发人员而言是简单自然且熟悉的呢?因为许多应用程序都从 Internet 检索数据,所以,让我们在一个常见的网络方案中看看这个问题: 检索 RSS 源。下面是用来以异步方式检索此类源的 C# 代码:

var feedUri = 
  new Uri("http://www.devhawk.com/rss.xml");
var client = new Windows.Web.Syndication.SyndicationClient();
var feed = await client.RetrieveFeedAsync(feedUri);
var title = feed.Title.Text;

下面是起同样作用的 Visual Basic 代码:

Dim feedUri = New Uri("http://www.devhawk.com/rss.xml");
Dim client = New Windows.Web.Syndication.SyndicationClient();
Dim feed = Await client.RetrieveFeedAsync(feedUri);
Dim title = feed.Title.Text;

除了根命名空间之外 (Windows),在此代码与您已使用 .NET Framework 编写的代码之间没有多大差异。 有命名空间, 有类和构造函数, 也有方法和属性。 标识符采用 Pascal 大小写,因为它们处于 .NET Framework 中,并且我们还将 await 关键字用于异步编程 — 包含开发人员在当今使用 C# 和 Visual Basic 编写代码时所期待的一切。

与 C#/Visual Basic 开发体验相似,C++ 使用命名空间、类、构造函数、方法和属性。 您还可以使用内置的 C++ 类型,例如字符串(这与以前的 Win32 数据类型相反)。 “::”和“->”之类的标准运算符以及其他操作符也按预期方式使用。 对于异步编程,我们提供了类似并发运行时的体验。 下面是用来以异步方式检索 RSS 源的 C++ 代码:

auto feedUri = ref new 
  Windows::Foundation::Uri("http://www.devhawk.com/rss.xml");
auto client = ref new Windows::Web::Syndication::SyndicationClient();
task<SyndicationFeed^> 
  retrieveTask(client->RetrieveFeedAsync(feedUri));
retrieveTask.then([this] (SyndicationFeed^ feed) {
  this->title = feed->Title->Text;
});

最后还有 JavaScript,它在起相同作用的代码中表现出了一些有趣的差异:

var title;
var feedUri = 
  new Windows.Foundation.Uri("http://www.devhawk.com/rss.xml");
var client = new Windows.Web.Syndication.SyndicationClient();
client.retrieveFeedAsync(feedUri).done(function (feed) {
  title = feed.title.text;
});

在 JavaScript 中您将注意到,所有方法和属性都采用驼峰式大小写,因为这对于 JavaScript 开发人员而言是自然且熟悉的。 在编写异步代码时,我们还将承诺框架用于匿名功能。 我们在此处还将继续利用该语言的内置类型。

开发人员的体验也是熟悉的,具有开发人员预期的工具功能(例如 IntelliSense)以及标准工具(例如 ILDASM 和 .NET Reflector)(参见图 3)。

Visual Studio IntelliSense for WinRT API
图 3 Visual Studio IntelliSense for WinRT API

Windows 运行时通过扩展和合并来自 .NET 和 COM 的基础概念以向 WinRT API 添加新型语义(例如类、构造函数、静态和事件),实现这一自然和熟悉的体验。 这些语义可在不同语言中以不同方式公开,这都依赖于在该语言中对开发人员的自然和熟悉程度。

这是可能的,因为所有 WinRT API 都包括编程语言用来知道该 API 是如何通过 Windows 运行时来公开的元数据。 WinRT 元数据文件使用 .NET 元数据格式的更新的版本。 每个开发人员都可以使用这些元数据,因为它们安装在每一台 Windows 8 机器上。

通过改变 Windows API 开发外围应用的面貌,不会感觉像在学习或使用新平台。 它不会让您感觉“陌生”,它的外观和感觉就像您已在编写的代码。 在您创建新的 Windows 应用商店应用程序时,您在利用现有的技能和资产。

Windows 运行时在功能上是丰富的

Windows 运行时所具有的 API 要少于 Win32,因此易于学习,但它在功能上是丰富的。 Windows 运行时的功能十分丰富,无法在这种篇幅的文章中全面介绍,但您预期应该有的标准 Windows 功能以及新的 Windows 8 功能都列举了,如图 4 中所示。

Windows 运行时包括标准 Windows 功能和新的 Windows 8 功能
图 4 Windows 运行时包括标准 Windows 功能和新的 Windows 8 功能

并且通过使用一组统一的 API 设计指导原则,在 Windows 运行时的设计上保持一致。 在您使用单个 API 学习一次这些概念和设计后,您就可以在整个 Windows API 外围应用中使用不同 API 时应用所学到的知识。

在 Windows 开发人员中心 (dev.windows.com),您将会找到许多涵盖上述大多数领域的示例。 以下是一些示例。

合约 您可以让用户在选择“搜索”超级按钮时在您的应用程序内进行搜索,并在“搜索”窗格中显示建议。 您的应用程序还可以通过“共享”超级按钮与其他应用程序共享内容。 示例如下:

社交 使用来自 Windows.ApplicationModel.Contacts 命名空间的类,您可以启动联系人选择器并获取联系人。 对于需要向其他应用程序提供联系人的应用程序,您可以使用 ContactPickerUI 类创建联系人的集合:

媒体 从简单的媒体播放和媒体捕获,到转换媒体代码和通过“播放至”合约将媒体播放到外部设备,您都将会在 Windows.Media 命名空间中找到 Windows API:

安全性 您可以检索凭据,然后将凭据传递到可能要求凭据的 API(例如,为了支持单一登录)。 您还可以使用基于密码的强加密在本地计算机上安全地存储私人信息,以便保护信用卡帐户、银行帐户和软件产品。 您甚至可以使用 Windows.Security.Cryptography 命名空间执行许多不同形式的加密:

网络/Web 无论您是使用 StreamSocket 类通过 TCP 连接到网络或使用 DatagramSocket 通过用户数据报协议 (UDP) 连接到网络,还是仅是查询网络信息状态,以便确定是可以将一些应用程序数据保存到 Web 服务还是需要在本地保存它,您都可以在 Windows.Networking 命名空间中找到需要的内容:

当然,Windows 运行时的所有功能并不仅限于此。 作为 Windows 开发人员,还有多得多的功能可供您使用,并且您可以在 Windows 开发人员中心(网址为 bit.ly/yyJI2J)更深入地研究。

Windows 运行时快速、流畅(本机和异步)

Windows 运行时是作为具有二进制合约的本机对象在最低级别实现的,但它通过元数据、公共类型系统和共享的习惯用语向全世界公开。 WinRT API 继续提供以前的 Windows API 所提供的快速性能,同时还提供新型的开发体验,如前所述。

使应用程序保持流畅和响应迅速是一种不同的挑战。 我们人类会自然而然地执行多任务,这直接影响我们期望应用程序对我们作出响应的方式。 我们期望应用程序迅速响应所有交互操作。 在我们使用喜爱的新闻阅读应用程序时,我们想要添加新闻源、阅读新闻文章、保存新闻文章等。 并且甚至是在应用程序正在从 Internet 检索最新文章时,我们也应该能够执行所有这些事项。

这在我们正使用触摸与应用程序交互时变得尤其重要。 我们会注意到应用程序不“依附”我们的手指。 甚至是很小的性能问题也可能会使我们的体验的满意程度下降,打断快速、流畅的感觉。

许多新型应用程序都连接到社交网站、在云中存储数据、在硬盘上处理文件、与其他小工具和设备进行通信等。 其中一些源具有无法预测的延迟时间,这使得创建快速、流畅的应用程序颇具挑战性。 如果未能正确构建,应用程序将会用更多的时间来等待外部环境,而用更少的时间来响应用户需求。

Windows 运行时的核心原则就是满足这个互联世界。 您作为开发人员,在创建与世界联通的应用程序时应落入“成功的陷阱”(参见 bit.ly/NjYMXM)。 为实现此目标,可能会受到 I/O 约束的或长时间运行的 API 在 Windows 运行时中是异步的。 如果以同步方式编写,这些 API 是最有可能导致性能明显下降的因素(例如,可能会用超过 50 ms 的时间来执行)。 通过这一针对 API 的异步方法,您可以编写内在就快速而流畅的代码,并且在开发 Windows 应用商店应用程序时提升应用程序快速响应的重要性。

为了更好地理解 Windows 运行时的异步本质,您可以阅读 bit.ly/GBLQLr 上的博客文章“在 Windows 运行时中通过异步使应用程序快速、流畅”。

Windows 运行时支持不同种类的应用程序

Windows 运行时的一个强大功能就是,您不会受到创建应用程序时所选的编程语言的限制。 您所受到的限制仅限于为该编程语言环境所提供的库和代码。 您可以使用最适合于您的工作的语言、库或组件。 它可以是开放源库。

如果您在使用 JavaScript/HTML/CSS 编写游戏并且想要非常快的物理库怎么办? 您可以使用 C++ 中的 Box2D。 是否使用 JavaScript 编写 Windows 应用商店应用程序并且想要处理一些压缩的文件? 您可以通过简单直接的方式使用 .NET Framework 中的压缩功能。 是否在 C# 中创建新的 Windows 应用商店音乐应用程序并且想要进行一些较低级别的音频编程? 没有问题,只需使用 C++ 中的 Xaudio 或 WASAPI 就可以了。 图 5 中阐释了这些功能。

Windows 运行时支持不同种类的应用程序
图 5 Windows 运行时支持不同种类的应用程序

让我们看一个软件合成器应用程序示例,它是在 C# 中使用 XAML 编写的。 我希望向这个应用程序中添加一些很酷的筛选器支持,所以我要利用 Xaudio 直接对音频缓冲区进行控制。 我使用 C++/CX 生成将向 C# 项目公开的 API。

首先,我围绕想要公开的 Xaudio 功能创建一个简单的 WinRT 包装(参见图 6)。

图 6 XAudioWrapper.h

#pragma once
#include "mmreg.h"
#include <vector>
#include <memory>
namespace XAudioWrapper
{
  public ref class XAudio2SoundPlayer sealed
  {
    public:
      XAudio2SoundPlayer(uint32 sampleRate);
      virtual ~XAudio2SoundPlayer();
      void Initialize();
      bool   PlaySound(size_t index);
      bool   StopSound(size_t index);
      bool   IsSoundPlaying(size_t index);
      size_t GetSoundCount();
      void Suspend();
      void Resume();
    private:
      interface IXAudio2*                m_audioEngine;
      interface IXAudio2MasteringVoice*  m_masteringVoice;
      std::vector<std::shared_ptr<ImplData>>  m_soundList;
    };
}

首先请注意,在类声明中使用了“public”、“ref”和“sealed”关键字。 这个例子只是为了说明如何在 C++/CX 中声明公共 WinRT 类。 这将允许您在其他语言(例如 JavaScript、C# 或 Visual Basic)中使用这个类。 值得注意的是,这不是托管的 C++ 或 C++/公共语言基础结构 (CLI)。 这不是向下编译到 Microsoft 中间语言;这完完全全是个本机组件。

您还将注意到,该类的公共功能(方法、属性等)被限制为 C++ 内置类型或 WinRT 类型。 这些类型是允许在 WinRT 组件中跨越语言边界的唯一类型。 但是,您可以使用与在实现您的 WinRT 组件时希望使用的一样多的现有 C++ 库(例如,标准模板库)。

有关在 C++ 中创建 WinRT 组件的详细信息,请阅读 Windows 开发人员中心文章“在 C++ 中创建 Windows 运行时组件”,网址为 bit.ly/TbgWz7

现在,我已定义了用于我的类的基本接口,接下来让我们快速看一些已实现的方法(细节并不重要),如图 7 中所示。

图 7 XAudioWrapper.cpp

XAudio2SoundPlayer::XAudio2SoundPlayer(uint32 sampleRate) :
  m_soundList()
{
  ...
  XAudio2Create(&m_audioEngine, flags);
  // Create the mastering voice
  m_audioEngine->CreateMasteringVoice(
    &m_masteringVoice,
    XAUDIO2_DEFAULT_CHANNELS,
    sampleRate
  );
}
bool XAudio2SoundPlayer::PlaySound(size_t index)
{
  //
  // Setup buffer
  //
  XAUDIO2_BUFFER playBuffer = { 0 };
  std::shared_ptr<ImplData> soundData = m_soundList[index];
  playBuffer.AudioBytes = soundData->playData->Length;
  playBuffer.pAudioData = soundData->playData->Data;
  playBuffer.Flags = XAUDIO2_END_OF_STREAM;
  HRESULT hr = soundData->sourceVoice->Stop();
  if (SUCCEEDED(hr))
  {
    ...
  }
  ...
}

如您在图 7 的代码段中所见,我在使用针对 Windows 商店应用程序的 Windows SDK 中提供的 Xaudio2 COM API 来关联音频引擎。 此外,除了 WinRT 类型之外,我还在使用 C++ 构造和类型来实现所需功能。

在我定义和实现了基本类后,只需添加从我的 C# 项目到 C++ 项目的引用。 因此,从我的 C++ 项目公开的类将可用于我的 C# 项目(参见图 8)。

图 8 MainPage.cs

using XAudioWrapper;
namespace BasicSoundApp
{   
  public sealed partial class MainPage : Page
  {
    XAudio2SoundPlayer _audioPlayer = new XAudio2SoundPlayer(48000);
    public MainPage()
    {
      this.InitializeComponent();
    }
    protected override void OnNavigatedTo(NavigationEventArgs e)
    {
      _audioPlayer.Initialize();
    }
    private void Button_Click_1(object sender, RoutedEventArgs e)
    {
      _audioPlayer.PlaySound(0);
    }
  }}

您可以看到,尽管 XAudioWrapper 是使用本机 C++ 编写的,但它可以就像是常规 .NET 类一样使用。 我引用其命名空间、实例化该组件并且开始调用其公开的不同方法 — 所有这些操作都不要求任何 DllImports 来调用本机代码!

没有任何边界。 作为 JavaScript 开发人员,您不应受限于 JavaScript 库。 作为 C#/Visual Basic 开发人员,您不应受限于 .NET 库。 并且作为 C++ 开发人员,您不应受限于 C/C++ 库。 您是否经常需要创建自己的组件? 也可能不经常。 但该选项可供您选择。

总之,Windows 运行时是在 Windows 8 上创建 Windows 应用商店应用程序的核心。 它提供了可供创建 Windows 应用商店应用程序的强大平台。 Windows 运行时提供一个开发外围应用,它具有一致和周密的设计并且功能丰富。 而且,无论您是 JavaScript 开发人员、C#/Visual Basic 开发人员还是 C++ 开发人员,现在都可以成为为 Windows 8 创建新的 Windows 应用商店应用程序的 Windows 开发人员。

常见问题

Q. Windows 运行时是否可以替代 CLR 或 Microsoft .NET Framework?

**回答:**不可以。 Windows 运行时不替代 .NET Framework 或任何其他框架。 当您在 C# 中构建 Windows 应用商店应用程序时,您的代码仍在使用 CLR 执行。 不仅如此,在您构建 Windows 应用商店应用程序时还会使用 .NET Framework 的子级(以及 Win32 和 COM)。

Q. 因此,在 C++ 中编写 Windows 应用商店应用程序时,我在使用 C++/CLI?

**回答:**不是。 将 Windows 运行时用于 Windows 应用商店应用程序的代码是使用 C++/CX 编写的。 尽管可能第一眼看上去代码很像 C++/CLI,但它是纯粹本机的。 您不会将垃圾收集或其他 C++/CLI 功能引入您的本机应用程序中。

Resources(资源)

有关详细信息,请查阅下列资源:

  • Windows 应用商店应用程序示例: dev.windows.com
  • “在 Windows 运行时中通过异步使应用程序快速、流畅”: bit.ly/GBLQLr
  • “在 C# 和 Visual Basic 中创建 Windows 运行时组件”: bit.ly/OWDe2A
  • “在 C++ 中创建 Windows 运行时组件”: bit.ly/TbgWz7
  • 针对 Windows 应用商店应用程序的 API 引用: bit.ly/zdFRK2

Jason Olson 是 Microsoft 从事 Windows 运行时的高级项目经理。 当他不在开展 Windows 方面的工作时,他可能会在西雅图弹钢琴,并且与他的太太和两个孩子共享美好时光。

衷心感谢以下技术专家对本文的审阅: Noel Cross、Anantha Kancherla、Ines Khelifi、John Lam、Martyn Lovell、Harry Pierson、Mahesh Prakriya 和 Steve Rowe