KDE neon con Plasma 5.8 — okubax, License CC-BY-2.0

Aplicaciones de consola con Qt

Jesús Torres
Jesús Torres
Published in
5 min readApr 11, 2013

--

Puedes leer la última versión actualizada y corregida del artículo original en mi sitio web: jesustorres.es.

Qt es un framework utilizado fundamentalmente para desarrollar aplicaciones con interfaz gráfica. Sin embargo nada impide que también sea utilizado para crear aplicaciones de linea de comandos.

QCoreApplication

Al crear un proyecto de aplicación para consola, el asistente de Qt Creator crea un archivo main.cpp con un contenido similar al siguiente:

#include <QCoreApplication>int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
return a.exec();
}

QCoreApplication es una clase que provee un bucle de mensaje para aplicaciones de consola, mientras que para aplicaciones gráficas lo adecuado es usar QApplication. El bucle de mensajes es iniciado con la invocación del método QCoreApplication::exec(), que no retorna hasta que la aplicación finaliza. Por ejemplo cuando el método QCoreApplication::quit() es llamado.

Si la aplicación no necesita de un bucle de mensajes, no es necesario instanciar QCoreApplication, pudiendo desarrollarla como cualquier programa convencional en C++, sólo que beneficiándonos de las facilidades ofrecidas por las clases de Qt.

Las clases de Qt que requieren disponer de un bucle de mensajes son:

  • Controles, ventanas y en general todas las relacionadas con el GUI.
  • Temporizadores.
  • Comunicación entre hilos mediante señales.
  • Red. Si se usan los métodos síncronos waitFor* se puede evitar el uso del bucle de mensajes, pero hay que tener en cuenta que las clases de comunicaciones de alto nivel (QHttp, QFtp, etc.) no ofrecen dicho API.

Entrada estándar

Muchas aplicaciones de consola interactúan con el usuario a través de la entrada estándar, para lo cual se pueden usar tanto las clases de la librería estándar de C++:

std::string line;
std::getline(std::cint, line)

como los flujos de Qt:

QTextStream qtin(stdin);
QString line = qtin.readLine();

Sin embargo es necesario tener presente que en ambos casos el hilo principal se puede bloquear durante la lectura —hasta que hayan datos que leer— lo que impediría la ejecución del bucle de mensajes.

Para evitarlo se puede delegar la lectura de la entrada estándar a otro hilo, que se comunicaría con el principal para informar de las acciones del usuario a través del mecanismo de señales y slots de Qt. El procedimiento sería muy similar al que comentamos en una entrada anterior, sólo que para leer la entrada estándar en lugar de para ordenar un vector de enteros.

Usando manejadores de señales POSIX con Qt

Los sistemas operativos compatibles con el estándar POSIX implementan un tipo de interrupción por software conocida como señales POSIX, que son enviadas a los procesos para informar de situaciones excepcionales durante la ejecución del programa. El problema es que las señales POSIX pueden llegar en cualquier momento, interrumpiendo así la secuencia normal de ejecución de instrucciones del proceso, lo que puede introducir problemas de concurrencia debido al posible acceso del manejador —o de funciones invocadas por el manejador— a datos que estén siendo manipulados por el programa en el momento en que es interrumpido.

Lo cierto es que sólo unas pocas funciones de la librería del sistema son seguras frente a señales POSIX. Y obviamente no hay seguridad de que las funciones del API de Qt lo sean, por lo que no podemos invocarlas directamente desde los manejadores de señal. Además Qt no integra ninguna solución que encapsule y simplifique la gestión de señales POSIX, puesto que éstas no están disponibles en sistemas operativos no POSIX y Qt sólo implementa características portables entre sistemas operativos.

Aun así en la documentación de Qt se describe una forma de usar señales POSIX, que en realidad es muy sencilla:

  • Basta con que al recibir la señal POSIX el manejador haga algo que provoque que Qt emita una señal, antes de retornar. Por ejemplo, escribir algunos bytes en un socket que está conectado a otro gestionado por Qt.
  • Al volver a la secuencia normal de ejecución del programa, tarde o temprano la aplicación volverá al bucle de mensajes. Entonces la condición será detectada — en el ejemplo anterior, sería que han llegado algunos bytes a través del socket que Qt gestiona — y se emitiría la señal de Qt correspondiente, que invocaría al slot al que está conectada, desde donde se podrían ejecutar de forma segura las operaciones que fueran necesarias.

Concretamente en el artículo Calling Qt Functions From Unix Signal Handlers se propone la solución esquematizada en la siguiente ilustración:

Manejo de señales POSIX en aplicaciones con Qt
Solución de manejo de señales POSIX en Qt.

Así que comenzaremos declarando una clase que contenga los manejadores de señal, los slots y otros elementos que comentaremos posteriomente.

En el constructor de la clase anterior, para cada señal que se quiere manejar, se usa la llamada al sistema socketpair() para crear una pareja de sockets de dominio UNIX anónimos conectados entre sí. Al estar conectados desde el principio, lo que se escribe en uno de los sockets de la pareja se puede leer en el otro. Además se crea un objeto QSocketNotifier para que gestione uno de los sockets de la pareja, con el objeto de detectar cuándo hay datos disponibles para ser leídos, en cuyo caso QSocketNotifier envía la señal QSocketNotifier::activated().

Entonces el manejador de señal POSIX lo único que tiene que hacer cuando es invocado es escribir algo en el socket que no gestiona QSocketNotifier.

Mientras que en el slot al que conectamos la señal QSocketNotifier::activated() se lee lo escrito desde el manejador anterior en el socket, para después pasar a tratar la señal como creamos conveniente. Como este slot es invocado desde el bucle de mensajes de la aplicación, podemos hacer cualquier acción que nos venga en gana, al contrario de lo que pasa dentro de un manejador de señal POSIX como MyDaemon::termSignalHandler(), donde sólo podemos invocar unas pocas funciones.

Por conveniencia, podemos añadir una función para asignar el manejador MyDaemon::termSignalHandler a la señal SIGTERM usando la llamada al sistema sigaction(). Recordemos que los métodos que se van a utilizar como manejadores se declaran como static para que puedan ser pasados como un puntero de función a la llamada al sistema sigaction().

Referencias

--

--

Jesús Torres
Jesús Torres

Docente e Investigador de la Universidad de La Laguna.