Compartir a través de


Extensiones de componente

Un recorrido por C++/CX

Thomas Petchel

Descargar el ejemplo de código

¿Está listo para escribir su primera aplicación de la Tienda Windows? ¿O ya ha estado escribiendo aplicaciones de la Tienda Windows con HTML/JavaScript, C# o Visual Basic y quiere conocer la historia de C++?

Con las extensiones de componente de Visual C++ (C++/CX), puede llevar sus habilidades existentes a nuevas alturas al combinar código de C++ con el rico conjunto de controles y bibliotecas que brinda Windows en tiempo de ejecución (WinRT). Y si usa Direct3D, realmente puede hacer que sus aplicaciones se destaquen en la Tienda Windows.

Cuando algunas personas escuchan sobre C++/CX piensan que tendrán que aprender un lenguaje completamente nuevo, pero en realidad, para la amplia mayoría de los casos, usará solo unos cuantos elementos de lenguaje no estándar, como el modificador ^ o las palabras clave ref new. Además, solo usará estos elementos en el límite de la aplicación, solo cuando deba interactuar con Windows en tiempo de ejecución. Su ISO C++ portátil seguirá siendo el caballito de batalla de su aplicación. Y quizás lo mejor de todo es que C++/CX es 100% código nativo. A pesar de que su sintaxis se vea similar a C++/ Common Language Infrastructure (CLI), la aplicación no incluirá CLR a menos que así se desee.

Sea que tenga código de C++ existente que ya se haya probado o bien solo prefiera la flexibilidad y el rendimiento de C++, puede estar seguro que con C++/CX no tiene que aprender un lenguaje completamente nuevo. En este artículo aprenderá por qué las extensiones de lenguaje C++/CX son únicas para la creación de aplicaciones de la Tienda Windows y cuándo usar C++/CX para crear su propia aplicación de la Tienda Windows.

¿Por qué elegir C++/CX?

Cada aplicación tiene su propio conjunto único de requisitos, tal como cada desarrollador tiene sus propias habilidades y capacidades únicas. Puede crear correctamente una aplicación de la Tienda Windows con C++, HTML/JavaScript o Microsoft .NET Framework, pero aquí presentamos algunas razones por las cuales puede elegir C++:

  • Usted prefiere C++ y tiene habilidades existentes.
  • Desea aprovechar el código que ya ha escrito y probado.
  • Desea usar bibliotecas como Direct3D y C++ AMP para aprovechar completamente el potencial del hardware.

La respuesta no tiene por qué ser una o la otra: también puede combinar y asociar lenguajes. Por ejemplo, cuando escribí el ejemplo Optimizador de recorridos de Mapas de Bing (bit.ly/13hkJhA), usé HTML y JavaScript para definir la IU y C++ para realizar el procesamiento en segundo plano. El proceso en segundo plano esencialmente soluciona el problema del "viajante". Usé la Biblioteca de modelos de procesamiento paralelos (PPL) (bit.ly/155DPtQ) en un componente de C++ de WinRT para ejecutar mi algoritmo en paralelo en todas las CPU disponibles para mejorar el rendimiento general. Hace esto solo desde JavaScript habría sido difícil.

¿Cómo funciona C++/CX?

Windows en tiempo de ejecución está en el centro de cualquier aplicación de la Tienda Windows. En el centro de Windows en tiempo de ejecución está la interfaz binaria de aplicaciones (ABI). Las bibliotecas de WinRT definen los metadatos a través de archivos de metadatos de Windows (.winmd). Un archivo .winmd describe los tipos públicos que se encuentran disponibles y su formato es similar al formato que se usa en los ensamblados de .NET Framework. En un componente de C++, el archivo .winmd contiene solo metadatos; el código ejecutable reside en un archivo distinto. Esto es lo que ocurre con los componentes de WinRT incluidos en Windows. (En el caso de los lenguajes .NET Framework, el archivo .winmd contiene tanto el código como los metadatos, al igual que un ensamblado de .NET Framework). Puede ver estos metadatos desde el desensamblador de MSIL (ILDASM) o desde cualquier lector de metadatos de CLR. La figura 1 muestra qué aspecto tiene Windows.Foundation.winmd, que contiene muchos de los tipos fundamentales de WinRT, en ILDASM.

Inspecting Windows.Foundation.winmd with ILDASMFigura 1 Inspección de Windows.Foundation.winmd con ILDASM

La ABI se compila mediante un subconjunto de COM para permitir que Windows en tiempo de ejecución interactúe con varios lenguajes. Para llamar a las API de WinRT, .NET Framework y JavaScript requiere proyecciones específicas para cada entorno de lenguaje. Por ejemplo, el tipo de cadena WinRT subyacente, HSTRING, se representa como System.String en .NET, un objeto String en JavaScript y con la clase de referencia Platform::String en C++/CX.

A pesar de que C++ puede interactuar directamente con COM, C++/CX pretender simplificar esta tarea a través de:

  • Recuento automático de referencias. La referencia de los objetos de WinRT se recuenta y, normalmente, se asignan en el montón (independientemente del lenguaje que los usa). Los objetos se destruyen cuando su recuento de referencias llega a cero. El beneficio que ofrece C++/CX es que el recuento de referencias es automático y uniforme. La sintaxis ^permite ambos aspectos.
  • Control de excepciones. C++/CX depende de las excepciones, y no de los códigos de error, para indicar errores. Los valores subyacentes de COM HRESULT se traducen a tipos de excepción de WinRT.
  • Una sintaxis fácil de usar para consumir las API de WinRT, a la vez que se mantiene un alto rendimiento.
  • Una sintaxis fácil de usar para crear nuevos tipos de WinRT.
  • Una sintaxis fácil de usar para realizar la conversión de tipo, trabajar con eventos y otras tareas.

Y recuerde que, a pesar de que C++/CX toma prestada la sintaxis de C++/CLI, genera código nativo puro. También puede interactuar con Windows en tiempo de ejecución si utiliza la Biblioteca de plantillas C++ de Windows en tiempo de ejecución (WRL), que presentaré más adelante. Sin embargo, estará de acuerdo con que tiene sentido una vez que use C++/CX. Se tiene el rendimiento y el control de código nativo, no tiene que aprender COM y el código que interactúa con Windows en tiempo de ejecución será lo más conciso posible, lo que le permitirá centrarse en la lógica principal que hace que su aplicación sea única. 

C++/CX se habilita a través de la opción de compilador /ZW. Este modificador se define automáticamente cuando se usa Visual Studio para crear un proyecto para la Tienda Windows.

Un juego de "tres en raya"

Creo que la mejor forma de aprender un lenguaje nuevo es usarlo para crear realmente algo. Para demostrar los componentes más comunes de C++/CX, escribí una aplicación de la Tienda Windows para jugar "Tres en raya" (o según su país, "Ta-te-tí", "Gato" o "Triqui").

Para esta aplicación, usé la plantilla Aplicación vacía de Visual Studio (XAML). El nombre del proyecto fue TicTacToe. Este proyecto usa XAML para definir la IU de la aplicación. No me voy a centrar demasiado en el XAML. Para obtener más información acerca de este aspecto del asunto, consulte el artículo “Presentación de C++/CX y XAML” (msdn.microsoft.com/magazine/jj651573) de Andy Rich que aparece en el número especial de Windows 8 de 2012.

También usé la plantilla Componente de Windows en tiempo de ejecución para crear un componente de WinRT que define la lógica de la aplicación. Me encanta reutilizar código, por lo que creé un proyecto de componente independiente para que cualquier persona pudiera usar la lógica principal del juego en cualquier aplicación de la Tienda Windows con XAML y C#, Visual Basic o C++.

La figura 2 muestra cómo se ve la aplicación.

The TicTacToe ApFigura 2 La aplicación TicTacToe

Cuando trabajé en el proyecto Hilo C++ (bit.ly/Wy5E92), me enamoré del patrón Model-View-ViewModel (MVVM). MVVM es un patrón de arquitectura que ayuda a separar la apariencia, o vista, de la aplicación de sus datos subyacentes, o modelo. El modelo de la vista conecta la vista con el modelo. A pesar de que no usé completamente MVVM en mi juego de tres en raya, sí encontré que usar enlace de datos para separar la IU de la lógica de la aplicación hizo que la aplicación fuese más fácil de escribir, más legible y más fácil de mantener en el futuro. Para obtener más información sobre cómo usamos MVVM en el proyecto Hilo C++, consulte bit.ly/XxigPg.

Para conectar la aplicación con el componente de WinRT, agregué una referencia al proyecto TicTacToeLibrary desde el cuadro de diálogo Páginas de propiedad del proyecto TicTacToe.

Simplemente al definir la referencia, el proyecto TicTacToe tiene acceso a todos los tipos públicos de C++/CX en el proyecto TicTacToeLibrary. No necesita especificar ninguna directiva #include ni hacer nada más.

Creación de la IU de TicTacToe

Tal como dije anteriormente, no profundizaré mucho en XAML, pero en mi diseño vertical configuré un área para mostrar la puntuación, una para el área de juegos principal y una para configurar el juego siguiente (puede ver el XAML en el archivo MainPage.xaml en la descarga de código de este número). Repito que aquí usé ampliamente el enlace de datos.

La figure 3 muestra la definición de la clase MainPage (MainPage.h).

Figura 3 Definición de la clase 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;
  };
}

¿Qué hay en MainPage.g.h? Un archivo .g.h contiene una definición de clase parcial generada por compilador para las páginas XAML. Básicamente, estas definiciones parciales definen las clases base y las variables de miembro requeridas para cualquier elemento XAML que tenga el atributo x:Name. El siguiente es el archivo 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;
  };
}

La palabra clave parcial es importante porque permite que una declaración de tipo abarque archivos. En este caso, MainPage.g.h contiene elementos generados por compilador y MainPage.h contiene los elementos adicionales que defino.

Observe las palabras clave de clase de referencia y públicas en la declaración de MainPage. Una diferencia entre C++/CX y C++ es el concepto de accesibilidad de clase. Si es programador de .NET, estará familiarizado con esto. Accesibilidad de clase se refiere a un tipo o método que está visible en los metadatos y, por lo tanto, que es accesible desde componentes externos. Un tipo de C++/CX puede ser público o privado. Si es público, se refiere a que es posible obtener acceso a la clase MainPage desde fuera del módulo (por ejemplo, puede obtener acceso a ella Windows en tiempo de ejecución u otro componente de WinRT). Solo es posible obtener acceso a un tipo privado desde dentro del módulo. Los tipos privados le brindan más libertad para usar tipos de C++ en métodos públicos, lo que no es posible con tipos públicos. En este caso, la clase MainPage es pública, por lo que puede obtenerse acceso a ella en XAML. Más adelante analizaremos algunos ejemplos de tipos privados.

Las palabras clave de clase de referencia indican al compilador que este es un tipo de WinRT y no un tipo de C++. Una clase de referencia se asigna en el montón y se cuentan las referencias de su duración. Como se cuentan las referencias de los tipos de referencia, sus duraciones son deterministas. Cuando se libera la última referencia a un objeto de tipo de referencia, se llama a su destructor y se libera la memoria para ese objeto. Compare esto con .NET, donde las duraciones son menos deterministas y la recolección de elementos no utilizados se usa para liberar memoria.

Cuando crea una instancia de un tipo de referencia, normalmente usa el modificador ^ (que se pronuncia "hat"). El modificador ^es similar a un puntero de C++ (*), pero le indica al compilador que inserte código para administrar el recuento de referencias del objeto de manera automática y que elimine el objeto cuando el recuento de referencias llegue a cero.

Para crear una estructura de datos estándar (POD), use una clase de valor o struct de valor. Los tipos de valor tienen un tamaño fijo y constan solo de campos. A diferencia de los tipos de referencia, no tienen propiedades. Windows::Foundation::DateTime y Windows::Foundation::Rect son dos ejemplos de tipos de valor de WinRT. Cuando cree una instancia de tipos de valor, no use el modificador ^:

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

Observe además que MainPage se declara como sellado. La palabra clave sellada, que es similar a la palabra final de C++11, evita una mayor derivación de ese tipo. MainPage se sella porque cualquier tipo de referencia público que tenga un constructor público también se debe declarar como sellado. Esto se debe a que el tiempo de ejecución es independiente del lenguaje y no todos los lenguajes (por ejemplo, JavaScript) comprenden la herencia.

Ahora dirija su atención a los miembros de MainPage. La variable de miembro m_processor (la clase GameProcessor está definida en el proyecto de componente de WinRT; más adelante hablaré sobre ese tipo) es privada simplemente porque la clase MainPage está sellada y no existe ninguna posibilidad de que una clase derivada pueda usarla (y, en general, los miembros de datos deben ser privados en la medida de lo posible para aplicar la encapsulación). El método OnNavigatedTo está protegido porque la clase Windows::UI::Xaml::Controls::Page, de la cual deriva MainPage, declara este método como protegido. XAML debe obtener acceso al constructor y a la propiedad Processor y, por lo tanto, ambos son públicos.

Usted ya conoce los especificadores de acceso públicos, protegidos y privados; sus significados en C++/CX son los mismos que en C++. Para aprender sobre los especificadores internos y otros especificadores de C++/CX, consulte bit.ly/Xqb5Xe. Más adelante verá un ejemplo de un modificador interno.

Una clase de referencia puede tener solo tipos accesibles públicamente en sus secciones públicas y protegidas; es decir, solo tipos primitivos, de referencia pública o tipos de valor público. Por el contrario, un tipo de C++ puede contener tipos de referencia como variables de miembro, en firmas del método y en variables de funciones locales. El siguiente es un ejemplo del proyecto Hilo C++:

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

El equipo de Hilo usa std::vector y no Platform::Collections::Vector para esta variable de miembro privada, porque no exponemos la colección fuera de la clase. Usar std::vector nos ayuda a usar lo más posible código C++ y hace que la intención sea clara.

Para moverse en el constructor MainPage:

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

Uso las palabras clave ref new para crear una instancia del objeto GameProcessor. Use ref new en lugar de new para construir objetos de tipo de referencias de WinRT. Cuando crea objetos en funciones, puede usar la palabra clave auto de C++ para disminuir la necesidad de especificar el tipo de nombre o el uso de ^:

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

Creación de la biblioteca de TicTacToe

El código de biblioteca para el juego TicTacToe contiene una combinación de C++ y C++/CX. Para esta aplicación, hice cuenta de que tenía código de C++ existente que ya había escrito y probado. Incorporé directamente este código y agregue código de C++/CX solo para conectar la implementación interna con XAML. En otras palabras, usé C++/CX solo para unir ambos mundos. Veamos algunos de los elementos importantes de la biblioteca y resaltemos cualquier característica de C++/CX que no hayamos analizado todavía.

La clase GameProcessor sirve como el contexto de datos para la IU (piense en el modelo de vista si conoce MVVM). Usé dos atributos, BindableAttribute y WebHostHiddenAttribute, cuando declaré esta clase (como en .NET, puede omitir el elemento "Attribute" cuando declara los atributos):

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

El atributo BindableAttribute genera metadatos que indican a Windows en tiempo de ejecución que el tipo es compatible con enlaces de datos. Esto garantiza que todas las propiedades públicas del tipo están visibles para los componentes de XAML. Realizo la derivación desde BindableBase para implementar la funcionalidad que se requiere para que el enlace funcione. Como BindableBase está pensado para que lo use XAML y no JavaScript, usa el atributo WebHost­HiddenAttribute (bit.ly/ZsAOV3). Por convención, también marqué la clase GameProcessor con este atributo para básicamente ocultarlo de JavaScript.

Separé las propiedades de GameProcessor en secciones públicas e internas. Las propiedades públicas se exponen a XAML; las propiedades internas solo se exponen a otros tipos y funciones en la biblioteca. Creí que al hacer esta distinción, la intención del código sería más obvia.

Un patrón de uso de propiedad común es enlazar colecciones con XAML: 

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

Esta propiedad define los datos del modelo para las celdas que aparecen en la cuadrícula. Cuando el valor de las celdas cambia, el XAML se actualiza automáticamente. El tipo de la propiedad es IObservableVector, que es uno de los diversos tipos definidos específicamente para que C++/CX permitan la completa interoperabilidad con Windows en tiempo de ejecución. Windows en tiempo de ejecución define interfaces de colección independientes del lenguaje y cada lenguaje implementa esas interfaces a su propia manera. En C++/CX, el espacio de nombres Platform::Collections brinda tipos como Vector y Map que proporcionan implementaciones concretas para estas interfaces de colecciones. Por lo tanto, puedo declarar la propiedad Cells como IObservableVector, pero hacer que un objeto Vector devuelva esa propiedad, lo que es específico de C++/CX:

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

De este modo, ¿cuándo usa las colecciones Platform::String y Platform::Collections en contraposición con las colecciones y tipos estándar? Por ejemplo, ¿debe usar std::vector o Platform::Collections::Vector para almacenar los datos? Como norma general, uso la funcionalidad Platform cuando planeo trabajar principalmente con Windows en tiempo de ejecución y tipos estándar como std::wstring y std::vector para mi código interno o mi código intensivo desde el punto de vista del cálculo. También puede realizar fácilmente la conversión entre Vector y std::vector cuando así lo necesite. Puede crear un Vector desde un std::vector o puede usar to_vector para crear un std::vector a partir de un Vector:

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

Existe un costo de copia asociado cuando se calcula la referencia entre los dos tipos de vectores por lo que le recordamos que considere el tipo que sea adecuado en su código.

Otra tarea común es realizar la conversión entre std::wsrting y Platform:String. Aquí le mostramos cómo:

// 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));

Hay dos puntos interesantes que se deben observar en la implementación de la clase GameProcessor (GameProcessor.cpp). Primero, solo uso C++ estándar para implementar la función checkEndOfGame. Este es un punto donde quise ilustrar cómo incorporar código C++ existente que ya había escrito y probado.

El segundo punto es el uso de programación asincrónica. Cuando llega el momento de cambiar turnos, uso la clase de tarea PPL para procesar los jugadores del sistema en segundo plano, tal como aparece en la figura 4.

Figura 4 Uso de la clase de tarea PPL para procesar jugadores del sistema en 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());
  }
}

Si es programador de .NET, piense en la tarea y en su método then como la versión C++ de async y await en C#. Las tareas están disponibles desde cualquier programa C++, pero las usará durante todo el código C++/CX para que la aplicación de la Tienda Windows se mantenga rápida y fluida. Para obtener más información acerca de la programación asincrónica en las aplicaciones de la Tienda Windows, lea el artículo “Programación asincrónica en C++ con PPL” (msdn.microsoft.com/magazine/hh781020) de Artur Laksberg de febrero de 2012 y el artículo de MSDN Library en msdn.microsoft.com/library/hh750082.

La clase Cell modela una celda en el tablero de juego. Dos nuevos elementos que demuestra esta clase son eventos y referencias débiles.

La cuadrícula para el área de juego de TicTacToe consta de controles Windows::UI::Xaml::Controls::Button. Un control Button genera un evento Click, pero también puede responder a la intervención del usuario si define un objeto ICommand que define el contrato para comandos. Uso la interfaz ICommand en lugar del evento Click para que los objetos Cell puedan responder directamente. En el XAML para los botones que definen las celdas, la propiedad Command se enlaza con la propiedad Cell::SelectCommand:

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

Usé la clase DelegateCommand de Hilo para implementar la interfaz ICommand. DelegateCommand contiene la función que se debe llamar cuando se emite el comando y una función opcional que determina si el comando se puede emitir. Configuré el comando de cada celda de la siguiente manera:

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

Normalmente usará eventos predefinidos cuando realice programación de XAML, pero también puede definir sus propios eventos. Creé un evento que surge cuando se selecciona un objeto Cell. La clase GameProcessor maneja este evento revisando si el juego se ha terminado y cambiando el jugador actual en caso de ser necesario.

Para crear un evento, primero debe crear un tipo delegado. Piense que un tipo delegado es como un puntero de función o un objeto de función:

delegate void CellSelectedHandler(Cell^ sender);

Luego creo un evento para cada objeto Cell:

event CellSelectedHandler^ CellSelected;

Esta es la manera en que la clase GameProcessor se suscribe al evento para cada celda:

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

Un delegado que se construye a partir de un ^ y de una función puntero a miembro (PMF) contiene solo una referencia débil al objeto ^, por lo que esta construcción no provocará referencias circulares.

Aquí aparece cómo los objetos Cell generan el evento cuando se les selecciona:

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);
  }
}

¿Cuál es el propósito de la llamada Resolve en el código anterior? La clase GameProcessor contiene una colección de objetos Cell, pero deseo que cada objeto Cell pueda obtener acceso a su GameProcessor principal. Si Cell hubiese contenido una fuerte a su principal (en otras palabras, un GameProcessor^), habría creado una referencia circular. Las referencias circulares pueden hacer que nunca se liberen los objetos, porque la asociación mutua hace que ambos objetos siempre tengan al menos una referencia. Para evitar esto, creo una variable de miembro Platform::WeakReference y la define desde el constructor Cell (piense con mucho cuidado en la administración de la duración y en qué objetos poseen qué elementos):

Platform::WeakReference m_gameProcessor;

Cuando llamo a WeakReference::Resolve, se devuelve nullptr si el objeto ya no existe. Como GameProcessor posee objetos Cell, espero que el objeto GameProcessor siempre sea válido.

En el caso de mi juego Tres en raya, puedo romper la referencia circular cada vez que se crea un nuevo tablero de juego pero, en general, intento evitar la necesidad de romper referencias circulares porque puede hacer que el código sea más difícil de mantener. Por lo tanto, cundo tengo una relación primario-secundario y los secundarios necesitan acceso a su primario, uso referencias débiles. 

Trabajo con interfaces

Para distinguir entre jugadores humanos y del sistema, creé una interfaz IPlayer con implementaciones concretas de HumanPlayer y ComputerPlayer. La clase GameProcessor contiene dos objetos IPlayer, uno para cada jugador, y una referencia adicional al jugador actual:

 

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

La figura 5 muestra la interfaz IPlayer.

Figura 5 La interfaz 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 la interfaz IPlayer es privada, ¿por qué no usé simplemente clases C++? En honor a la verdad, lo hice para mostrar cómo crear una interfaz y cómo crear un tipo privado que no estuviera publicado en los metadatos. Si hubiese creado una biblioteca reutilizable, habría declarado a IPlayer como una interfaz pública para que otras aplicaciones pudieran usarla. De lo contrario, habría elegido seguir con C++ y no usar una interfaz C++/CX.

La clase ComputerPlayer implementa ThinkAsync ejecutando el algoritmo minimax en segundo plano (consulte el archivo ComputerPlayer.cpp en la descarga de código de este número para explorar esta implementación).

Minimax es un algoritmo común que se usa al crear componentes de inteligencia artificial para juegos como el tres en raya. Puede obtener más información acerca de minimax en el libro "Artificial Intelligence: A Modern Approach” (Prentice Hall, 2010) de Stuart Russell y Peter Norvig.

Adapté el algoritmo minimax de Russell y Norvig para que se ejecute en paralelo al usar PPL (consulte minimax.h en la descarga de código). Fue una gran oportunidad para usar C++11 puro para escribir la parte con gran uso del procesador de mi aplicación. Todavía tengo que vencer al sistema y nunca lo he visto vencerse a sí mismo en un juego de sistema contra sistema. Admito que esto no hace que sea el más emocionante de los juegos, por lo que esta es su llamada a la acción: agregue lógica adicional para hacer que se pueda ganar el juego. Una manera básica de hacer esto sería hacer que el sistema hiciera selecciones aleatorias en momentos aleatorios. Una manera más sofisticada sería hacer que el sistema escogiera a propósito una movida menos óptima en momentos aleatorios. Para puntos adicionales, agregue un control deslizante a la IU que ajuste la dificultad del juego (para tener un juego menos difícil, el equipo elige más veces un movimiento menos óptimo o al menos uno aleatorio)

ThinkAsync no tiene nada que ver con la clase HumanPlayer, por lo que genero Platform::NotImplementedException. Esto requiere que pruebe primero la propiedad IPlayer::Player, pero me ahorra una tarea:

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

La WRL

En el cuadro de herramientas hay disponible un gran mazo para cuando C++/CX no tenga lo necesario o cuando prefiera trabajar directamente con COM: la WRL. Por ejemplo, cuando crea una extensión de medios para Microsoft Media Foundation, debe crear un componente que implemente interfaces de COM y WinRT. Como las clases de referencia C++/CX solo pueden implementar interfaces de WinRT, para crear una extensión de medios debe usar la WRL porque es compatible con la implementación de las interfaces de COM y WinRT. Para obtener más información acerca de la programación de WRL, consulte bit.ly/YE8Dxu.

Mayor profundización

Al comienzo tenía ciertas dudas con las extensiones de C++/CX, pero pronto fueron automáticas y me gustan porque me permite escribir aplicaciones de la Tienda Windows de manera rápida y usar modernas expresiones de C++. Si es desarrollador de C++, le recomiendo encarecidamente que les dé al menos una oportunidad.

Revisé solo algunos de los patrones comunes que encontrará al escribir código de C++/CX. Hilo, una aplicación fotográfica que usa C++ y XAML, es mucho más profunda y más completa. Lo pasé muy bien trabajando en el proyecto Hilo C++ y realmente vuelvo a él con frecuencia cuando escribo aplicaciones nuevas. Le recomiendo revisarlo en bit.ly/15xZ5JL.

Thomas Petchel trabaja como programador senior en la División de programadores de Microsoft. Ha pasado los últimos ocho años trabajando en conjunto con el equipo de Visual Studio en la creación de documentación y ejemplos de código para la audiencia de desarrolladores.

Gracias a los siguientes expertos técnicos por su ayuda en la revisión de este artículo: Michael Blome (Microsoft) y James McNellis (Microsoft)
Michael Blome ha trabajado durante más de 10 años en Microsoft y ha participado en la tarea Sisyphean de escribir y reescribir documentación de MSDN para Visual C++, DirectShow, referencia del lenguaje de C++, LINQ y programación en paralelo en .NET Framework.

James McNellis es aficionado a C++ y desarrollador de software en el equipo de Visual C++ de Microsoft, donde crea increíbles bibliotecas de C y C++. Es un prolífico colaborador en Stack Overflow, su cuenta de Twitter es @JamesMcNellis y puedes encontrarlo en línea en cualquier otro lugar a través de jamesmcnellis.com.