SDK Multiplataforma en C logo

SDK Multiplataforma en C

Crear nueva aplicación

❮ Anterior
Siguiente ❯

Me considero una persona técnica que eligió un gran proyecto y una excelente manera para llevarlo a cabo. Linus Torvalds.


En Compilar NAppGUI hemos visto como compilar y empaquetar el SDK. También, en ¡Hola Mundo!, aprendimos la estructura básica de una aplicación basada en NAppGUI. Ha llegado el momento de crear nuestras propias aplicaciones, aprovechando los módulos CMake incluidos en la carpeta /prj de la instalación.

Este capítulo está enfocado en el uso de CMake. Si utilizas otro build system en tus proyectos, deberás adaptar tu mismo la gestión de dependencias.

1. Uso de find_package()

NAppGUI soporta el comando find_package() de CMake, por lo que gestionar las dependencias es sumamente sencillo. Para crear una nueva aplicación de escritorio, comienza en una nueva carpeta con dos archivos: CMakeLists.txt y main.c:

Nuestra primera aplicación NAppGUI.
1
2
3
4
12/15/23  04:20 PM    <DIR>          .
12/15/23  04:19 PM    <DIR>          ..
12/15/23  04:11 PM               292 CMakeLists.txt
12/15/23  03:57 PM             2,315 main.c
CMakeLists.txt
1
2
3
4
5
6
cmake_minimum_required(VERSION 3.0)
project(NAppHello)
find_package(nappgui REQUIRED)
# Remove WIN32 in Linux and macOS
add_executable(naphello WIN32 main.c)
target_link_libraries(naphello ${NAPPGUI_LIBRARIES})
main.c. Hello World
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
/* NAppGUI Hello World */

#include <nappgui.h>

typedef struct _app_t App;

struct _app_t
{
    Window *window;
    TextView *text;
    uint32_t clicks;
};

/*---------------------------------------------------------------------------*/

static void i_OnButton(App *app, Event *e)
{
    String *msg = str_printf("Button click (%d)\n", app->clicks);
    textview_writef(app->text, tc(msg));
    str_destroy(&msg);
    app->clicks += 1;
    unref(e);
}

/*---------------------------------------------------------------------------*/

static Panel *i_panel(App *app)
{
    Panel *panel = panel_create();
    Layout *layout = layout_create(1, 3);
    Label *label = label_create();
    Button *button = button_push();
    TextView *text = textview_create();
    app->text = text;
    label_text(label, "Hello!, I'm a label");
    button_text(button, "Click Me!");
    button_OnClick(button, listener(app, i_OnButton, App));
    layout_label(layout, label, 0, 0);
    layout_button(layout, button, 0, 1);
    layout_textview(layout, text, 0, 2);
    layout_hsize(layout, 0, 250);
    layout_vsize(layout, 2, 100);
    layout_margin(layout, 5);
    layout_vmargin(layout, 0, 5);
    layout_vmargin(layout, 1, 5);
    panel_layout(panel, layout);
    return panel;
}

/*---------------------------------------------------------------------------*/

static void i_OnClose(App *app, Event *e)
{
    osapp_finish();
    unref(app);
    unref(e);
}

/*---------------------------------------------------------------------------*/

static App *i_create(void)
{
    App *app = heap_new0(App);
    Panel *panel = i_panel(app);
    app->window = window_create(ekWINDOW_STD);
    window_panel(app->window, panel);
    window_title(app->window, "Hello, World!");
    window_origin(app->window, v2df(500, 200));
    window_OnClose(app->window, listener(app, i_OnClose, App));
    window_show(app->window);
    return app;
}

/*---------------------------------------------------------------------------*/

static void i_destroy(App **app)
{
    window_destroy(&(*app)->window);
    heap_delete(app, App);
}

/*---------------------------------------------------------------------------*/

#include "osmain.h"
osmain(i_create, i_destroy, "", App)

Ya puedes generar y compilar la solución, utilizando CMake de la forma habitual. Si instalaste NAppGUI cmake --install en una ubicación específica (parámetro --prefix) deberás indicar la misma ruta mediante -DCMAKE_INSTALL_PREFIX.

 
cmake -S . -B build -DCMAKE_INSTALL_PREFIX=C:/nappgui
cmake --build build

El comando find_package() sabe como localizar un paquete dentro de los directorios habituales del sistema, en función de cada plataforma. Necesitaremos especificar el prefijo únicamente cuando el paquete esté instalado en cualquier directorio alternativo.

-DCMAKE_INSTALL_PREFIX no implica prioridad en la búsqueda. find_package() podría encontrar primero una instalación en las carpetas del sistema.

En el directorio /build/Debug tendrás el ejecutable napphello (Figura 1).

Aplicación NAppGUI Hello World.
Figura 1: Resultado de compilar y ejecutar napphello.

2. NAppProject.cmake

Si bien puedes gestionar tu mismo el CMakeLists.txt de tu proyecto, configurar una aplicación de escritorio multiplataforma puede resultar algo tedioso (incluso para CMake). NAppGUI proporciona una serie de módulos dentro del directorio /prj de la instalación, que pueden simplificar esta tarea. Para probarlo, crea una nueva carpeta y añade este único fichero CMakeLists.txt:

CMakeLists.txt
1
2
3
4
5
cmake_minimum_required(VERSION 3.0)
project(NAppHello)
find_package(nappgui REQUIRED)
include("${NAPPGUI_ROOT_PATH}/prj/NAppProject.cmake")
nap_project_desktop_app(napphello hello)

Ejecutamos de CMake de la misma manera que en el caso anterior:

 
cmake -S . -B build -DCMAKE_INSTALL_PREFIX=C:/nappgui
cmake --build build

En este caso hemos utilizado la función nap_project_desktop_app() del módulo NAppProject.cmake, que ha creado una nueva carpeta /hello. Abrimos la solución de Visual Studio que se ha generado en /build (Figura 2).

Captura del explorador de soluciones de VisualStudio.
Figura 2: Solución creada por NAppProject.cmake.
 
nap_project_desktop_app(appName path)
  • appName: El nombre de la aplicación.
  • path: Ruta relativa al CMakeLists.txt donde se ubicará el proyecto (en este caso ./hello). Se admite cualquier profundidad de ruta. Por ejemplo: games/myapp, demo/games/myapp, etc.

La primera vez que se ejecuta esta función se realizan varias cosas:

  • Se ha creado un nuevo directorio hello con una aplicación de escritorio por defecto napphello.c y un CMakeLists.txt.
  • Se ha creado una carpeta hello/res con una imagen, y se ha utilizado como icono de la aplicación (Figura 3). En Recursos seguiremos profundizando.
  • El /hello/CMakeLists.txt recién creado, ha enlazado automáticamente con los binarios de NAppGUI.

Sucesivas llamadas a CMake no sobrescribirán los archivos del proyecto, por lo que podemos editarlos sin temor a perder los cambios. Una vez creado el proyecto, nap_project_desktop_app() se limitará a llamar a add_subdirectory(). El comando nap_desktop_app() del /hello/CMakeLists.txt sabe como manejar las particularidades entre plataformas. Por ejemplo, en caso de macOS creará un bundle en lugar de un ejecutable aislado.

Aplicación NAppGUI Hello World.
Figura 3: NAppHello con icono de aplicación.

No tenemos porque limitarnos a una sola aplicación. Nuestra solución admitirá numerosos targets. Por ejemplo añade esta línea al CMakeLists.txt y vuelve a lanzar cmake -S . -B build.

CMakeLists.txt
1
2
3
4
5
6
cmake_minimum_required(VERSION 3.0)
project(NAppHello)
find_package(nappgui REQUIRED)
include("${NAPPGUI_ROOT_PATH}/prj/NAppProject.cmake")
nap_project_desktop_app(napphello hello)
nap_project_desktop_app(nappbye bye)

En caso de que la solución ya estuviese abierta, es posible que el IDE te avise que han habido cambios (Figura 4). Tras pulsar [Reload], verás que ha aparecido el nuevo proyecto (Figura 5).

Advertencia que muestra Visual Studio, al detectar que se han añadido nuevos archivos.
Figura 4: Aviso de cambios en Visual Studio.
Captura del explorador de soluciones de VisualStudio con nuevos cambios en la solución.
Figura 5: Actualización de la solución, con el nuevo proyecto nappbye.

3. Añadir archivos

Volviendo al proyecto napphello, vemos que por defecto solo se crea un archivo de código fuente (napphello.c) que contiene toda la aplicación. Es muy probable que quieras dividir el código entre diferentes archivos. Crea un par de nuevos archivos /hello/myfunc.c y /hello/myfunc.h desde el IDE o directamente desde el explorador. Ábrelos y añade estas líneas:

/hello/myfunc.h
1
2
3
4
5
// Example of new header

#include <core/core.hxx>

real32_t myadd_func(real32_t a, real32_t b);
/hello/myfunc.c
1
2
3
4
5
6
7
8
// Example of new c file

#include "myfunc.h"

real32_t myadd_func(real32_t a, real32_t b)
{
    return a + b;
}

Abre /hello/napphello.c y edita la función i_OnButton.

/hello/napphello.c
1
2
3
4
5
6
7
8
9
...
static void i_OnButton(App *app, Event *e)
{
    real32_t res = myadd_func(56.4f, 23.3f);
    textview_printf(app->text, "Button click (%d-%.2f)\n", app->clicks, res);
    app->clicks += 1;
    unref(e);
}
...

Vuelve a re-generar la solución con cmake -S . -B build. El IDE, Visual Studio en este caso, nos vuelve a informar que han habido cambios en el proyecto napphello. Simplemente presiona [Reload All] como ya hicimos en el caso anterior.

Vuelve a compilar y ejecutar napphello para ver los cambios que acabas de realizar. Puedes crear tantos archivos y subcarpetas dentro del directorio /hello como necesites para organizar mejor tu código. Recuerda siempre ejecutar cmake -S . -B build cada vez que añadas o elimines archivos del proyecto. El comando nap_desktop_app() actualizará la solución "clonando" la estructura de directorios dentro del proyecto (napphello en este caso).

Llegados a este punto te recomendamos que dediques algo de tiempo a investigar, compilar y probar los ejemplos de la carpeta demo dentro del repositorio de NAppGUI.

4. Aplicaciones por línea de comandos

De forma similar a las aplicaciones de escritorio vistas anteriormente, es posible crear aplicaciones de consola. Añade esta nueva línea al CMakeLists.txt de la solución.

CMakeLists.txt
1
2
3
4
5
6
7
cmake_minimum_required(VERSION 3.0)
project(NAppHello)
find_package(nappgui REQUIRED)
include("${NAPPGUI_ROOT_PATH}/prj/NAppProject.cmake")
nap_project_desktop_app(napphello hello)
nap_project_desktop_app(nappbye bye)
nap_project_command_app(myutil utils/myutil)

Al regenerar la solución con cmake -S . -B build, Visual Studio te volverá a alertar que debes recargar la solución. Se habrá creado un nuevo proyecto en ./utils/myutil (Figura 6), pero esta vez si lo compilas y ejecutas no aparecerá ninguna ventana. Tan solo verás un mensaje en la consola de Visual Studio:

1
Hello world!
Captura del explorador de soluciones de VisualStudio con tres proyectos.
Figura 6: Solución con los tres ejecutables (targets).

Si abres myutil.c encontrarás el código que ha generado la salida anterior:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
/* NAppGUI Console Application */

#include <core/coreall.h>

int main(int argc, char *argv[])
{
    unref(argc);
    unref(argv);
    core_start();
    bstd_printf("Hello world!\n");
    core_finish();
    return 0;
}

Que es la típica plantilla de un programa en C, a la que se le ha incluido el soporte de la librería core. A partir de aquí, ya podemos modificar el código y compilar. nap_command_app() ya lo configuró todo por nosotros.

 
nap_project_command_app(appName path)
  • appName: El nombre de la aplicación.
  • path: Ruta relativa a . donde se ubicará el proyecto (en este caso ./utils/myutil).

Ni que decir tiene que el comportamiento de nap_project_command_app() es idéntico al de nap_project_desktop_app(). No sobrescribirá los archivos del proyecto una vez creado e integrará todos los nuevos archivos que añadamos en un futuro.


5. Uso de librerías

Imaginemos que nuestras tres aplicaciones necesitaran compartir ciertas funcionalidades. Lo más inteligente sería encapsular estas funciones en una librería y que las tres tuvieran acceso a ellas. Esto lo lograremos insertando una nueva línea en nuestro CMakeLists.txt:

CMakeLists.txt
1
2
3
4
5
6
7
8
cmake_minimum_required(VERSION 3.0)
project(NAppHello)
find_package(nappgui REQUIRED)
include("${NAPPGUI_ROOT_PATH}/prj/NAppProject.cmake")
nap_project_library(common common)
nap_project_desktop_app(napphello hello)
nap_project_desktop_app(nappbye bye)
nap_project_command_app(myutil utils/myutil)

Observa que el comando que hemos insertado nap_project_library() precede a las aplicaciones. Esto se debe a que CMake necesita procesar las dependencias antes que los proyectos que las utilizan.

 
nap_project_library(libName path)
  • libName: Nombre de la librería.
  • path: Ruta relativa a CMakeLists.txt donde se ubicará el proyecto (en este caso ./common).

Al igual que con los proyectos de aplicaciones, la primera vez que se ejecuta nap_project_library(), se crean una serie de archivos predeterminados. Posteriormente se podrán editar, eliminar o añadir más como acabamos de ver en el caso de las aplicaciones:

  • common.def: Archivo que define la macro _common_api necesaria para la exportación de símbolos. Más información en Símbolos y visibilidad.
  • common.hxx: Aquí incluiremos las definiciones de tipos públicos, como enum, struct. Por el momento, common no contiene tipos públicos.
  • common.h: Archivo de cabecera. Aquí escribiremos las declaraciones de funciones generales de la librería. De forma predeterminada, CMake crea dos: common_start() y common_finish(), donde implementaríamos, si fuera necesario, el código de inicio y finalización de la librería.
  • common.c: Implementación de funciones generales.
  • CMakeLists.txt: Donde se utiliza el comando nap_library(), análogo a nap_desktop_app() que manejará las particularidades de cada plataforma.

Abre common.h y common.c, agregando una nueva función:

common.h
1
_common_api uint32_t common_add(uint32_t a, uint32_t b);
common.c
1
2
3
4
uint32_t common_add(uint32_t a, uint32_t b)
{
    return a + b;
}

Edita el comando nap_desktop_app() en /hello/CMakeLists.txt, e incluye la dependencia con common:

/hello/CMakeLists.txt
1
nap_desktop_app(napphello "common" NRC_NONE)

Ejecuta cmake -S -B build nuevamente para que todos los cambios surtan efecto. Ahora ya es posible utilizar la nueva función common_add dentro de napphello.c:

napphello.c
1
2
3
4
5
6
7
static void i_OnButton(App *app, Event *e)
{
    uint32_t r = common_add(100, 200);
    textview_printf(app->text, "Button click (%d-%d)\n", app->clicks, r);
    app->clicks += 1;
    unref(e);
}

Puedes crear tantas librerías como tu proyecto necesite. Lo único que debes tener en cuenta es incluir el nombre de la nueva dependencia en los comandos nap_desktop_app(), nap_command_app() o nap_library() de cada target. En Crear nueva librería seguiremos profundizando en el uso de las librerías.


6. Estándar C/C++

Por lo general, los compiladores permiten comprobar que el código se ajuste a ciertos estándares de C/C++, emitiendo advertencias o errores cuando no sea así. En pro de la portabilidad, todos los proyectos generados por nap_desktop_app(), nap_command_app() y nap_library() establecen los estándares más antiguos (C90 y C++98 respectivamente). Es posible que quieras utilizar estándares más modernos en tus proyectos. Abre /hello/CMakeLists.txt y añade estas dos líneas:

1
2
3
4
nap_desktop_app(napphello "" NRC_NONE)
nap_target_c_standard(napphello 11)
nap_target_cxx_standard(napphello 14)
target_link_libraries(napphello ${NAPPGUI_LIBRARIES})

El comando nap_target_c_standard() ha establecido el estándar C11 para napphello. De igual forma, nap_target_cxx_standard() ha seleccionado C++14.

  • Estándar C: 90, 99, 11, 17 y 23.
  • Estándar C++: 98, 11, 14, 17, 20, 23 y 26.
Si CMake o el compilador no soportasen el estándar indicado, se establecerá el mayor permitido. Es responsabilidad del programador utilizar los compiladores apropiados al estándar elegido.

7. NAppCompilers.cmake

De igual forma que el módulo NAppProject.cmake nos ayuda a crear y configurar nuestros propios proyectos, NAppCompilers.cmake que hace lo propio con los compiladores. Para utilizarlo en tu proyecto, tan solo deberás añadir estas dos líneas a tu CMakeLists.txt principal.

1
2
3
4
5
6
7
cmake_minimum_required(VERSION 3.0)
project(NAppHello)
find_package(nappgui REQUIRED)
include("${NAPPGUI_ROOT_PATH}/prj/NAppCompilers.cmake")
nap_config_compiler()
include("${NAPPGUI_ROOT_PATH}/prj/NAppProject.cmake")
nap_project_desktop_app(napphello hello)

La función nap_config_compiler() detecta el compilador entre todos los soportados por NAppGUI: MSVC, GCC, Clang y AppleClang, estableciendo las opciones típicas para cada configuración y plataforma. También añade soporte para las diferentes opciones de configuración de CMake que utiliza NAppGUI en sus propias librerías y aplicaciones de ejemplo. Más información en Generadores, compiladores e IDEs.

❮ Anterior
Siguiente ❯