Extensões de componente
Um tour pelo C++/CX
Pronto para escrever seu primeiro aplicativo da Windows Store? Ou você já está escrevendo aplicativos da Windows Store usando HTML/JavaScript, C# ou Visual Basic, e está curioso a respeito da história do C++?
Com as extensões de componente do Visual C++ (C++/CX) você pode elevar suas habilidades existentes combinando código C++ com o conjunto avançado de controles e bibliotecas fornecido pelo Tempo de Execução do Windows (WinRT). E se estiver usando o Direct3D, realmente poderá fazer com que seus aplicativos se destaquem na Windows Store.
Quando algumas pessoas escutam sobre o C++/CX, eles acham que têm de aprender uma linguagem totalmente nova, mas o fato é que para a grande maioria dos casos você lidará apenas com um punhado de elementos de linguagem não padrão como o modificador ^ ou as novas palavras-chave ref. Além disso, você só usará esses elementos no limite do seu aplicativo, ou seja, apenas quando precisar interagir com o Tempo de Execução do Windows. Seu ISO C++ portátil ainda atuará como o burro de carga do seu aplicativo. Talvez melhor de tudo, C++/CX é 100% código nativo. Embora sua sintaxe se assemelhe ao C++/Common Language Infrastructure (CLI), seu aplicativo não trará o CLR a menos que o queira.
Tenha você código C++ existente que já foi testado ou apenas prefira a flexibilidade e o desempenho do C++, tenha certeza de que com o C++/CX você não tem de aprender uma linguagem inteiramente nova. Nesse artigo você aprenderá o que torna as extensões de linguagem C++/CX para criar aplicativos da Windows Store únicas, e quando usar C++/CX para criar seu aplicativo da Windows Store.
Por que escolher o C++/CX?
Todo aplicativo tem seu próprio conjunto exclusivo de requisitos, como todo desenvolvedor tem suas próprias habilidades e capacidades exclusivas. É possível criar com sucesso um aplicativo da Windows Store usando C++, HTML/JavaScript ou o Microsoft .NET Framework, mas aqui estão algumas razões pelas quais você deve escolher o C++:
- Você prefere o C++ e tem habilidades existentes.
- Você deseja aproveitar o código que já escreveu e testou.
- Você deseja usar bibliotecas como Direct3D e C++ AMP para aproveitar totalmente o potencial do hardware.
A resposta não te de ser uma ou outra, é possível também misturar e combinar linguagens. Por exemplo, quando escrevi o exemplo do Otimizador de viagem do Bing Maps (bit.ly/13hkJhA), usei HTML e JavaScript para definir a interface do usuário e C++ para executar o processamento em segundo plano. O processo em segundo plano essencialmente resolve o problema do “caixeiro viajante”. Usei a Biblioteca de Padrões Paralela (PPL) (bit.ly/155DPtQ) em um componente WinRT C++ para executar meu algoritmo em paralelo em todas as CPUs disponíveis para melhorar o desempenho geral. Isso teria sido difícil de fazer com apenas o JavaScript.
Como o C++/CX funciona?
No coração de qualquer aplicativo da Windows Store está o Tempo de Execução do Windows. No coração do Tempo de Execução do Windows está a interface binária do aplicativo (ABI). As bibliotecas WinRT definem metadados através de arquivos de metadados do Windows (.winmd). Um arquivo .winmd descreve os tipos públicos que estão disponíveis, e seu formato é parecido com o formato usado em assemblies do .NET Framework. Em um componente C++, o arquivo .winmd contém apenas metadados; o código executável reside em um arquivo separado. Esse é o caso dos componentes do WinRT incluídos no Windows. (Para as linguagens do .NET Framework, o arquivo .winmd contém o código e os metadados, como um assembly do .NET Framework.) É possível exibir esses metadados do Desmontador MSIL (ILDASM) ou qualquer leitor de metadados CLR. A Figura 1 mostra como o Windows.Foundation.winmd, que contém muitos dos tipos WinRT fundamentais, se parece no ILDASM.
Figura 1 Inspecionando o Windows.Foundation.winmd com o ILDASM
A ABI é criada usando um subconjunto de COM para permitir ao Tempo de Execução do Windows interagir com diversas linguagens. Para chamar as APIs do WinRT, o .NET Framework e o JavaScript exigem projeções que são específicas a cada ambiente de linguagem. Por exemplo, o tipo de cadeia de caracteres subjacente do WinRT, HSTRING, é representado como System.String no .NET, um objeto String no JavaScript e a classe ref Platform::String no C++/CX.
Embora o C++ possa interagir diretamente com o COM, o C++/CX visa simplificar essa tarefa por meio de:
- Contagem automática de referência. Os objetos do WinRT são contados por referência e normalmente alocados em heap (não importa a linguagem que os usa). Os objetos são destruídos quando sua contagem de referência atinge zero. O benefício que o C++/CX oferece é que a contagem de referência é automática e uniforme. A sintaxe ^ permite as duas coisas.
- Manipulação de exceção. O C++/CX conta com exceções, e não códigos de erro, para indicar falhas. Valores COM HRESULT subjacentes são convertidos em tipos de exceção do WinRT.
- Uma sintaxe fácil de usar para consumir as APIs do WinRT, ao mesmo tempo que ainda mantendo o alto desempenho.
- Uma sintaxe fácil de usar para criar novos tipos WinRT.
- Uma sintaxe fácil de usar para executar conversão de tipo, trabalhando com eventos e outras tarefas.
E lembre-se, embora o C++/CX tome emprestado a sintaxe do C++/CLI, ele produz código nativo puro. Também é possível interagir com o Tempo de Execução do Windows usando a Biblioteca de Modelos C++ do Tempo de Execução do Windows (WRL), que apresentarei mais tarde. No entanto, espero que depois de usar o C++/CX você concordará que faz sentido. Você obtém o desempenho e o controle do código nativo, você não tem de aprender COM e seu código que interage com o Tempo de Execução do Windows será tão sucinto quanto possível, permitindo que você se concentre na lógica principal que torna seu aplicativo único.
O C++/CX é habilitado por meio da opção do compilador /ZW. Essa opção é definida automaticamente ao usar o Visual Studio para criar um projeto da Windows Store.
Um jogo da velha
Penso que a melhor maneira de aprender uma linguagem nova é criar realmente algo com ela. Para demonstrar as partes mais comuns do C++/CX, escrevi um aplicativo da Windows Store que joga o jogo da velha.
Para esse aplicativo, usei o modelo do Aplicativo em Branco (XAML) do Visual Studio. Chamei o projeto de Jogo da velha. Esse projeto usa XAML para definir a interface do usuário do aplicativo. Não vou me concentrar muito no XAML. Para saber mais sobre esse lado das coisas, consulte o artigo de Andy Rich, “Apresentando o C++/CX e XAML” (msdn.microsoft.com/magazine/jj651573), na edição especial de 2012 do Windows 8.
Também usei o modelo do componente do Tempo de Execução do Windows para criar um componente do WinRT que define a lógica do aplicativo. Adoro reutilização de código, portanto, criei um projeto de componente separado para que qualquer um pudesse usar a lógica central do jogo em qualquer aplicativo da Windows Store usando XAML e C#, Visual Basic ou C++.
A Figura 2 mostra a aparência do aplicativo.
Figura 2 O aplicativo do jogo da velha
Quando trabalhei no projeto Hilo C++ (bit.ly/Wy5E92), me apaixonei pelo padrão MVVM (Model-View-ViewModel). MVVM é um padrão de arquitetura que o ajuda a separar a aparência, ou exibição, de seu aplicativo de seus dados subjacentes, ou modelo. O modelo de exibição conecta a exibição ao modelo. Embora não tenha usado MVVM completo para meu jogo da velha, descobri que usar associação de dados para separar a interface do usuário da lógica do aplicativo tornou o aplicativo mais fácil de escrever, mais legível e fácil de manter no futuro. Para saber mais sobre como usamos MVVM no projeto Hilo C++, consulte bit.ly/XxigPg.
Para conectar o aplicativo ao componente do WinRT, adicionei uma referência ao projeto TicTacToeLibrary na caixa de diálogo Página de Propriedades do projeto TicTacToe.
Simplesmente definindo a referência, o projeto TicTacToe tem acesso a todos os tipos C++/CX públicos no projeto TicTacToeLibrary. Não é necessário especificar qualquer diretiva #include ou fazer algo mais.
Criando a interface do usuário do jogo da velha
Como disse anteriormente, não vou entrar muito no XAML, mas no meu layout vertical configuro uma área para exibir a pontuação, uma para a área de jogo principal e uma para configurar o próximo jogo (é possível ver o XAML no arquivo MainPage.xaml no download de código fornecido). Novamente, usei bastante a associação de dados aqui.
A definição da classe MainPage (MainPage.h) é mostrada na Figura 3.
Figura 3 A definição da classe 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;
};
}
Então, o que está em MainPage.g.h? Um arquivo .g.h contém uma definição de classe parcial gerada por compilador das páginas XAML. Basicamente, essas definições parciais definem as classes base necessárias e as variáveis de membro de qualquer elemento XAML que tenha o atributo x:Name. Aqui está 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;
};
}
A palavra-chave partial é importante porque ela permite a uma declaração de tipo distribuir arquivos. Nesse caso, MainPage.g.h contém partes geradas pelo compilador, e MainPage.h contém as partes adicionais que eu defino.
Observe as palavras-chave public e ref class na declaração MainPage. Uma diferença entre C++/CX e C++ é o conceito de acessibilidade de classe. Se você for um programador de .NET, você estará familiarizado com isso. Acessibilidade de classe significa se um tipo ou método é visível nos metadados e, portanto, acessível a partir de componentes externos. Um tipo C++/CX pode ser público ou privado. Público significa que a classe MainPage pode ser acessada fora do módulo (por exemplo, pelo Tempo de Execução do Windows ou por outro componente do WinRT). Um tipo privado pode ser acessado apenas dentro do módulo. Tipos privados lhe dão maior liberdade para usar tipos C++ em métodos públicos, o que não é possível com tipos públicos. Nesse caso, a classe MainPage é pública, portanto, acessível ao XAML. Examinarei alguns exemplos de tipos privados mais tarde.
As palavras-chave ref class dizem ao compilador que esse é um tipo WinRT e não um tipo C++. Um ref class é alocado no heap e seu tempo de vida é contado por referência. Como os tipos ref não contados por referência, seus tempos de vida são determinísticos. Quando a última referência a um objeto de tipo ref é liberada, seu destruidor é chamado e a memória desse objeto é liberada. Compare isso com o .NET, em que os tempos de vida são menos determinísticos e a coleta de lixo é usada para liberar memória.
Ao criar uma instância de um tipo ref, normalmente você usa o modificador ^. O modificador ^ é similar a um ponteiro do C++ (*), mas ele diz ao compilador para inserir código para gerenciar a contagem de referência do objeto automaticamente e excluir o objeto quando sua contagem de referência atingir zero.
Para criar uma estrutura POD (plain old data), use uma classe value ou struct value. Tipos value têm um tamanho fixo e consistem em campos apenas. Diferentes dos tipos ref, eles não têm propriedades. Windows::Foundation::DateTime e Windows::Foundation::Rect são dois exemplos de tipos value do WinRT. Ao criar uma instância dos tipos value, você não usa o modificador ^:
Windows::Foundation::Rect bounds(0, 0, 100, 100);
Observe também que MainPage é declarada como sealed. A palavra-chave sealed, que é similar à palavra-chave final do C++11, evita derivação adicional desse tipo. MainPage está sealed porque qualquer tipo ref público que tem um construtor público também deve ser declarado como sealed. Isso é porque o tempo de execução não reconhece a linguagem e nem todas as linguagens (por exemplo, JavaScript) entendem a herança.
Preste atenção agora aos membros da MainPage. A variável de membro m_processor (a classe GameProcessor é definida no projeto do componente do WinRT—falarei sobre esse tipo mais tarde) é privada simplesmente porque a classe MainPage é selada e não há possibilidade que uma classe derivada possa usá-la (e, em geral, membros de dados devem ser privados sempre que possível para impor o encapsulamento). O método OnNavigatedTo é protegido porque a classe Windows::UI::Xaml::Controls::Page, da qual a MainPage é derivada, declara esse método como protegido. O construtor e a propriedade Processor devem ser acessados por XAML e, portanto, ambos são públicos.
Você já está familiarizado com especificadores de acesso público, protegido e privado; seus significados no C++/CX são os mesmos que no C++. Para saber a respeito de especificadores internos e outros do C++/CX, consulte bit.ly/Xqb5Xe. Veremos um exemplo dos internos posteriormente.
Uma classe ref pode ter apenas tipos publicamente acessíveis em suas seções pública e protegida, isso é, apenas tipos primitivos, tipos public ref ou public value. Por outro lado,um tipo C++ pode conter tipos ref como variáveis de membro, em assinaturas de método e em variáveis de função local. Segue um exemplo do projeto Hilo C++:
std::vector<Windows::Storage::IStorageItem^> m_createdFiles;
A equipe do Hilo usa std::vector e não Platform::Collections::Vector para essa variável de membro privado porque não expomos a coleção fora da classe. Usar std::vector nos ajuda a usar o código C++ o máximo possível e torna sua intenção clara.
Passando para o construtor MainPage:
MainPage::MainPage() : m_processor(ref
new TicTacToeLibrary::GameProcessor())
{
InitializeComponent();
DataContext = m_processor;
}
Uso as palavras-chave ref new para criar uma instância do objeto GameProcessor. Use ref new em vez de new para construir objetos do tipo de referência do WinRT. Ao criar objetos em funções, é possível usar a palavra-chave auto do C++ para reduzir a necessidade de especificar o nome do tipo ou o uso de ^:
auto processor = ref new TicTacToeLibrary::GameProcessor();
Criando a Biblioteca TicTacToe
O código da biblioteca para o jogo da velha contém uma mistura de C++ e C++/CX. Para esse aplicativo, fingi que tinha um código C++ existente que eu já tinha escrito e testado. Incorporei esse código diretamente, adicionando C++/CX apenas para conectar a implementação interna ao XAML. Em outras palavras, usei C++/CX apenas para juntar os dois mundos. Vamos examinar algumas partes importantes da biblioteca e destacar os recursos do C++/CX ainda não discutidos.
A classe GameProcessor serve como o contexto de dados da interface do usuário (pense em modelo de exibição se estiver familiarizado com MVVM). Usei dois atributos, BindableAttribute e WebHostHiddenAttribute, ao declarar essa classe (como no .NET, é possível omitir a parte “Attribute” ao declarar atributos):
[Windows::UI::Xaml::Data::Bindable]
[Windows::Foundation::Metadata::WebHostHidden]
public ref class GameProcessor sealed : public Common::BindableBase
O BindableAttribute produz metadados que informam o Tempo de Execução do Windows de que o tipo suporta associação de dados. Isso garante que todas as propriedades públicas do tipo sejam visíveis para os componentes XAML. Derivo de BindableBase para implementar a funcionalidade requerida para fazer a associação funcionar. Como BindableBase é destinado para uso pelo XAML e não JavaScript, ele usa o atributo WebHostHiddenAttribute (bit.ly/ZsAOV3). Por convenção, também marquei a classe GameProcessor com esse atributo para essencialmente ocultá-la do JavaScript.
Separei as propriedades do GameProcessor em seções internas e públicas. As propriedades públicas são expostas ao XAML; as propriedades internas são expostas apenas para outros tipos e funções na biblioteca. Senti que fazendo essa distinção ajuda a tornar a intenção do código mais óbvia.
Um padrão de uso da propriedade comum é associar coleções ao XAML:
property Windows::Foundation::Collections::IObservableVector<Cell^>^ Cells
{
Windows::Foundation::Collections::IObservableVector<Cell^>^ get()
{ return m_cells; }
}
Essa propriedade define os dados do modelo para as células que aparecem na grade. Quando o valor de Células se altera, o XAML é atualizado automaticamente. O tipo da propriedade é IObservableVector, que é um dos diversos tipos definidos especificamente para C++/CX para permitir a total interoperabilidade com o Tempo de Execução do Windows. O Tempo de Execução do Windows define as interfaces de coleção independentemente da linguagem, e cada linguagem implementa as interfaces de sua própria maneira. No C++/CX, o namespace Platform::Collections fornece tipos como Vector e Map que fornecem implementações concretas para essas interfaces de coleções. Portanto, posso declarar a propriedade Cells como IObservableVector, mas retornar essa propriedade por um objeto Vector, que é específico do C++/CX:
Platform::Collections::Vector<Cell^>^ m_cells;
Então, quando você usa as coleções Platform::String e Platform::Collections versus os tipos e coleções padrão? Por exemplo, você deveria usar std::vector ou Platform::Collections::Vector para armazenar seus dados? Como regra prática, uso a funcionalidade Platform quando planejo trabalhar principalmente com o Tempo de Execução do Windows e tipos padrão como std::wstring e std::vector para meu código interno ou que exige muitos recursos computacionais. Também é possível converter entre Vector e std::vector quando for necessário. Você pode criar um Vector de um std::vector ou pode usar to_vector para criar um std::vector de um Vector:
std::vector<int> more_numbers =
Windows::Foundation::Collections::to_vector(result);
Há um custo de cópia associado ao fazer o marshalling entre os dois tipos de vetor. Portanto, novamente, considere que tipo é apropriado em seu código.
Outra tarefa comum é converter entre std::wstring e Platform::String. Veja como:
// 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));
Há dois pontos interessantes para serem observados na implementação da classe GameProcessor (GameProcessor.cpp). Primeiro, uso apenas C++ padrão para implementar a função checkEndOfGame. Esse é um lugar em que eu queria ilustrar como incorporar código C++ existente que eu já tinha escrito e testado.
O segundo ponto é o uso de programação assíncrona. Quando é hora de mudar a vez, uso a classe de tarefa PPL para processar os jogadores do computador no segundo plano, como mostrado na Figura 4.
Figura 4 Usando a classe de tarefa PPL para processar jogadores do computador no segundo plano
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());
}
}
Se você for um programador do .NET, pense na tarefa e seu método then como a versão C++ de async e await do C#. As tarefas estão disponíveis de qualquer programa C++, mas você as usará em todo o seu código C++/CX para manter seu aplicativo da Windows Store rápido e fluido. Para saber mais sobre programação assíncrona nos aplicativos da Windows Store, leia o artigo de Artur Laksberg de fevereiro de 2012, “Programação assíncrona em C++ usando PPL” (msdn.microsoft.com/magazine/hh781020) e o artigo da Biblioteca MSDN em msdn.microsoft.com/library/hh750082.
A classe Cell modela uma célula no tabuleiro de jogo. Duas novas coisas que essa classe demonstra são eventos e referências fracas.
A grade para a área do jogo da velha consiste em controles Windows::UI::Xaml::Controls::Button. Um controle Button gera um evento Click, mas você também pode responder a entradas do usuário definindo um objeto ICommand que define o contrato para comandos. Eu uso a interface ICommand em vez do evento Click para que os objetos Cell possam responder diretamente. No XAML para os botões que definem as células, a propriedade Command se associa à propriedade Cell::SelectCommand:
<Button Width="133" Height="133" Command="{Binding SelectCommand}"
Content="{Binding Text}" Foreground="{Binding ForegroundBrush}"
BorderThickness="2" BorderBrush="White" FontSize="72"/>
Usei a classe DelegateCommand do Hilo para implementar a interface ICommand. DelegateCommand guarda a função a ser chamada quando o comando é emitido e uma função opcional que determina se o comando pode ser emitido. Eis como configuro o comando para cada célula:
m_selectCommand = ref new DelegateCommand(
ref new ExecuteDelegate(this, &Cell::Select), nullptr);
Normalmente, você usará eventos predefinidos ao trabalhar com programação XAML, mas também poderá definir seus próprios eventos. Criei um evento que é gerado quando um objeto Cell é selecionado. A classe GameProcessor manipula esse evento verificando se o jogo terminou e trocando o jogador atual se necessário.
Para criar um evento, crie primeiro um tipo delegate. Pense em um tipo delegate com um ponteiro de função ou um objeto de função:
delegate void CellSelectedHandler(Cell^ sender);
Crio então um evento para cada objeto Cell:
event CellSelectedHandler^ CellSelected;
Eis como a classe GameProcessor assina esse evento para cada célula:
for (auto cell : m_cells)
{
cell->CellSelected += ref new CellSelectedHandler(
this, &GameProcessor::CellSelected);
}
Um delegate que é construído de um ^ e uma função ponteiro para membro (PMF) guarda apenas uma referência fraca ao objeto ^, portanto, essa construção não causará referências circulares.
Eis como objetos Cell geram o evento quando são selecionados:
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);
}
}
Qual o propósito da chamada Resolve no código anterior? Bem, a classe GameProcessor guarda uma coleção de objetos Cell, mas eu quero que cada objeto Cell possa acessar seu GameProcessor pai. Se Cell guardou uma referência forte a seu pai, em outras palavras, um GameProcessor^, eu criaria uma referência circular. Referências circulares podem fazer com que os objetos nunca sejam liberados porque a associação mútua faz com que ambos os objetos tenham sempre pelo menos uma referência. Para evitar isso, eu crio uma variável de membro Platform::WeakReference e a separo do construtor Cell (pense com muito cuidado sobre o gerenciamento do tempo de vida e o que cada objeto possui):
Platform::WeakReference m_gameProcessor;
Quando chamo WeakReference::Resolve, nullptr é retornado se o objeto não mais existir. Como o GameProcessor possui objetos Cell, espero que o objeto GameProcessor seja sempre válido.
No caso do meu jogo da velha, posso quebrar a referência circular cada vez que um novo tabuleiro de jogo é criado, mas em geral, tento evitar a necessidade de quebrar referências circulares, pois isso pode tornar o código menos capaz de manutenção. Portanto, quando tenho um relacionamento pai-filho e os filhos precisam acessar seu pai, uso referências fracas.
Trabalhando com interfaces
Para distinguir entre jogadores humanos e de computador, criei uma interface IPlayer com implementações concretas HumanPlayer e ComputerPlayer. A classe GameProcessor guarda dois objetos IPlayer, um para cada jogador, e uma referência adicional para o jogador atual:
IPlayer^ m_player1;
IPlayer^ m_player2;
IPlayer^ m_currentPlayer;
A Figura 5 mostra a interface IPlayer.
Figura 5 A interface 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);
};
Como a interface IPlayer é privada, por que simplesmente não usei classes C++? Para ser honesto, fiz isso para mostrar como criar uma interface e como criar um tipo privado que não é publicado nos metadados. Se eu estivesse criando uma biblioteca reutilizável, poderia declarar IPlayer como uma interface pública de modo que outros aplicativos pudessem usá-la. Caso contrário, poderia decidir ficar com C++ e não utilizar uma interface C++/CX.
A classe ComputerPlayer implementa ThinkAsync executando o algoritmo minimax no segundo plano (consulte o arquivo ComputerPlayer.cpp no download do código fornecido para explorar essa implementação).
Minimax é um algoritmo comum usado ao criar componentes de inteligência artificial para jogos como o jogo da velha. Você pode saber mais sobre o minimax no livro, “Artificial Intelligence: A Modern Approach” (Prentice Hall, 2010), por Stuart Russell e Peter Norvig.
Adaptei o algoritmo minimax de Russell e Norvig para ser executado em paralelo usando o PPL (consulte minimax.h no download do código). Foi uma grande oportunidade usar C++11 puro para escrever a parte que faz uso intenso do processador do meu aplicativo. Ainda não venci o computador e ainda não vi o computador vencer a si próprio em um jogo computador contra computador. Admito que isso não torna o jogo tão excitante, então, aqui está sua oportunidade de ação: Adicione lógica adicional para tornar o jogo possível de ser vencido. Uma maneira básica de fazer isso seria fazer com que o computador fizesse seleções aleatórias em tempos aleatórios. Uma maneira mais sofisticada seria fazer com que o computador escolhesse intencionalmente uma movimentação aquém da ideal em tempos aleatórios. Para pontos de bonificação, adicione um controle deslizante à interface do usuário que ajuste a dificuldade do jogo (quanto menos difícil, mais o computador escolhe uma movimentação aquém da ideal ou pelo menos uma aleatória).
Para a classe HumanPlayer, ThinkAsync não tem nada a fazer, assim, lanço Platform::NotImplementedException. Isso requer que eu teste a propriedade IPlayer::Player primeiro, mas isso me economiza uma tarefa:
IAsyncOperationWithProgress<uint32, double>^
HumanPlayer::ThinkAsync(IVector<wchar_t>^ gameBoard)
{
(void) gameBoard;
throw ref new NotImplementedException();
}
A WRL
Há uma poderosa ferramenta disponível em sua caixa de ferramentas para quando o C++/CX não fizer o que você precisa ou quando preferir trabalhar diretamente com o COM: a WRL. Por exemplo, ao criar uma extensão de mídia para o Microsoft Media Foundation, você deve criar um componente que implemente as interfaces do COM e do WinRT. Como as classes ref do C++/CX podem implementar apenas interfaces do WinRT, para criar uma extensão de mídia você deve usar a WRL, porque ela suporta a implementação das interfaces do COM e do WinRT. Para saber mais sobre a programação da WRL, consulte bit.ly/YE8Dxu.
Aprofundando-se
No início eu tive dúvidas em relação às extensões do C++/CX, mas logo elas se tornaram menos importantes, e eu gosto delas porque me permitem escrever aplicativos da Windows Store rapidamente e usar expressões modernas do C++. Se você for um desenvolvedor de C++, é altamente recomendável que você faça uma tentativa.
Examinei apenas alguns dos padrões comuns que você encontrará ao escrever código C++/CX. O Hilo, um aplicativo de fotos que usa C++ e XAML, vai muito mais fundo e é muito mais completo. Foi muito bom para mim trabalhar no projeto Hilo C++ e, na verdade, eu me refiro a ele com frequência quando escrevo novos aplicativos. Recomendo examiná-lo em bit.ly/15xZ5JL.
Thomas Petchel é um escritor técnico de programação sênior na Microsoft Developer Division. Ele passou os últimos oito anos com a equipe do Visual Studio criando documentação e exemplos de código para o público de desenvolvedores.
Agradecemos aos seguintes especialistas técnicos pela revisão deste artigo: Michael Blome (Microsoft) e James McNellis (Microsoft)
Michael Blome trabalhou na Microsoft por mais de 10 anos, envolvido na tarefa Sisyphean de escrever e reescrever documentação do MSDN para as referências de linguagens Visual C++, DirectShow e C#, LINQ e programação paralela no .NET Framework.
James McNellis é um aficionado do C++ e desenvolvedor de software na equipe do Visual C++ na Microsoft, onde ele cria formidáveis bibliotecas do C e C++. Ele é um colaborador produtivo do Stack Overflow, usa o tweet em @JamesMcNellis e pode ser encontrado ainda online via jamesmcnellis.com.