本文章是由機器翻譯。

元件擴展

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 很難做到這一點!

How Does C++/CX Work?

所有 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 中的樣子。


圖 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 是這一應用程式的外觀。


圖 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 元素定義需要的基類和成員變數。Here’s 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();
}

The WRL

當 C++/CX 不能滿足需要或你希望直接使用 COM 時,可以使用一種非常好的工具: the 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) and 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 可以線上聯繫他。