组件扩展

C++/CX 教程

Thomas Petchel

下载代码示例

准备编写你的第一个 Windows 应用商店应用程序?或者已经使用 HTML/JavaScript、C# 或 Visual Basic 编写过 Windows 应用商店应用程序,但是还对 C++ 比较好奇?

使用 Visual C++ 组件扩展 (C++/CX),可以将 C++ 代码与 Windows 运行时 (WinRT) 提供的丰富控件集和库相结合,从而使现有技能提升到新的高度。如果使用 Direct3D,一定能让你的应用程序在 Windows 应用商店中脱颖而出。

听到 C++/CX,有些人会以为必须学习一种全新的语言,但实际上在绝大多数情况下,只需要处理少量非标准语言元素,如 ^ 修饰符或 ref new 关键字。此外,这些元素也只会在应用程序边界使用,也就是只在需要与 Windows 运行时交互时使用。便携型 ISO C++ 仍将是应用程序的主力。也许最重要的一点是,C++/CX 是 100% 本机代码。虽然它的语法与 C++/公共语言基础结构 (CLI) 类似,但除非你愿意,否则应用程序不会采用 CLR。

无论你是有已经测试过的 C++ 代码,还是只是喜欢 C++ 的灵活性和性能,都请放心,使用 C++/CX 不必学习全新的语言。在本文中,你将了解为什么说 C++/CX 语言扩展可以构建独特的 Windows 应用商店应用程序,以及何时使用 C++/CX 构建 Windows 应用商店应用程序。

为什么选择 C++/CX?

就像每个开发者都有独特的技能和能力一样,每个应用程序都有一些独特的要求。无论是使用 C++、HTML/JavaScript 还是 Microsoft .NET Framework,都可以成功创建 Windows 应用商店应用程序;但是,在以下情况下,您可能会选择 C++:

  • 你喜欢 C++ 并且具备相关技能。
  • 你希望利用已经编写并测试过的代码。
  • 你希望利用 Direct3D 和 C++ AMP 等库充分发挥硬件的潜力。

答案不是非此即彼 — 你还可以混合搭配语言。例如,我在编写 Bing Maps Trip Optimizer 示例 (bit.ly/13hkJhA) 时,使用 HTML 和 JavaScript 定义 UI,使用 C++ 执行后台处理。后台进程主要解决“旅行推销员”问题。我在一个 WinRT C++ 组件中使用并行模式库 (PPL) (bit.ly/155DPtQ) 在所有可用 CPU 上并行运行我的算法以提高整体性能。只使用 JavaScript 很难做到这一点!

C++/CX 是如何工作的?

所有 Windows 应用商店应用程序的核心是 Windows 运行时,而 Windows 运行时的核心则是应用程序二进制接口 (ABI)。WinRT 库通过 Windows 元数据 (.winmd) 文件定义元数据。.winmd 文件介绍可用的公共类型,它的格式类似于 .NET Framework 程序集中使用的格式。在 C++ 组件中,.winmd 文件只包含元数据;可执行代码位于一个单独的文件中。Windows 中包含的 WinRT 组件就是这样。(对于 .NET Framework 语言,.winmd 文件既包含代码又包含元数据,就像 .NET Framework 程序集一样。) 你可以使用 MSIL 反汇编程序 (ILDASM) 或任意 CLR 元数据读取器查看这些元数据。图 1 显示了包含有很多基础 WinRT 类型的 Windows.Foundation.winmd 文件在 ILDASM 中的样子。

Inspecting Windows.Foundation.winmd with ILDASM图 1 使用 ILDASM 查看 Windows.Foundation.winmd

ABI 是使用一个 COM 子集构建的,目的是使 Windows 运行时可以与多种语言交互。要调用 WinRT API,.NET Framework 和 JavaScript 需要针对每种语言环境的投影。例如,基础 WinRT 字符串类型 HSTRING 在 .NET 中表示为 System.String,在 JavaScript 中表示为 String 对象,在 C++/CX 中表示为 Platform::String ref 类。

虽然 C++ 可以直接与 COM 交互,但 C++/CX 的目的是通过以下方法简化这一任务:

  • 自动引用计数。WinRT 对象可以进行引用计数,并且通常采用堆分配(无论用于何种语言)。对象在引用计数达到零时销毁。C++/CX 的优势在于引用计数是自动进行的并且是统一的。^ 语法可实现这两个目的。
  • 异常处理。C++/CX 通过异常而不是错误代码来指示失败。基础 COM HRESULT 值转换为 WinRT 异常类型。
  • 简单的 WinRT API 使用语法,同时仍保持高性能。
  • 简单的 WinRT 类型创建语法。
  • 简单的类型转换语法,可以处理事件和其他任务。

请注意,尽管 C++/CX 借用 C++/CLI 语法,但是它生成的是纯本机代码。你也可以使用 Windows 运行时 C++ 模板库 (WRL) 与 Windows 运行时交互,该模板库将稍后介绍。不过,我认为在使用 C++/CX 后,你就会喜欢上它。利用它,你不必学习 COM 就可以获得本机代码的性能和控制,而且与 Windows 运行时交互的代码又非常简洁,这样你可以专注于核心逻辑,从而实现应用程序的独特性。

C++/CX 是通过 /ZW 编译器选项启用的。使用 Visual Studio 创建 Windows 应用商店项目时会自动设置这一开关。

Tic-Tac-Toe 游戏

我认为学习新语言最好的方法是付诸实践。为演示 C++/CX 中最常用的部分,我编写了一个玩 tic-tac-toe 的 Windows 应用商店应用程序(也许你们那里叫“井字游戏”或“三子一线”)。

对于这一应用程序,我使用了 Visual Studio 空白应用程序 (XAML) 模板。此项目命名为 TicTacToe。此项目使用 XAML 定义应用程序的 UI。对于 XAML,我就不讲太多了。要了解这方面的详细内容,请参阅 2012 年 Windows 8 特刊上 Andy Rich 的文章“C++/CX 和 XAML 简介”(msdn.microsoft.com/magazine/jj651573)。

我还用 Windows 运行时组件模板创建了一个 WinRT 组件来定义应用程序的逻辑。我喜欢代码重用,因此创建了一个单独的组件项目,这样,任何人都可以在任何使用 XAML 和 C#、Visual Basic 或 C++ 编写的 Windows 应用商店应用程序中使用这一核心游戏逻辑。

图 2 是这一应用程序的外观。

The TicTacToe Ap图 2 TicTacToe 应用程序

在我开发 Hilo C++ 项目 (bit.ly/Wy5E92) 时,我喜欢上了“模型-视图-视图模型”(MVVM) 模式。MVVM 是一种体系结构模式,可以帮助你将应用程序的外观(即视图)与其基础数据(即模型)分离。视图模型将视图连接到模型。尽管我在 tic-tac-toe 游戏中没有典型的 MVVM,但是我发现,使用数据绑定将 UI 从应用程序逻辑分离会使应用程序更容易编写,更具可读性,将来更容易维护。要了解我们在 Hilo C++ 项目中是如何使用 MVVM 的更多信息,请参阅 bit.ly/XxigPg

为将应用程序连接到 WinRT 组件,我从 TicTacToe 项目的 Property Pages(属性页)对话框添加了一个对 TicTacToeLibrary 项目的引用。

简单设置该引用,TicTacToe 项目就能访问 TicTacToeLibrary 项目中的全部公共 C++/CX 类型。你不必指定任何 #include 指令或执行任何其他操作。

创建 TicTacToe UI

前面说过,我不详细介绍 XAML,但是,我在垂直布局中设置了一个区域显示得分,一个主要的游戏区域,还有一个开始下一个游戏(在所附代码下载的文件 MainPage.xaml 中可以查看相应 XAML)。在这里我又大量使用了数据绑定。

MainPage 类的定义 (MainPage.h) 如图 3 所示。

图 3 MainPage 类的定义

 

#pragma once #include "MainPage.g.h" namespace TicTacToe {   public ref class MainPage sealed   {   public:     MainPage();     property TicTacToeLibrary::GameProcessor^ Processor     {       TicTacToeLibrary::GameProcessor^ get() { return m_processor; }     }   protected:     virtual void OnNavigatedTo(      Windows::UI::Xaml::Navigation::NavigationEventArgs^ e) override;   private:     TicTacToeLibrary::GameProcessor^ m_processor;   }; }

MainPage.g.h 有哪些内容? .g.h 文件包含编译器生成的 XAML 页面分部类定义。 一般来说,这些分部类定义是为具有 x:Name 特性的所有 XAML 元素定义需要的基类和成员变量。 下面是 MainPage.g.h:

namespace TicTacToe {   partial ref class MainPage :     public ::Windows::UI::Xaml::Controls::Page,     public ::Windows::UI::Xaml::Markup::IComponentConnector   {   public:     void InitializeComponent();     virtual void Connect(int connectionId, ::Platform::Object^ target);   private:     bool _contentLoaded;   }; }

partial 关键字非常重要,因为它使类型声明可以跨文件有效。 在本例中,MainPage.g.h 包含编译器生成的部件,MainPage.h 包含我定义的其他部件。

请注意 MainPage 声明中的 public 和 ref 类关键字。 C++/CX 和 C++ 的一个不同之处是类可访问性的概念。 如果你是 .NET 程序员,对这一点应该很熟悉。 类可访问性表示一个类型或方法在元数据中是否可见,从而是否可从外部组件访问。 C++/CX 类型可以是公有类型或私有类型。 公有表示 MainPage 类可以从模块外部访问(例如,由 Windows 运行时或其他 WinRT 组件访问)。 私有类型只能从模块内访问。 利用私有类型,可以在公有方法中更自由地使用 C++ 类型,这一点是公有类型无法实现的。 在本例中,MainPage 类是公有类型,因此 XAML 可以访问。 稍后将介绍一些私有类型的示例。

ref 类关键字告诉编译器这是一个 WinRT 类型而不是 C++ 类型。 ref 类在堆上分配,其生存期通过引用计数。 因为 ref 类型通过引用计数,所以它们的生存期是确定的。 释放对 ref 类型对象的最后一次引用时,会调用它的析构函数并释放该对象的内存。 而在 .NET 中,生存期不太确定,而且使用垃圾收集释放内存。

实例化 ref 类型时,通常使用 ^(发音为“hat”)修饰符。 ^ 修饰符类似于 C++ 指针 (*),但是它告诉编译器插入代码来自动管理该对象的引用计数并在引用计数达到零时删除该对象。

要创建普通旧数据 (POD) 结构,请使用值类或值结构。 值类型有固定大小并且只由字段组成。 与 ref 类型不同,它们没有属性。 Windows::Foundation::DateTime 和 Windows::Foundation::Rect 就是两个 WinRT 值类型。 实例化值类型时,不使用 ^ 修饰符:

Windows::Foundation::Rect bounds(0, 0, 100, 100);

另请注意,MainPage 声明为密封的。 sealed 关键字类似于 C++11 final 关键字,阻止进一步派生该类型。 MainPage 是密封的,因为任何具有公有构造函数的公有 ref 类型都必须声明为密封的。 这是因为运行时与语言无关,不是所有语言(例如 JavaScript)都理解继承。

现在我们来看一看 MainPage 成员。 m_processor 成员变量(GameProcessor 类是在 WinRT 组件项目中定义的,稍后介绍这个类型)是私有变量,只是因为 MainPage 类是密封的,并且派生类不可能使用它(一般来说,可能强制封装时,数据成员应为私有)。 OnNavigatedTo 方法是受保护的,因为 Windows::UI::Xaml::Controls::Page 类(MainPage 派生自此类)将此方法声明为 protected。 构造函数和 Processor 属性必须由 XAML 访问,因此它们是公有的。

你已熟悉了 public、protected 和 private 访问说明符;它们在 C++/CX 中的含义与在 C++ 中相同。 要了解 internal 和其他 C++/CX 说明符,请参阅 bit.ly/Xqb5Xe。 稍后将介绍一个 internal 示例。

ref 类在其 public 和 protected 部分只有一个可公开访问的类型,即唯一的基元类型、公共引用或公共值类型。 与之相反,C++ 类型可以在方法签名和本地函数变量中将引用类型包含为成员变量。 下面是 Hilo C++ 项目中的一个示例:

std::vector<Windows::Storage::IStorageItem^> m_createdFiles;

Hilo 团队对此私有成员变量使用 std::vector 而不是 Platform::Collections::Vector,因为我们不会在该类外公开此集合。 使用 std::vector 有助于我们尽可能多地使用 C++ 代码,并且使代码意图更明确。

下面看看 MainPage 构造函数:

MainPage::MainPage() : m_processor(ref new TicTacToeLibrary::GameProcessor()) {   InitializeComponent();   DataContext = m_processor; }

我使用 ref new 关键字实例化 GameProcessor 对象。 使用 ref new 而不是 new 构造 WinRT 引用类型对象。 在函数中创建对象时,通过使用 C++ auto 关键字,可以减少对类型名称的指定或 ^ 的使用:

auto processor = ref new TicTacToeLibrary::GameProcessor();

创建 TicTacToe 库

TicTacToe 游戏的库代码混合使用了 C++ 和 C++/CX。 对于此应用程序,我假设有一些以前编写并测试过的现成 C++ 代码。 我直接采用这些代码,只是添加了 C++/CX 代码将内部实现连接到 XAML。 也就是说,我只是使用 C++/CX 将这两者连接起来。 下面看一看这个库的重要部分,关注尚未讨论的所有 C++/CX 功能。

GameProcessor 类充当 UI 的数据上下文(如果熟悉 MVVM,考虑一下视图模型)。 我使用了 BindableAttribute 和 WebHostHiddenAttribute 这两个特性来声明此类(和 .NET 一样,声明特性时可以省略“Attribute”部分):

[Windows::UI::Xaml::Data::Bindable] [Windows::Foundation::Metadata::WebHostHidden] public ref class GameProcessor sealed : public Common::BindableBase

BindableAttribute 生成元数据来告诉 Windows 运行时该类型支持数据绑定。 这可以确保该类型的所有公共属性都对 XAML 组件可见。 从 BindableBase 派生,以实现进行绑定所需的功能。 因为 BindableBase 适用于 XAML 而不是 JavaScript,所以它使用 WebHost­HiddenAttribute (bit.ly/ZsAOV3) 特性。 按照惯例,我还使用此特性标记了 GameProcessor 类,使它对 JavaScript 隐藏。

将 GameProcessor 的属性分为公共和内部两个部分。 公共属性向 XAML 公开;内部属性只对库中的其他类型和函数公开。 我认为这样区分有助于使代码意图更加明显。

一种常见的属性使用模式是将集合绑定到 XAML: 

property Windows::Foundation::Collections::IObservableVector<Cell^>^ Cells {   Windows::Foundation::Collections::IObservableVector<Cell^>^ get()     { return m_cells; } }

此属性定义网格上显示的单元格的模型数据。 当 Cells 的值发生更改时,XAML 会自动更新。 属性类型为 IObservableVector,这是为使 C++/CX 能够与 Windows 运行时完全互操作专门定义的几个类型之一。 Windows 运行时定义与语言无关的集合接口,每种语言都以自己的方式实现这些接口。 在 C++/CX 中,Platform::Collections 命名空间提供 Vector 和 Map 这样的类型为这些集合接口提供具体的实现。 因此,我可以将 Cells 属性声明为 IObservableVector,但是用一个特定于 C++/CX 的 Vector 对象支持该属性:

Platform::Collections::Vector<Cell^>^ m_cells;

那么,什么时候该使用 Platform::String 和 Platform::Collections 集合而不是标准类型和集合呢? 例如,该使用 std::vector 还是 Platform::Collections::Vector 来存储数据? 根据实际经验,如果计划主要使用 Windows 运行时,则使用平台功能,对于内部或计算密集型代码,则使用 std::wstring 和 std::vector 这样的标准类型。 如果需要,也可以在 Vector 和 std::vector 之间轻松转换。 你可以从 std::vector 创建 Vector,也可以使用 to_vector 从 Vector 创建 std::vector:

std::vector<int> more_numbers =   Windows::Foundation::Collections::to_vector(result);

这两种矢量类型之间的封送涉及到复制开销,因此,请考虑代码中适合使用哪个类型。

另一项常见任务是 std::wstring 和 Platform::String 之间的转换。 以下是操作步骤:

// Convert std::wstring to Platform::String.
std::wstring s1(L"Hello"); auto s2 = ref new Platform::String(s1.c_str()); // Convert back from Platform::String to std::wstring.
// String::Data returns a C-style string, so you don’t need // to create a std::wstring if you don’t need it.
std::wstring s3(s2->Data()); // Here's another way to convert back.
std::wstring s4(begin(s2), end(s2));

在 GameProcessor 类实现 (GameProcessor.cpp) 中,需要注意两点。 第一,我只使用标准 C++ 来实现 checkEndOfGame 函数。 我希望在这里演示如何合并已经编写并测试过的 C++ 代码。

第二,异步编程的使用。 当玩家换轮时,我使用 PPL 任务类在后台处理计算机玩家,如图 4 所示。

图 4 使用 PPL 任务类在后台处理计算机玩家

void GameProcessor::SwitchPlayers() {   // Switch player by toggling pointer.
m_currentPlayer = (m_currentPlayer == m_player1) ?
m_player2 : m_player1;   // If the current player is computer-controlled, call the ThinkAsync   // method in the background, and then process the computer's move.
if (m_currentPlayer->Player == TicTacToeLibrary::PlayerType::Computer)   {     m_currentThinkOp =       m_currentPlayer->ThinkAsync(ref new Vector<wchar_t>(m_gameBoard));     m_currentThinkOp->Progress =       ref new AsyncOperationProgressHandler<uint32, double>([this](       IAsyncOperationWithProgress<uint32, double>^ asyncInfo, double value)       {         (void) asyncInfo; // Unused parameter         // Update progress bar.
m_backgroundProgress = value;         OnPropertyChanged("BackgroundProgress");       });       // Create a task that wraps the async operation.
After the task       // completes, select the cell that the computer chose.
create_task(m_currentThinkOp).then([this](task<uint32> previousTask)       {         m_currentThinkOp = nullptr;         // I use a task-based continuation here to ensure this continuation         // runs to guarantee the UI is updated.
You should consider putting         // a try/catch block around calls to task::get to handle any errors.
uint32 move = previousTask.get();         // Choose the cell.
m_cells->GetAt(move)->Select(nullptr);         // Reset the progress bar.
m_backgroundProgress = 0.0;         OnPropertyChanged("BackgroundProgress");       }, task_continuation_context::use_current());   } }

如果你是 .NET 程序员,可以将任务及其 then 方法视为 C# 中的 async 和 await 的 C++ 版本。 任务可从任意 C++ 程序获得,不过你将在整个 C++/CX 代码中使用它们来保持 Windows 应用商店应用程序快速流畅。 有关 Windows 应用商店应用程序中的异步编程,请阅读 Artur Laksberg 在 2012 年 2 月的文章“在 C++ 中使用 PPL 进行异步编程”(msdn.microsoft.com/magazine/hh781020) 和 MSDN 库文章 msdn.microsoft.com/library/hh750082

Cell 类在游戏板上建模一个单元格。 此类还演示了两个新事物:事件和弱引用。

TicTacToe 游戏区域的网格由 Windows::UI::Xaml::Controls::Button 控件组成。 一个 Button 控件引发一个 Click 事件,不过你也可以通过定义一个 ICommand 对象定义命令规则来响应用户输入。 我使用 ICommand 接口而不是 Click 事件,这样 Cell 对象可以直接响应。 在定义单元格的按钮的 XAML 中,Command 属性绑定到 Cell::SelectCommand 属性:

<Button Width="133" Height="133" Command="{Binding SelectCommand}"   Content="{Binding Text}" Foreground="{Binding ForegroundBrush}"   BorderThickness="2" BorderBrush="White" FontSize="72"/>

我使用了 Hilo DelegateCommand 类来实现 ICommand 接口。 DelegateCommand 承载命令发出时要调用的函数和一个确定命令是否可以发出的可选函数。 我是这样为每个单元格设置命令的:

m_selectCommand = ref new DelegateCommand(   ref new ExecuteDelegate(this, &Cell::Select), nullptr);

进行 XAML 编程时,通常会使用预定义事件,不过也可以定义自己的事件。 我创建了一个在选定 Cell 对象时引发的事件。 GameProcessor 类通过检查游戏是否结束来处理此事件,并根据需要切换当前玩家。

要创建事件,必须先创建委托类型。 可以将委托类型当作函数指针或函数对象:

delegate void CellSelectedHandler(Cell^ sender);

然后,我为每个 Cell 对象创建一个事件:

event CellSelectedHandler^ CellSelected;

GameProcessor 类订阅每个单元格的事件,如下所示:

for (auto cell : m_cells) {   cell->CellSelected += ref new CellSelectedHandler(     this, &GameProcessor::CellSelected); }

从一个 ^ 和一个指向成员函数的指针 (PMF) 构造的委托只承载对该 ^ 对象的弱引用,因此,此构造不会导致循环引用。

Cell 对象选定时引发事件,如下所示:

void Cell::Select(Platform::Object^ parameter) {   (void)parameter;   auto gameProcessor = m_gameProcessor.Resolve<GameProcessor>();   if (m_mark == L'\0' && gameProcessor != nullptr &&     !gameProcessor->IsThinking && !gameProcessor->CanCreateNewGame)   {     m_mark = gameProcessor->CurrentPlayer->Symbol;     OnPropertyChanged("Text");     CellSelected(this);   } }

上面代码中的 Resolve 调用的目的是什么? GameProcessor 类承载一个 Cell 对象集合,但是我希望每个 Cell 对象都能够访问其父 GameProcessor。 如果 Cell 承载对其父级(即 GameProcessor^)的强引用 — 就会创建循环引用。 循环引用可以导致对象永不释放,因为相互关联导致两个对象始终有至少一个引用。 为避免这一问题,我创建一个 Platform::WeakReference 成员变量,从 Cell 构造函数中对其进行设置(仔细考虑生存期管理以及对象从属关系!):

Platform::WeakReference m_gameProcessor;

调用 WeakReference::Resolve 时,如果对象不再存在,则返回 nullptr。 因为 GameProcessor 拥有 Cell 对象,我希望 GameProcessor 对象始终有效。

在我的 TicTacToe 游戏中,每次创建新游戏板时,我都可以打破循环引用,但是一般来说,我会尽力避免产生打破循环引用的需要,因为这会增加代码维护的难度。 因此,当存在父-子关系并且子级需要访问父级时,我使用弱引用。

使用接口

为区分人和计算机玩家,我通过 HumanPlayer 和 ComputerPlayer 的具体实现创建了一个 IPlayer 接口。 GameProcessor 类承载了两个 IPlayer 对象(每个玩家一个)和一个对当前玩家的附加引用:

 

IPlayer^ m_player1; IPlayer^ m_player2; IPlayer^ m_currentPlayer;

图 5 是 IPlayer 接口。

图 5 IPlayer 接口

private interface class IPlayer {   property PlayerType Player   {     PlayerType get();   }   property wchar_t Symbol   {     wchar_t get();   }   virtual Windows::Foundation::IAsyncOperationWithProgress<uint32, double>^     ThinkAsync(Windows::Foundation::Collections::IVector<wchar_t>^ gameBoard); };

既然 IPlayer 接口是私有的,为什么不只使用 C++ 类呢? 坦白讲,我这么做是为了演示如何创建接口,以及如何创建不发布到元数据的私有类型。 如果要创建可重用的库,我可能会将 IPlayer 声明为公共接口,以便其他应用程序使用。否则,我可以选择只使用 C++ 而不使用 C++/CX 接口。

ComputerPlayer 类通过在后台执行 minimax 算法实现 ThinkAsync(请参阅所附代码下载中的 ComputerPlayer.cpp 文件了解此实现)。

Minimax 是创建 tic-tac-toe 等游戏的人工智能组件时常用的一种算法。 在 Stuart Russell 和 Peter Norvig 编写的书籍《人工智能: 一种现代方法》(Prentice Hall,2010)中可以了解有关 minimax 的更多信息。

我使用 PPL 改写了 Russell 和 Norvig 的 minimax 算法,以便并行运行(请参阅代码下载中的 minimax.h)。 这非常适合用纯 C++11 编写应用程序中大量涉及处理器的部分。 我没打败过计算机,也没见过计算机在计算机对计算机的游戏中打败它自己。 我承认,大多数好游戏都不会发生这种情况,因此,你可以决定如何应对: 增加另一个逻辑来分出游戏胜负。 要实现这个目的,一个基本方法是让计算机在随机的时间随机选择。 稍复杂一些的方法,是让计算机在随机的时间选择非最优下法。 对于奖励积分,向 UI 添加一个滑块控件来调整游戏的难度(难度越低,计算机就越有可能选择不太好的下法,至少是随机选择)。

ThinkAsync 对 HumanPlayer 类没有影响,因此我引发 Platform::NotImplementedException。 这需要我先测试 IPlayer::Player 属性,但是它为我节省了一项任务:

IAsyncOperationWithProgress<uint32, double>^   HumanPlayer::ThinkAsync(IVector<wchar_t>^ gameBoard) {   (void) gameBoard;   throw ref new NotImplementedException(); }

WRL

当 C++/CX 不能满足需要或你希望直接使用 COM 时,可以使用一种非常好的工具: WRL。 例如,为 Microsoft Media Foundation 创建媒体扩展时,必须创建既实现 COM 接口又实现 WinRT 接口的组件。 C++/CX ref 类只能实现 WinRT 接口,要创建媒体扩展必须使用 WRL,因为它支持 COM 和 WinRT 接口的实现。 有关 WRL 编程的更多信息,请参阅 bit.ly/YE8Dxu

深入了解

最初我对 C++/CX 扩展有所怀疑,但是很快就习惯了,我喜欢 C++/CX 扩展,因为用它们可以快速编写 Windows 应用商店应用程序和使用现代 C++ 特性。 如果你是 C++ 开发者,强烈建议至少尝试一下。

我只讨论了编写 C++/CX 代码时会遇到的一些常见模式。 Hilo(使用 C++ 和 XAML 编写的照片应用程序)更深入也更完整。 Hilo C++ 项目的开发工作很令我开心,编写新应用程序时我也经常将它作为参考。 建议你也看看 bit.ly/15xZ5JL

Thomas Petchel 是 Microsoft 开发部门的高级编程作者。 在过去八年中,他与 Visual Studio 团队一起为开发者受众创建文档和代码示例。

衷心感谢以下技术专家对本文的审阅: Michael Blome (Microsoft) 和 James McNellis (Microsoft)
Michael Blome 在 Microsoft 工作了 10 多年,致力于为 .NET Framework 中的 Visual C++、DirectShow、C# 语言参考、LINQ 和并行编程编写和重新编写文档。

James McNellis 是 C++ 迷,也是 Microsoft 的 Visual C++ 团队的软件开发者,他的工作是构建精彩的 C 和 C++ 库。 他是多产的 Stack Overflow 特约撰稿人,他的 Tweet 是 @JamesMcNellis,通过 jamesmcnellis.com 可以在线联系他。