OSApp
Funciones
type* | (*FPtr_app_create (void)) |
void | (*FPtr_app_update (...)) |
uint32_t | (*FPtr_task_main (...)) |
void | (*FPtr_task_update (...)) |
void | (*FPtr_task_end (...)) |
void | osmain (...) |
void | osmain_sync (...) |
void | osapp_finish (void) |
void | osapp_task (...) |
void | osapp_menubar (...) |
void | osapp_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.
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:
- 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 sentenciaexit()
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:
- 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.
- 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 deosmain()
) 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.
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.
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.
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
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. |