SDK Multiplataforma en C logo

SDK Multiplataforma en C

OSApp

❮ Anterior
Siguiente ❯

Funciones

type*(*FPtr_app_create (void))
void(*FPtr_app_update (...))
uint32_t(*FPtr_task_main (...))
void(*FPtr_task_update (...))
void(*FPtr_task_end (...))
voidosmain (...)
voidosmain_sync (...)
voidosapp_finish (void)
voidosapp_task (...)
voidosapp_menubar (...)
voidosapp_open_url (...)

La librería OSApp arranca y gestiona el ciclo de mensajes de una aplicación de escritorio (Figura 1). Si bien la librería Gui podría integrarse en aplicaciones existentes mediante un plugin, si queremos crear una aplicación desde cero, necesitaremos gestionar los eventos que el sistema operativo envía al programa.

  • Utiliza osmain para iniciar una aplicación de escritorio.
  • Utiliza osapp_finish para finalizar una aplicación de escritorio.
  • Árbol de dependencias de la librería osapp.
    Figura 1: Dependencias de OSApp. Ver NAppGUI API.

1. main() y osmain()

La clásica función main es el punto de inicio de cualquier programa C/C++ por línea de comandos (Figura 2). Su funcionamiento no entraña dificultad alguna y puede resumirse en:

Esquema que muestra las fases principales en la ejecución de un programa en C/C++.
Figura 2: Ejecución de una aplicación C de consola.
  • El sistema operativo carga el programa en memoria y llama a la función main() para empezar su ejecución.
  • Las sentencias se van ejecutando de manera secuencial y en el orden en el que están escritas. Dicho orden puede alterarse mediante sentencias de control (for, if, switch, etc) o llamadas a función.
  • Si es necesario realizar entrada/salida, el programa esperará a que termine la comunicación y continuará con la ejecución.
  • Cuando se alcance el final de la función main() o se ejecute una sentencia exit() el programa finalizará y el sistema operativo lo descargará de memoria.

Sin embargo, en aplicaciones de escritorio (dirigidas por eventos), el ciclo de ejecución es un poco más complicado. En esencia, el programa se encuentra continuamente ejecutando un bucle a la espera que el usuario realice alguna acción (Figura 3) (Listado 1). En ¡Hola Mundo! tienes un ejemplo sencillo:

Esquema que muestra las fases principales en la ejecución de un programa de escritorio en C/C++.
Figura 3: Ejecución de una aplicación C de escritorio.
  • El sistema operativo carga el programa en memoria y llama a la función main(). Ahora está encapsulada dentro de la macro osmain que inicia ciertas estructuras necesarias para la captura y gestión de eventos.
  • En el algún momento de este proceso inicial, se llamará al constructor de la aplicación (el primer parámetro de osmain()) que deberá crear el objeto principal. Dado que el programa está continuamente devolviendo el control al sistema operativo, en este objeto se mantendrá el estado de los datos y de las ventanas.
  • Una vez inicializada, la aplicación entrará en un bucle conocido como ciclo de mensajes (Figura 4), permaneciendo a la espera de que el usuario realice alguna acción sobre la interfaz del programa.
  • Esquema que muestra como se implementa el ciclo de mensajes en cada sistema operativo.
    Figura 4: Implementación del ciclo de mensajes.
  • Cuando esto ocurra, el sistema operativo capturará el evento producido y lo enviará a la aplicación.
  • Si la aplicación ha definido un manejador para dicho evento, este será invocado y se ejecutará el código de respuesta. Una aplicación puede recibir cientos de mensajes pero solo responderá a los que considere necesarios, ignorando el resto.
  • Hay un evento especial de salida que es generado al llamar a osapp_finish. Cuando esto ocurre, osmain() empieza a liberar recursos y a preparar una salida limpia. En algún punto se llamará al destructor de la aplicación (segundo parámetro de osmain()) para que haga su parte del trabajo, cerrando posibles archivos abiertos y destruyendo el objeto principal.
  • El sistema operativo descarga la aplicación de la memoria.
  • Los bloques de color rosa son dependientes de plataforma y están implementados dentro de NAppGUI.
  • Los bloques de color naranja son multiplataforma (totalmente portables) y están implementados dentro de la aplicación.
  • Listado 1: Esqueleto elemental de una aplicación de escritorio.
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    
    typedef struct _app_t App;
    struct _app_t
    {
        // Program data
        Window *window;
    };
    
    static App* i_create(void)
    {
        App *app = heap_new(App);
        // Init program data, GUI and Event handlers
        app->window = ...
        return app;
    }
    
    static void i_destroy(App *app)
    {
        // Destroy program data
        window_destroy(&(*app)->window);
        heap_delete(app, App);
    }
    
    osmain(i_create, i_destroy, "", App);
    

2. Aplicaciones síncronas

Cierto tipo de aplicaciones, entre las que se encuentran los videojuegos, reproductores multimedia o simuladores requieren actualizarse a intervalos regulares de tiempo intervenga o no el usuario (Figura 5) (Listado 2). Para estos casos necesitaremos una variante de osmain, que acepte una función de actualización y un intervalo de tiempo. En Bricks tienes un ejemplo.

  • Utiliza osmain_sync para iniciar una aplicación síncrona.
  • Gráfico de eventos en aplicaciones síncronas.
    Figura 5: Eventos en aplicaciones síncronas.
    Listado 2: Esqueleto elemental de una aplicación síncrona.
     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
    
    typedef struct _app_t App;
    struct _app_t
    {
        // Program data
        Window *window;
    };
    
    static App* i_create(void)
    {
        App *app = heap_new(App);
        // Init program data, GUI and Event handlers
        app->window = ...
        return app;
    }
    
    static void i_update(App *app, const real64_t prtime, const real64_t ctime)
    {
        // Update program state every 40ms
    }
    
    static void i_destroy(App *app)
    {
        // Destroy program data
        window_destroy(&(*app)->window);
        heap_delete(app, App);
    }
    
    osmain_sync(0.04, i_create, i_destroy, i_update, "", App);
    

3. Tareas multi-hilo

Tanto las aplicaciones síncronas como asíncronas ejecutan el ciclo de mensajes en un único hilo de la CPU. Esto significa que si, como respuesta a un evento, se debe ejecutar una tarea relativamente lenta la aplicación quedará "congelada" hasta que esta termine (Figura 6) (a). Esto producirá un efecto indeseado ya que el programa no responderá durante unos segundos, dando la impresión que se ha bloqueado. La solución es lanzar una tarea en paralelo (Figura 6) (b) (Listado 3) que libere rápidamente el hilo que gestiona el GUI. En Login multi-hilo tienes un ejemplo del uso de tareas.

  • Utiliza osapp_task para lanzar una nueva tarea en un hilo paralelo.
  • Gráfico que compara código en un hilo o varios hilos.
    Figura 6: (a) Bloqueo de la interfaz por una función lenta. (b) Función lenta en un hilo paralelo.
    Listado 3: Nueva tarea en un hilo paralelo.
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    // Runs in new thread
    static uint32_t i_task_main(TaskData *data)
    {
        // Do the task work here!
    }
    
    // Runs in GUI thread
    static void i_task_update(TaskData *data)
    {
        // Update the GUI here!
    }
    
    // Runs in GUI thread
    static void i_task_end(TaskData *data, const uint32_t rvalue)
    {
        // Finish task code here!
    }
    
    osapp_task(tdata, .04, i_task_main, i_task_update, i_task_end, TaskData);
    

El nuevo hilo comenzará su ejecución en task_main. Esta función no debería acceder a los elementos de la interfaz, tan solo realizar cálculos o tareas de entrada/salida. Si fuera necesario actualizar el GUI mientras dure la tarea (incrementando una barra de progreso o similar) deberá realizarse en task_update, indicando en updtime el intervalo de actualización. El nuevo hilo terminará cuando se retorne de task_main, momento que se llamará a task_end en el hilo principal. Evidentemente, si ambos hilos acceden a variables compartidas, deberán protegerse mediante un Mutex.


FPtr_app_create

Prototipo de constructor de una aplicación.

type*
(*FPtr_app_create)(void);

Retorna

Objeto aplicación.


FPtr_app_update

Prototipo de función de actualización de una aplicación síncrona.

void
(*FPtr_app_update)(type *app,
                   const real64_t prtime,
                   const real64_t ctime);
app

Objeto aplicación.

prtime

Tiempo de la actualización anterior.

ctime

Tiempo actual.


FPtr_task_main

Prototipo de función de inicio de una tarea.

uint32_t
(*FPtr_task_main)(type *data);
data

Datos iniciales de la tarea.

Retorna

Valor de retorno de la tarea.


FPtr_task_update

Prototipo de función de actualización de una tarea.

void
(*FPtr_task_update)(type *data);
data

Datos de la tarea.


FPtr_task_end

Prototipo de función de finalización de una tarea.

void
(*FPtr_task_end)(type *data,
                 const uint32_t rvalue);
data

Datos de la tarea.

rvalue

Valor de retorno de la tarea.


osmain ()

Inicia una aplicación de escritorio.

void
osmain(FPtr_app_create func_create,
       FPtr_destroy func_destroy,
       const char_t *options,
       type);
func_create

Constructor del objeto aplicación.

func_destroy

Destructor del objeto aplicación.

options

Cadena de opciones.

type

Tipo de objeto aplicación.

Observaciones

En ¡Hola Mundo! tienes un ejemplo sencillo de aplicación de escritorio.


osmain_sync ()

Inicia una aplicación de escritorio síncrona.

void
osmain_sync(const real64_t lframe,
            FPtr_app_create func_create,
            FPtr_destroy func_destroy,
            FPtr_app_update func_update,
            const char_t *options,
            type);
lframe

Tiempo en segundos del intervalo de actualización (0.04 = 25 fps).

func_create

Constructor del objeto aplicación.

func_destroy

Destructor del objeto aplicación.

func_update

Función que se llamará en cada intervalo de actualización.

options

Cadena de opciones.

type

Tipo de objeto aplicación.

Observaciones

Ver Aplicaciones síncronas.


osapp_finish ()

Finaliza una aplicación de escritorio, destruyendo el ciclo de mensajes y el objeto aplicación.

void
osapp_finish(void);

osapp_task ()

Lanza una tarea en paralelo, evitando el bloqueo del hilo que controla la interfaz de usuario.

void
osapp_task(type *data,
           const real32_t updtime,
           FPtr_task_main func_main,
           FPtr_task_update func_update,
           FPtr_task_end func_end,
           type);
data

Datos iniciales de la tarea.

updtime

Tiempo del intervalo de actualización, en caso que lo requiera.

func_main

Función de inicio de la tarea.

func_update

Función de actualización de la tarea.

func_end

Función que se llamará al acabar la tarea.

type

Tipo de los datos iniciales de la tarea.

Observaciones

Ver Tareas multi-hilo.


osapp_menubar ()

Establece la barra de menú general de la aplicación.

void
osapp_menubar(Menu *menu,
              Window *window);
menu

El menú.

window

La ventana que albergará el menú.

Observaciones

En macOS el menú de aplicación no se vincula con ninguna ventana.


osapp_open_url ()

Abre una dirección de Internet utilizando el navegador por defecto del sistema operativo.

void
osapp_open_url(const char_t *url);
url

La dirección URL.

❮ Anterior
Siguiente ❯