Novedades de C++17

Después de tres años de trabajo, C++17 ha sido finalmente estandarizado. Esta nueva versión de C++ incorpora y elimina elementos del lenguaje, con el fin de ponerlo al día y convertirlo en un lenguaje moderno y eficaz. El comité ISO de C++ se ha tomado muy en serio su labor, C++11 supuso este cambio de mentalidad, que se ha mantenido en C++14 y ahora en C++17, la última versión de C++.

Repasemos las noveades que incorpora C++17 respecto a C++14

if-init

Ahora podemos incluir una sentencia de inicialización antes de la condición en sentencias if y switch. Esto es particularmente útil si queremos operar con un objeto y desconocemos su validez.

// ANTES
Device dev = get_device();
if(dev.isOk()){
    dev.hacerCosas();
}

// AHORA
if(Device dev = get_device(); dev.isOk()){
    dev.hacerCosas();
}

Declaraciones de descomposición

Azúcar sintántico que permite mejorar la legibiliad en ciertas situaciones. Por ejemplo, en el caso de las tuplas, su uso se vuelve trivial.

// FUNCIÓN QUE DEVUELVE TUPLA
std::tuple<int, std::string> funcion();

// C++14
auto tup = funcion();
int i = std::get<0>(tup);
std::string s = std::get<1>(tup);

// C++17
auto [i,s] = funcion();

Esto funciona para multitud de estructuras de datos, como estructuras, arrays, std::array, std::map,…

std::map m = ...;
for(auto && [key, value] : m){

}

Deduction Guides

Ahora es menos necesario que nunca indicar los tipos en ciertas expresiones. Por ejemplo, al crear pares y tuplas:

// ANTES
auto p = std::pair<int,std::string>(42,"Adrianistan");

// AHORA
auto p = std::pair(42,"Adrianistan");

Esto por supuesto también sirve para estructuras y otras construcciones:

template<typename T>
struct Thingy
{
  T t;
};

// Observa
Thingy(const char *) -> Thingy<std::string>;

Thingy thing{"A String"}; // thing.t es de tipo std::string

template auto

// ANTES
template <typename T, T v>
struct integral_constant
{
   static constexpr T value = v;
};
integral_constant<int, 2048>::value
integral_constant<char, 'a'>::value

// AHORA
template <auto v>
struct integral_constant
{
   static constexpr auto value = v;
};
integral_constant<2048>::value
integral_constant<'a'>::value

Fold expressions

Imagina que quieres hacer una función suma, que admita un número ilimitado de parámetros. En C++17 no se necesita apenas código.

template <typename... Args>
auto sum(Args&&... args) {
   return (args + ... + 0);
}

Namespaces anidados

Bastante autoexplicativo

// ANTES

namespace A{
    namespace B {
        bool check();
    }
}

// AHORA

namespace A::B {
    bool check();
}

Algunos [[atributos]] nuevos

[[maybe_unused]]

Se usa para suprimir la advertencia del compilador de que no estamos usando una determinada variable.

int x = 5;
[[maybe_unused]] bool azar = true;
x = x + 10

[[fallthrough]]

Permite usar los switch en cascada sin advertencias del compilador.

switch (device.status())
{
case sleep:
   device.wake();
   [[fallthrough]];
case ready:
   device.run();
   break;
case bad:
   handle_error();
   break;
}

Variables inline

Ahora es posible definir variables en múltiples sitios con el mismo nombre y que compartan una misma instancia. Es recomendable definirlas en un fichero de cabecera para luego reutilizarlas en ficheros fuente.

// ANTES
// en una cabecera para que la usasen los demás
extern int x;

// solo en un fichero fuente, para inicializarla
int x = 42;
// AHORA

// en la cabecera
inline int x = 42;

if constexpr

Ahora es posible introducir condicionales en tiempo de compilación (similar a las macros #IFDEF pero mejor hecho). Estas expresiones con constexpr, lo que quiere decir que son código C++ que se evalúa en tiempo de compilación, no de ejecución.

template<class T>
void f (T x)
{
    if  constexpr(std:: is_integral <T>::value)  {
        implA(x);
    }
    else  if  constexpr(std:: floating_point <T>::value)  {
        implB(x);
    }
    else
    {
        implC(x);
    }
}

std::optional

Tomado de la programación funcional, se incorpora el tipo optional, que representa un valor que puede existir o no. Este tipo ya existe en Rust bajo el nombre de Option y en Haskell como Maybe.

std::optional opt = f();
if(opt)
    g(*opt);

// otra opción de uso si queremos proveer de un reemplazo
std::optional opt = f();
std::cout << opt.value_or(0) << std::endl;

std::variant

Descritas como las unions pero bien hechas. Pueden contener variables de los tipos que nosotros indiquemos.

std::variant<int, double, std::vector> precio; // precio puede ser un int, un double o un std::vector

// comprobar si el valor en un variant es de un determinado tipo
if(std::holds_alternative<double>(precio))
    double x = std::get<double>(precio);

std::any

Si con std::variant restringimos los posibles tipos de la variable a los indicados, con std::any admitimos cualquier cosa.

std::any v = ...;
if (v.type() == typeid(int)) {
   int i = any_cast<int>(v);
}

std::filesystem

Se añade a la librería estándar este namespace con el tipo path y métodos para iterar y operar con directorios. Dile adiós a las funciones POSIX o Win32 equivalentes.

#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;

void main(){
  fs::path dir = "/";
  dir /= "sandbox";
  fs::path p = dir / "foobar.txt";
  std::cout << p.filename() << "\n";
  fs::copy(dir, "/copy", fs::copy_options::recursive);
}

Algoritmos en paralelo

Muchos de los algoritmos de STL ahora pueden ejecutarse en paralelo bajo demanda. Con std::execution::par indicamos que queremos que el algoritmo se ejecute en paralelo.

std::sort(std::execution::par, first, last);

¿Qué novedades se esperan en C++20?

Ya hemos visto lo que trae C++17. Ahora veremos que se espera que traiga C++20 en 2020.

  • Módulos. Reemplazar el sistema de includes
  • Corrutinas. Mejorar la programación asíncrona
  • Contratos. Mejorar la calidad del código
  • Conceptos. Mejorar la programación genérica
  • Redes. Estandarizar la parte de red en C++ tal y como se ha hecho con std::filesystem
  • Rangos. Nuevos contenedores

Referencias:

 

loading...

Un nuevo lenguaje de programación para juegos

En la inocentada sobre Rust puse un vídeo de Jonathan Blow titulado Ideas about a new programming language for games. En el vídeo, Blow analiza los problemas que presenta C++ para el desarrollo de juegos y por qué según él ni Go ni D ni Rust consiguen mejorar la situación. El lenguaje de programación perfecto para juegos debería tener las siguientes características:

  • Poca fricción
  • Placer por programar
  • Rendimiento
  • Simplicidad
  • Diseñado para buenos programadores

Con poca fricción se refiere a que la tarea de programar no debe añadir mucha complejidad para solucionar problemas que tendríamos si programásemos de la forma más simple posible. Fricción es para él RAII en C++. Fricción es la gestión de errores en Rust. Fricción se entiende como código que no añade significativamente nada pero que es necesario para un correcto funcionamiento. Fricción es rellenar papeleo de Hacienda. Muchos defensores de estas posturas argumentan que en realidad esa productividad perdida se recupera con el tiempo al reducir el número de bugs que pueden ocurrir. Blow dice que según su experiencia en juegos AAA realmente no compensa. Tardas más tiempo solventado bugs potenciales que bugs reales. Su solución no es evitar al 100% este tipo de bugs (como hace Rust) sino habilitar unas herramientas potentes que ayuden a solucionar estos bugs si alguna vez suceden.

Esto se relaciona con el placer por programar. Un lenguaje que te invite a programar, a experimentar, donde te sientas a gusto. Muchos lenguajes han perdido esa esencia. Con el tiempo muchos lenguajes se han ido complicando de forma innecesaria y se han ido volviendo pesadillas. Ruby sería el caso contrario, un lenguaje que conserva ese placer. Pero Ruby no entra en juego por razones obvias de rendimiento.

Con rendimiento básicamente dice que cualquier lenguaje que disponga de GC (recolector de basura) no es válido. Incluso Go, que tiene un GC mucho mejor que Java o la plataforma .NET no lo considera correcto.

Con simplicidad se busca legibilidad y potencia. El lenguaje debe ser uniforme, con cohesión en todos sus elementos.

Y con diseñado para buenos programadores se refiere a que el lenguaje no debe suponer que el programador es idiota e intentar corregir sus errores. Debe permitir hacer virguerías si así lo desea el programador. Rust está bien pues permite tener código unsafe. Justo lo que se necesita para hacer virguerías. Pero hace falta más trabajo en este aspecto pues supone un cambio de mentalidad.

La idea detrás de RAII es incorrecta

Mucha gente opina que RAII es una de las mejores cosas que han pasado en la programación. Muchos lenguajes presuponen RAII. D por ejemplo considera que RAII es la manera correcta de programar. Resource Acquisition Is Initialization consiste en que cada vez que vamos a acceder a un recurso tenemos que codificarlo en una clase, inicializar el recurso en un constructor y liberar el recurso en un destructor. Añades operadores para permitir copia, … Este sistema presenta una elevada fricción. Y además no funciona bien, en el sentido de que todo se acaba sobrecomplicando. Alejándose de esa simplicidad que estamos buscando.

Uno de los principales problemas de este patrón de diseño es que no existe un recurso. Es una generalización errónea de varios conceptos. Un recurso puede ser memoria, otro recurso puede ser un archivo, una entrada de teclado, etc El problema es que estos recursos son demasiado diferentes como para ser tratados con un mismo patrón de forma óptima. Mientras RAII puede ser interesante hablando de archivos, es una muy mala opción si hablamos de memoria. Porque la memoria es el recurso más importante para un programador. Se podría simplificar diciendo que un programador lo único que hace es modificar la memoria constantemente.

Pero muchos de los usos de RAII tienen que ver con las excepciones. Y a Blow tampoco le gustan las excepciones. La gestión de errores en C es pésima pero las excepciones son muy complejas. Una de las cosas más complejas que implementan los lenguajes de programación que disponen de ellas. Y la implementación de C++ más todavía. Blow se lamenta de que haya gente que siga pensando que es una buena idea. Reduce la claridad del código, complica el flujo del programa. RAII en C++ ayuda a que en caso de que se de una excepción los recursos puedan ser liberados.

No solo lo dice él, sino que enlaza el siguiente vídeo: Systematic Error Handling in C++ por Andrei Alexandrescu.


Un mejor sistema que las excepciones

Go implementa múltiples valores de retorno (al contrario que la mayoría de lenguajes derivados de C donde solo de devuelve una cosa). Go lo soporta de forma nativa. Pero Matt Newport le responde como podría hacer eso en C++11 con std::tie.

#include <iostream>;
#include <tuple>;
#include <functional>;
 
std::tuple<int, int> f()
{
    int x = 5;
    return std::make_tuple(x, 7); // return {x,7}; en C++17
}
 
int main()
{
    int a, b;
    std::tie(a, b) = f();
    std::cout << a << " " << b << "\n";
}

Rust, como Go, soporta esto de forma nativa:

fn f() -> (i32,i32) {
	(4,7)
}

fn main() -> () {
	let (a,b) = f();
	println!("A es {}, B es {}",a,b);
}

Aunque no es la manera en la que Rust maneja los errores. En su lugar posee Option y Result que en C++17 también van a ser implementados en std::optional y que es en realidad un concepto presente en Haskell.

Sintaxis exasperante

En la charla Blow sigue hablando y comenta que desde un punto de visto purista y correcto la sintaxis de punteros de C++11 es incorrecta. Que std::unique_ptr<Vector3[]> implica que quieres un Unique Ptr basado en Vector3 pero en realidad la idea correcta sería quiero un Vector3 con características de Unique Ptr. Lo mismo es aplicable para std::shared_ptr. Este tipo de punteros no deberían estar expresados de esta forma sino que deberían entrar en la sintaxis del lenguaje, por su utilidad práctica.

En Rust, el equivalente a std::unique_ptr sería Box que es el puntero más usado. El equivalente a std::shared_ptr sería Rc, no tan usado pero disponible.

Blow sigue hablando en este vídeo y en el siguiente de más errores que tiene C++, aunque de menor importancia. En todo caso, Blow sigue en el desarrollo de su compilador de Jai. C++ ha avanzado un montón y me ha llegado a sorprender que existiesen cosas como constexpr y los módulos de C++, una solución a los archivos de cabecera que son tan tediosos de escribir.

Si tenéis tiempo y sabéis inglés miráos el vídeo original. Creo que esta mucho mejor explicado que esto. Y también la respuesta de Matt Newport en la que dice que C++ SÍ es válido para todo lo que dice Blow.

Mozilla desiste, Rust será sustituido por Jai

La lucha por un nuevo lenguaje de programación que sustituya a C comenzó hace años con los siguientes contendientes: C++14, Go, Rust y D. Finalmente Mozilla ha abandonado el desarrollo del lenguaje de programación Rust. Se ha decidido, no con polémica, iniciar una transición hacia Jai, el lenguaje de programación diseñado por Jonathan Blow.

Rust

Muchos conocerán a Jonathan Blow por ser el creador de juegos como Braid o The Witness, pero además, lleva trabajando en Jai desde 2014.

Jai actualmente es un lenguaje que transpila a C++, para posteriormente ser compilado por un compilador normal de C++. La idea detrás de Jai es ser el lenguaje de programación de videojuegos. Tras haber trabajado muchos años con C++, Blow ha diseñado un lenguaje que mejore las partes malas de C, conservando sus partes buenas. Blow criticó al lenguaje Rust asegurando que usar Rust era como la casa que tiene sus esquinas plastificadas pero luego te deja una pistola cargada sobre la mesa.

Brendan Eich, creador de JavaScript ha declarado:

En el fondo sabíamos que podría pasar. En Mozilla solo sabemos diseñar lenguajes horribles. De hecho a mí me dejaron hacer JavaScript y ya sabemos lo que pasó

Ahora Mozilla se ha acercado a Blow para aprender más sobre Jai, un lenguaje mucho más práctico. Jai permite al programador hacer lo que quiera, pero opcionalmente y a través de syntactic sugar es optimizado y analizado para llegar a mejorar en rendimiento incluso a C.

James Gosling, creador de Java ha dicho:

Nosotros diseñamos Java para que fuese muy difícil escribir código malo. Cualquier mejora sobre eso se merece mis aplausos, lástima que no se oigan porque la JVM todavía está arrancando.

A día de hoy no existen compiladores de Jai disponibles al público y la única copia se cree que está en manos de Jonathan Blow. La única documentación existente se encuentra recopilada en GitHub por fans de Jai.

Definitivamente este año no ha sido muy bueno para Mozilla, que ya ha tenido que abandonar otros proyectos como Firefox OS o Matchstick, que prometían innovación pero que por A o por B no han sabido llegar a buen puerto.

ACTUALIZACIÓN: ¡Feliz día de los inocentes!

Programando para Haiku – Barras de menús – Parte III

Seguimos con los tutoriales de Haiku, hoy veremos como crear barras de menú. Partimos del código de la ventana vacía, lo tienes en la foto junto con el comando de compilación.

MiAplicacionHaikuCodigo
Haz click en la imagen para verlo en grande

Debemos saber que en la API de BeOS hay tres clases que nos van a servir.

  • BMenuBar, se trata de la barra en sí, que esta pegada a la ventana. Un BMenuBar contiene BMenus.
  • BMenu, son los menús en sí. Un menú es un elemento que contiene acciones, organizadas como si fuera una lista. Los menús no definen acciones, pero sí su organización. Así, a un BMenu le tendremos que añadir BMenuItems u otro BMenus (menús anidados).
  • BMenuItem, son las acciones de los menús. Cada BMenuItem tiene la capacidad de generar un BMessage nuevo, listo para ser procesado por nuestra aplicación.

En la imagen se puede ver perfectamente

BMenu

Vamos a crear una barra de menú con dos menús, uno de ellos simple y otro con un menú anidado y un separador.

#include <AppKit.h>
#include <InterfaceKit.h>
#include <InterfaceDefs.h>

#define NEW_FILE 3
#define EXPORT_AS 5

class VentanaPrueba : public BWindow{
	public:
		VentanaPrueba() : BWindow(BRect(100,100,900,700),"Mi ventana",B_TITLED_WINDOW,0){
			AddChild(CreateMenuBar());
		}
		bool QuitRequested(){
			be_app_messenger.SendMessage(B_QUIT_REQUESTED);
			return BWindow::QuitRequested();
		}
		void MessageReceived(BMessage* msg){
			switch(msg->what){
				default:
					BWindow::MessageReceived(msg);
			}
		}
		BMenuBar* CreateMenuBar(){
			BMenuBar* bar = new BMenuBar(BRect(0,0,100,20),"MenuBar");
			
			BMenu* file = new BMenu("File");
			BMenu* help = new BMenu("Help");
			BMenu* exportMenu = new BMenu("Export");
			
			bar->AddItem(file);
			bar->AddItem(help);
			
			/* FILE */
			BMenuItem* newFile = new BMenuItem("New file",new BMessage(NEW_FILE));
			newFile->SetShortcut('N',B_COMMAND_KEY);
			file->AddItem(newFile);
			
			file->AddItem(exportMenu);
			file->AddSeparatorItem();
			
			BMenuItem* quit = new BMenuItem("Quit",new BMessage(B_QUIT_REQUESTED));
			quit->SetShortcut('Q',B_COMMAND_KEY);
			file->AddItem(quit);
			
			/* EXPORT */
			BMenuItem* exportAs = new BMenuItem("Export as...",new BMessage(EXPORT_AS));
			exportMenu->AddItem(exportAs);
			
			/* HELP */
			BMenuItem* helpVersion = new BMenuItem("Help",NULL);
			help->AddItem(helpVersion);
			
			return bar;
		}
};

class AplicacionPrueba : public BApplication{
	public:
			VentanaPrueba* ventana;
			AplicacionPrueba() : BApplication("application/x-aplicacion-prueba"){
				ventana = new VentanaPrueba();
				ventana->Show();	
			}
};

int main(int argc, char** argv){
	AplicacionPrueba app;
	return app.Run();
}

Y el resultado es el siguiente:

BMenuBar

Como vemos, SetShortcut hace que los menús sean seleccionables con combinaciones de teclado. Hay que tener en cuenta que en BeOS, la tecla que en Windows normalmente Ctrl, es Alt. Así operaciones como copiar y pegar con Alt+C y Alt+V. Para responder al evento solo hace falta escuchar en la función MessageReceived. En el caso de B_QUIT_REQUESTED, el mensaje ya está implementado en la función QuitRequested.

Tutorial de WiX (Windows Installer MSI)

Siguiendo la estela del tutorial de CMake hoy traigo el tutorial de WiX.

¿Qué es WiX?

WiX es un proyecto opensource destinado a producir instaladores de la plataforma Windows Installer. Windows Installer es la plataforma de instalaciones preferida por Microsoft desde que comenzó su desarrollo en 1999. Los archivos MSI son los instaladores genéricos que usa Windows Installer. Provee de un entorno más seguro para modificar el sistema al de un EXE tradicional por el hecho de que Windows Installer es declarativo, no imperativo. Windows Installer es transaccional, facilita el despliegue en entornos empresariales, tiene APIs (Windows Installer API y Windows Installer SDK), permite la localización de manera sencilla, la validación de instalaciones y la gestión de reinicios. Tiene los siguientes inconvenientes: es complejo, los accesos directos, su línea de comandos, su uso del registro y sus herramientas. Así pues con WiX podemos crear paquetes MSI de Windows Installer con unos ficheros XML donde definimos la instalación.

WindowsInstaller

También conviene diferenciar los diferentes tipos de archivos que soporta Windows Installer.

  • MSI | Instalador convencional
  • MSM | Módulo de fusión
  • MSP | Módulo de parche
  • MST | Módulo de transformación

No hay que confundir estos archivos con los MSU, también usados por Microsoft pero para Windows Update y cuya estructura es diferente.

Instalando WiX

Para instalar WiX podemos usar Chocolatey. Abrimos PowerShell como administrador y ejecutamos.

choco install -y wixtoolset

Si queremos añadir las herramientas al PATH, en PowerShell.

$PATH = [Environment]::GetEnvironmentVariable("PATH")
$WIX_BIN = "C:\Program Files (x86)\WiX Toolset v3.9\bin"
[Environment]::SetEnvironmentVariable("PATH","$PATH;$WIX_BIN")

Herramientas

WiX viene con un conjunto de herramientas diseñadas para trabajar con Windows Installer.

  • candle.exe – Compila los archivos .wxs y .wxi para generar archivos objeto .wixobj
  • light.exe – Enlaza los objetos .wixobj y .wixlib produciendo el .msi
  • lit.exe – Permite unir archivos .wixobj en una librería .wixlib
  • dark.exe – Convierte un archivo ya compilado en código fuente WiX
  • heat.exe – Podemos añadir archivos al fichero WiX en masa, sin especificar manualmente
  • insignia.exe – Permite firmar archivos MSI con las mismas firmas que los CABs
  • melt.exe – Convierte un archivo MSM a un fichero código fuente de WiX
  • torch.exe – Extrae las diferencias entre objetos WiX para generar una transformación. Usado para generar parches.
  • smoke.exe – Valida ficheros MSI o MSM
  • pyro.exe – Genera un fichero de parches MSP
  • WixCop.exe – Recomienda estándares en los ficheros WiX
  • WixUnit.exe – Realiza una validación a los ficheros WiX
  • lux.exe – Usado para tests unitarios
  • nit.exe – Usado para tests unitarios

Nota: Vamos a necesitar generar GUIDs. En PowerShell podeis usar `[GUID]::NewGuid()` o `[GUID]::NewGuid().ToString()`. Si teneis instalado Visual Studio, también está disponible `uuidgen`

El fichero .wxs

El fichero WXS es la base de WiX y es un fichero XML. Para el ejemplo voy a empaquetar Lumtumo. Lumtumo contiene un ejecutable, varias DLL y varios archivos como imágenes y fuentes que están distribuidos por carpetas. He depositado la carpeta con los binarios y todos lo necesario en SourceDir.

<?xml version="1.0" encoding="utf-8"?>
<!-- Variables del preprocesador -->
<?define Platform = x86 ?>
<?if $(var.Platform) = x64 ?>
<?define ProductName = "Lumtumo (64 bit)" ?>
<?define Win64 = "yes" ?>
<?define PlatformProgramFilesFolder = "ProgramFiles64Folder" ?>
<?else ?>
<?define ProductName = "Lumtumo" ?>
<?define Win64 = "no" ?>
<?define PlatformProgramFilesFolder = "ProgramFilesFolder" ?>
<?endif ?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
	<!-- Códigos de idioma https://msdn.microsoft.com/en-us/library/Aa369771.aspx -->
	<Product Name="Lumtumo" Manufacturer="Adrian Arroyo Calle" Id="BAE7D874-3177-46A6-BB2D-043EFF8C59F2" UpgradeCode="457861BD-A051-4150-8752-82907E7BAF19" Language="1033" Codepage="1252" Version="1.0">
   <!-- Nunca, NUNCA, se debe cambiar el valor de UpgradeCode. Es el identificador que distingue a los productos entre versiones -->
    <Package Description="$(var.ProductName)" Platform="$(var.Platform)" Keywords="Game" Id="*" Compressed="yes" InstallScope="perMachine" InstallerVersion="300" Languages="1033" SummaryCodepage="1252" Comments="Space invaders redefined" Manufacturer="Adrian Arroyo Calle"/>
	  <!-- Generamos el listado de ficheros a instalar con heat.exe dir ..\..\build\Release\ -dr LumtumoDir -cg LumtumoComponent -gg -g1 -sf -srd -out files.wxs -->
    <!-- Es necesario actualizar Shortcut/@Target cada vez que usemos Heat para conservar la referencia al ejecutable principal válida -->
    <!-- Instalar con msiexec /i lumtumo.msi , desinstalar con msiexec /x lumtumo.msi-->
    
        <MajorUpgrade DowngradeErrorMessage="A later version of Lumtumo is already installed. Setup will now exit."/>
    <!-- Gestiona las actualizaciones posteriores. Estos MSI de actualización deben tener igual UpgradeCode, distinto Id y una versión superior-->

    <Icon Id="icon.ico" SourceFile="SourceDir/Lumtumo.exe"/>
    <!-- El icono puede estar en un ejecutable -->
    <Property Id="ARPPRODUCTICON" Value="icon.ico" />
    <!-- Este es el icono del panel de control -->
    <Property Id="ARPHELPLINK" Value="http://adrianarroyocalle.github.io" />
    <!-- Enlace de ayuda para el panel de control. Hay más propiedades con enlaces para el panel de control-->

    <MediaTemplate CabinetTemplate="LUM{0}.cab" CompressionLevel="high" EmbedCab="yes"/>
    <!-- WiX se encargará de generar los archivos Cabinet necesarios. Esto será cuando llamemos a light.exe -->
    
    <Feature Id="ProductFeature" Title="Game" Level="1">
      <ComponentGroupRef Id="LumtumoComponent"/>
      <ComponentRef Id="ApplicationShortcut" />
    </Feature>

    <!-- Solo hay una funcionalidad, el juego completo. Esta incluye todos los archivos de Lumtumo y además el acceso directo en el menú de Inicio. LumtumoComponent está definido en files.wxs -->

    <Directory Id='TARGETDIR' Name='SourceDir'>
      <Directory Id='$(var.PlatformProgramFilesFolder)'>
        <Directory Id="LumtumoDir" Name='Lumtumo'>
        </Directory>
      </Directory>
      <Directory Id="ProgramMenuFolder">
        <Directory Id="ApplicationProgramsFolder" Name="Lumtumo"/>
      </Directory>
    </Directory>
    <!-- La estructura de carpetas básica. TARGETDIR debe ser el ROOT siempre. Además, ProgramFilesFolder (en este caso usamos el preprocesador antes) y ProgramMenuFolder ya están definidos por Windows. Así que realmente creamos LumtumoDir (que usa files.wxs) y ApplicationProgramsFolder (justo abajo trabajamos con esa carpeta). Ambas se crean con el nombre de Lumtumo en el sistema de archivos -->

    <DirectoryRef Id="ApplicationProgramsFolder">
      <Component Id="ApplicationShortcut" Guid="03466DA8-7C3F-485E-85E6-D892E9F7FFE4">
        <Shortcut Id="ApplicationStartMenuShortcut" Name="Lumtumo" Description="Space Invaders redefined" Target="[#fil7BBA1E2E293173E87EC3F765BF048B16]" Icon="icon.ico" WorkingDirectory="LumtumoDir"/>
        <RemoveFolder Id="ApplicationProgramsFolder" On="uninstall"/>
        <!-- Nos aseguramos de borrar la carpeta con el acceso directo al desinstalar-->
        <RegistryValue Root="HKCU" Key="Software\Microsoft\Lumtumo" Name="installed" Type="integer" Value="1" KeyPath="yes"/>
        <!-- Con esta llave del registro apareceremos en el panel de control para ser desinstalados-->
      </Component>
    </DirectoryRef>

    <Condition Message='This application only runs on Windows NT'>
      VersionNT
    </Condition>
    <!-- Un ejemplo de condiciones. En este caso pedimos que Windows tenga el núcleo NT. Se pueden especificar rangos de versiones de Windows e incluso las ediciones. Las condiciones se pueden aplicar a componentes específicos también-->
    
  </Product>
</Wix>

Ahora se genera el archivo MSI

heat.exe dir SourceDir -dr LumtumoDir -cg LumtumoComponent -gg -g1 -sf -srd -out files.wxs
# Editamos el archivo Lumtumo.wxs para actualizar la referencia al ejecutable principal en el acceso directo
candle.exe files.wxs
candle.exe Lumtumo.wxs
light.exe files.wixobj Lumtumo.wixobj -out lumtumo.msi

Este ejemplo básico está muy bien, instala todo, añade un icono en el menú de inicio (también se puede poner en el escritorio pero no me gusta), se integra con el panel de control para hacer una desinstalación limpia e incluso con modificar un valor podemos generar el paquete para 64 bits. Sin embargo faltan cosas. No se puede especificar la carpeta de instalación, de hecho, el MSI no muestra ninguna opción al usuario al instalarse. Para introducir GUI en WiX tenemos que usar la librería WiXUI.

WixUI

Para usar WixUI tenemos que cambiar el comando que usamos para llamar a light

light.exe -ext WixUIExtension files.wixobj Lumtumo.wixobj -out lumtumo.msi

Con esto ya podemos usar la librería WixUI. La manera más básica de añadir una GUI es introducir la GUI Minimal. Al final de la etiqueta Product podemos insertar

    <WixVariable Id="WixUILicenseRtf" Value="LICENSE.rtf" />
    <UIRef Id="WixUI_Minimal" />
    <UIRef Id="WixUI_ErrorProgressText" />

Y LICENSE.rtf en un archivo que tenemos junto a los ficheros WXS.

Si queremos un buen resultado visualmente hablando toca diseñar dos ficheros BMP.

<WixVariable Id="WixUIBannerBmp" Value="path\banner.bmp" /> <!-- 493x58 -->
<WixVariable Id="WixUIDialogBmp" Value="path\dialog.bmp" /> <!-- 493x312 -->

Aquí uso la interfaz Minimal que lo único que hace es pedir aceptar la licencia e instalarse tal como habíamos visto antes. Hay más interfaces: WixUI_Mondo, WixUI_FeatureTree, WixUI_InstallDir y WixUI_Advanced. Todas ellas permiten más ajustes que WixUI_Minimal. También podemos crear ventanas personalizadas pero en mi opinión es algo a evitar. Las instalaciones deben ser lo más simples posibles y dejar a la aplicación en sí toda la configuración que necesite. Respecto a cambiar el directorio de instalación, no soy partidario pero si quereis la cuestión es sustituir LumtumoDir por INSTALLDIR (en hate.exe y en el fichero Lumtumo.wxs) y en la etiqueta Feature añadir `Display=”expand” ConfigurableDirectory=”INSTALLDIR”`

Bundle y Bootstrap

Un archivo MSI está bien pero quizá nos gusté más un EXE que además instale las dependencias.

<?xml version="1.0" encoding="utf-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
  <Bundle Name="Lumtumo" Version="1.0" Manufacturer="Adrián Arroyo Calle" UpgradeCode="AC44218D-B783-4DF9-B441-5AE54394DAA9" AboutUrl="http://adrianarroyocalle.github.io">
    <!-- <BootstrapperApplicationRef Id="WixStandardBootstrapperApplication.RtfLicense" /> -->
    <BootstrapperApplicationRef Id="WixStandardBootstrapperApplication.HyperlinkLicense">
      <bal:WixStandardBootstrapperApplication LicenseUrl="" xmlns:bal="http://schemas.microsoft.com/wix/BalExtension"/>
    </BootstrapperApplicationRef>

    <Chain>
      <ExePackage Id="Dependency1" SourceFile="Dependency_package_1.exe" />
      <ExePackage Id="Dependency2" SourceFile="Dependency_package_2.exe" />

      <RollbackBoundary Id="RollBack" />

      <MsiPackage Id="MainPackage" SourceFile="lumtumo.msi" Vital="yes" />
    </Chain>
  </Bundle>
</Wix>

Y compilamos

candle.exe setup.wxs
light.exe -ext WixBalExtension setup.wxs

Y se genera el archivo `setup.exe`. Por defecto el archivo MSI se instala en modo silencioso por lo que la GUI pasa a ser la de Bootstrap.

Más sintaxis WiX

Registrar un tipo de archivo

<ProgId Id='ACME.xyzfile' Description='Acme Foobar data file' Icon="icon.ico">
    <Extension Id='xyz' ContentType='application/xyz'>
        <Verb Id='open' Command='Open' TargetFile='[#ID_DE_EJECUTABLE]' Argument='"%1"' />
    </Extension>
</ProgId>

Escribir configuración en archivo INI

<IniFile Id="WriteIntoIniFile" Action="addLine" Key="InstallDir" Name="Foobar.ini" Section="Paths" Value="[INSTALLDIR]" />

Escribir en el registro

<RegistryKey Id='FoobarRegInstallDir' Root='HKLM' Key='Software\Acme\Foobar 1.0' Action='createAndRemoveOnUninstall'>
    <RegistryValue Type='string' Name='InstallDir' Value='[INSTALLDIR]'/>
    <RegistryValue Type='integer' Name='Flag' Value='0'/>
</RegistryKey>

Borrar archivos extra en la desinstalación

<RemoveFile Id='LogFile' On='uninstall' Name='Foobar10User.log' />
<RemoveFolder Id='LogFolder' On='uninstall' />