SDK Multiplataforma en C logo

SDK Multiplataforma en C

Bienvenidos a NAppGUI

❮ Anterior
Siguiente ❯

Mientras que otros estaban contentos con escribir programas que solo resolvieran problemas, los primeros hackers estaban obsesionados con escribir programas que resolvieran problemas bien. Un nuevo programa que alcanzara el mismo resultado que otro existente pero que usase menos tarjetas perforadas se consideraba mejor, aunque hiciera lo mismo. La diferencia fundamental era cómo el programa lograba su resultado. - elegancia. Jon Erickson - Hacking: The Art of Exploitation


NAppGUI es un SDK para crear aplicaciones nativas multiplataforma en C. Por software nativo entendemos aquel que se compila/ensambla utilizando las instrucciones específicas de la CPU (no está interpretado ni utiliza bytecode) y por multiplataforma la capacidad de generar versiones para Windows, macOS y Linux utilizando la misma base de código fuente (Figura 1). Desde sus primeras funciones escritas en agosto de 2010, el principal objetivo de NAppGUI ha sido simplificar al máximo la ardua tarea de crear aplicaciones con interfaz gráfica en C. Aunque ya existen diferentes soluciones, nosotros hemos optado por la simplicidad creando una ligera capa de abstracción que encapsula las tecnologías nativas, las unifica bajo un mismo API y añade cierta lógica para la gestión y automatización de tareas. Siendo algo más específicos, la filosofía en la que se basa el proyecto y algunas de sus características son:

Esquema que muestra como el mismo código fuente puede funcionar en todas las versiones de Windows, macOS y Linux.
Figura 1: Desarrollo multiplataforma nativo con NAppGUI.
  • Rápido prototipado, evolución y mantenimiento en aplicaciones de verdad, al margen de los simples ejemplos que encontramos en la literatura e Internet.
  • La interfaz de usuario se describe mediante funciones ANSI-C, eliminando completamente el diseño visual. Este hecho facilita la creación de interfaces dinámicas, garantiza la portabilidad y posibilita el acceso al API desde cualquier lenguaje de programación.
  • Las ventanas se componen y dimensionan automáticamente, sin que el programador tenga que indicar explícitamente las coordenadas y el tamaño de los controles.
  • Es posible tener una aplicación completa en un solo archivo .c, eliminando los habituales archivos de recursos (*.rc, *.xvid, etc) y sus controladores asociados. El programador tiene total libertad a la hora de definir su propia estructura de archivos.
  • Sincronización automática de las estructuras de datos internas con la interfaz o con canales de E/S. Data binding.
  • Gestión unificada de recursos lo que facilita la internacionalización. Recursos.
  • Traducciones entre idiomas en tiempo de ejecución sin necesidad de reiniciar la aplicación. Traducción en ejecución.
  • La versión compilada de NAppGUI ocupa menos de 1Mb, y se distribuye en varias librerías estáticas que generan ejecutables de tamaño muy reducido. Esta es una gran ventaja con respecto a otras soluciones que requieren la distribución de pesadas .DLLs, en ocasiones más grandes que la aplicación en sí.
  • Apariencia nativa: Las aplicaciones se integrarán en cada sistema respetando su estética original (Figura 2).
  • Back-ends. El núcleo de NAppGUI proporciona estructuras y objetos para la creación de aplicaciones en línea de comandos, muy eficientes en servidores Windows o Linux.
  • Varias capturas de pantalla que muestran la apariencia de la misma aplicación en diferentes sistemas operativos.
    Figura 2: Apariencia nativa de la demo Hello, World!.

1. APIs originales

Microsoft, Apple y GNU/Linux proponen diferentes APIs para interactuar con sus sistemas. Esto significa que la misma aplicación debe ser reescrita para que funcione correctamente en cada plataforma. NAppGUI proporciona un conjunto unificado de funciones para crear interfaces gráficas de usuario y permitir el acceso directo a los recursos de la máquina (memoria, disco, red, etc.) (Figura 3). Cada implementación tiene en cuenta las condiciones particulares de la plataforma de destino y utiliza los comandos nativos apropiados para realizar la tarea de la manera más óptima posible.

Esquema que muestra las llamadas a los APIs nativos para crear un botón.
Figura 3: Llamadas a las APIs nativas, desde el mismo código fuente.

2. Basado en C

A pesar de que hoy en día disponemos de una gran cantidad de lenguajes de programación, el lenguaje C sigue siendo el más potente y portable del mundo. El núcleo de Windows, macOS, Linux, Android, iOS y otros grandes programas están escritos en gran parte en C. En el mundo de las aplicaciones, su uso se ha visto disminuido un poco a favor de otros con más glamour. Tal vez esta sea una de las razones por las que la ley de Wirth sea cada vez día más cierta.

"El software se ralentiza más deprisa de lo que se acelera el hardware."

NAppGUI está escrito, prácticamente en su totalidad, en lenguaje C con pequeñas partes en C++ y Objective-C. Este lenguaje es ampliamente soportado y compatible entre plataformas. En su desarrollo hemos prescindido de lenguajes minoritarios, propietarios o vinculados a una marca como: C#, Swift, Java u Objective-C. También de los interpretados (como Python o JavaScript) y de aquellos basados en máquinas virtuales (Java y C#) por la penalización en el rendimiento (Figura 4). Finalmente, no hemos utilizado C++, ya que no presentamos NAppGUI como una jerarquía de clases sino como una biblioteca de funciones. Nuestros objetivos han sido minimizar el impacto del SDK, simplificar la programación, aumentar la legibilidad y producir binarios de alto rendimiento.

Diferentes lenguajes de programación. Desde los más lentos (interpretados) a los más rápidos (compilados).
Figura 4: Intérprete, máquina virtual y código binario. Cuanto más nos acercamos al lenguaje máquina, mayor rendimiento obtendremos del software.

3. Sin editores visuales

La creación de interfaces gráficas puede convertirse en un proceso tedioso, ya que es difícil saber de antemano el tamaño final de elementos que contienen texto o imágenes, como es el caso de los botones. Por otro lado, las ventanas son entidades dinámicas sujetas a cambios en tiempo de ejecución (tamaño, traducción, cambio de subpaneles, áreas ocultas, etc.). Cuando usamos un editor visual, tenemos que ubicar los elementos en la posición y tamaño exactos (Figura 5). Esta es una tarea que requiere un uso intensivo del ratón, lo que ralentiza la conexión entre los objetos GUI y los controladores de eventos. En el ciclo de desarrollo, si los textos u otros elementos cambian (y por supuesto, cambiarán), tendremos que reubicar de nuevo los componentes a mano. Este problema crece en soluciones multilingües. Mantener a los desarrolladores moviendo píxeles y llenando formularios de propiedades es costoso para las empresas y muy aburrido para ellos. Todo esto sin mencionar que todos estos diseños visuales no serán compatibles entre plataformas (.rc Windows, .xib macOS, .glade GTK/Gnome, etc.).

Captura del editor de interfaces incluido en Xcode.
Figura 5: Los editores de recursos no son buenos aliados para crear complejas interfaces dinámicas.
Muchos programadores prefieren no mover las manos del teclado, ya que lo consideran mucho más productivo.

NAppGUI utiliza una estrategia declarativa, donde solo es necesario indicar la celda donde se ubicará el elemento dentro de una rejilla rectangular (Layout). El tamaño y posición final se calcularán en tiempo de ejecución, realizando una composición recursiva de los layouts y sublayouts en función de su contenido (Listado 1) (Figura 6).

Listado 1: Creación de una ventana.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
Panel *panel = panel_create();
Layout *layout = layout_create(1, 3);
Label *label = label_create();
Button *button = button_push();
TextView *view = textview_create();
Window *window = window_create(ekWINDOW_STD);
label_text(label, "Hello!, I'm a label");
button_text(button, "Click Me!");
layout_label(layout, label, 0, 0);
layout_button(layout, button, 0, 1);
layout_textview(layout, view, 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);
window_panel(window, panel);
window_title(window, "Hello, World!");
Interfaz de usuario sencilla en Windows. Interfaz de usuario sencilla en macOS a partir del mismo código fuente que Windows.
Figura 6: La composición declarativa es rápida, adaptable y portátil.

4. Dependencias

NAppGUI no utiliza librerías de terceros, tan solo conecta con las APIs nativas de cada sistema operativo. Este hecho, junto con el uso de C y el enlace estático, posibilita que:

  • Las aplicaciones no necesitan entornos de ejecución adicionales, como ocurre con Python, Java o C#. Van directamente a la CPU a través del scheduler del sistema.
  • Toda la aplicación puede estar contenida en un solo archivo .exe. Se enlaza el mínimo código posible y no es necesario distribuir ninguna .dll adicional. A partir de la versión 1.3, NAppGUI puede compilarse como librerías dinámicas.
  • Las aplicaciones ocupan muy poco espacio en disco, ya que todas sus dependencias se encuentran presentes de forma natural en los sistemas donde se ejecutan.
  • El rendimiento es máximo, ya que están compiladas en código máquina nativo, utilizando el mayor nivel de optimización que soporta cada CPU.
  • Se pueden editar, compilar y ejecutar en plataformas obsoletas a día de hoy como un Pentium III con Visual Studio 2005 y WindowsXP.
  • Con NAppGUI podemos moverlas de Windows a macOS o Linux, sin tocar una sola línea de código fuente. Ver Generadores, compiladores e IDEs.
  • El lenguaje C es el puto amo.

Tres paquetes dentro del SDK actuarán como wrappers tecnológicos (Figura 8), ocultando los detalles específicos de cada plataforma bajo una interfaz común, sin que esto suponga una sobrecarga para el programa.

Tecnologías de base con las que enlaza el SDK en cada sistema operativo.
Figura 8: Diferentes tecnologías en la base de NAppGUI. En NAppGUI API tienes el esquema completo.
  • Osbs: Operating System Basic Services. API sobre archivos y directorios, procesos, hebras, memoria, etc.
  • Draw2D: API sobre dibujo vectorial 2d, imágenes y fuentes tipográficas.
  • Gui: API sobre interfaces gráficas: Ventanas, controles y menús.
  • Llamadas al sistema Unix: En sistemas tipo Unix (Linux, macOS) es la forma en que un programa se comunica con el núcleo para realizar alguna tarea relacionada con archivos, procesos, memoria, red o hardware en general.
  • API de Windows: Es el API de más bajo nivel proporcionado por Microsoft para la programación bajo Windows. Es muy amplio e integra diferentes aspectos del desarrollo:
    • kernel32.dll: El equivalente a las llamadas Unix (archivos, procesos, memoria, etc).
    • ws2_32.dll: Proporciona funciones de red TCP/IP (Las llamadas Unix incluyen el soporte TCP/IP).
    • user32.dll, comctl32.dll, comdlg32.dll, uxtheme.dll: Implementa controles estándar para interfaces gráficas de usuario (etiquetas, cajas de edición, combos, barras de progreso, diálogos comunes, etc.).
  • Cocoa: API de programación orientado a objetos de los sistemas Mac OSX (ahora macOS). Está escrito en Objective-C, por lo tanto, no es accesible directamente desde C "puro". Cocoa se basa en OpenStep, el API de NeXTSTEP, el sistema operativo creado por Steve Jobs cuando fue despedido de Apple. En 1996, Apple compra NeXT y recupera a Jobs, que utiliza su tecnología como base para el nuevo Macintosh. Muchas clases en Cocoa aún conservan el prefijo NS como herencia NeXTSTEP. Aunque hay un API basado en C de nivel inferior llamado Carbon, este se encuentra discontinuado desde Mac OSX 10.4 Tiger. No tiene acceso a todas las funcionalidades del sistema ni es compatible con las aplicaciones de 64 bits. Por tanto, Cocoa es el API actual de más bajo nivel para los sistemas de Apple.
  • Gtk+: Acrónimo de GIMP ToolKit. Es una librería de alto nivel para crear interfaces gráficas con multitud de objetos predefinidos (llamados widgets). Es una de las más extendidas en sistemas GNU/Linux, pero en realidad es multiplataforma con versiones para Windows y macOS. Entornos de escritorio como Gnome, Xfce o aplicaciones como GIMP se basan en GTK.
  • GDI+: Es la evolución de GDI (Graphics Device Interface), un API de dibujo vectorial 2d desarrollado por Microsoft para la primera versión de Windows de 16 bits. GDI+ se introdujo con WindowsXP como un conjunto de clases C++ y está encapsulado en la plataforma .NET a través del espacio de nombres System.Drawing. También es accesible directamente desde C a través del GDI+ Flat API, pero Microsoft recomienda utilizarla a través de las clases de C++. Incorpora mejoras sustanciales con respecto a GDI, como coordenadas de punto flotante, transformaciones afines, antialias, sombreado con degradados y compatibilidad con formatos de imagen como JPG, PNG o GIF. El dibujo con máscaras y la incompatibilidad con PDF son los dos inconvenientes más notables en comparación con Quartz 2D y Cairo, sus "competidores" directos en otras plataformas.
  • Quartz 2D: Es el nombre comercial de Core Graphics, la poderosa API de dibujo del macOS. Al igual que Cocoa, Core Graphics es una evolución de las bibliotecas gráficas de NeXTSTEP y llegó a Apple después de la adquisición de NeXT. Quartz 2D se basa en los formatos Adobe PostScript y PDF, incorporando canal alfa y antialias. Los Mac clásicos (pre-NeXT) utilizaban la biblioteca QuickDraw, desarrollada inicialmente por Bill Atkinson para el Apple Lisa. Los macs modernos aún incorporan QuickDraw, pero Xcode ya no proporciona cabeceras, por lo que no se puede utilizar en nuevos proyectos. Core Graphics es un API basado en C y todas sus funciones comienzan con el prefijo CG.
  • Cairo: Cairo es una librería de dibujo vectorial 2d basada en C. A diferencia de GDI+ o Quartz 2D, es multiplataforma, se puede descargar de forma independiente e incorporarla en cualquier proyecto (bajo licencia LGPL). Desde su versión 3, GTK+ utiliza Cairo para todas las tareas de dibujado de widgets. GTK+2 también utilizaba Cairo para generar los documentos PDF para imprimir. NAppGUI usa Cairo para implementar el API draw2d en la plataforma GNU/Linux, ya que esta librería se encuentra de forma natural en todos los entornos de escritorio basados en GTK+: Gnome, Cinnamon, LXDE, Mate, Pantheon, Sugar o Xfce. Técnicamente, Cairo es bastante avanzada, equiparándose a Quartz 2D en términos de funcionalidad. Admite transformaciones afines, máscaras de imagen, curvas de bezier, procesamiento de texto y dibujo sobre superficies PDF y PostScript.
  • C stdlib: C es un lenguaje pequeño y hermoso, pero no proporciona funciones de apoyo adicionales. Durante la década de 1970, el lenguaje C se hizo muy popular y los usuarios comenzaron a compartir ideas sobre cómo resolver tareas comunes y repetitivas. Con su estandarización en la década de los 80, algunas de estas ideas se convirtieron en la librería estándar de C, que proporciona un conjunto básico de funciones matemáticas, manipulación de cadenas, conversiones de tipo y entrada/salida. NAppGUI integra de una forma u otra la funcionalidad de la librería estándar, por lo que no recomendamos su uso en aplicaciones finales (ver Sewer).

5. Bajo y alto nivel

Durante su diseño e implementación, NAppGUI ha tratado de mantener un balance equilibrado entre la programación de bajo y alto nivel. Los amantes del bajo nivel encontrarán una especie de librería C extendida y multiplataforma para acceder al sistema, los elementos de interfaz y los comandos de dibujo. No obstante, aún conservarán el poder para crear código optimizado y el acceso directo a la memoria. ¡Recuerda, estamos en C!

Por otro lado, NAppGUI integra algunas soluciones de alto nivel como la gestión de recursos, la composición de interfaces, las traducciones automáticas o la vinculación de datos entre otras. NAppGUI también incorpora scripts CMake para la creación automatizada de proyectos en Visual Studio, Xcode o Eclipse/Make.

Finalmente, son los desarrolladores los que deciden con qué librerías enlazar según las necesidades del proyecto y con el grado de automatización que deseen adoptar. Cada aplicación basada en NAppGUI realiza un enlace estático de todas sus dependencias, por lo que ni el ejecutable ni su distribución final tendrán rastro de código binario innecesario. De esta manera, produciremos ejecutables autocontenidos de tamaño reducido que no requerirán de instalador ni incluirán megabytes de dependencias en forma de .DLLs.

❮ Anterior
Siguiente ❯