viernes, 28 de septiembre de 2012

Compilación y bibliotecas (parte 5): crear bibliotecas con ZinjaI

En los cuatro posts anteriores de esta serie Compilación y Bibliotecas hablé un poco en general del proceso de compilación y el uso de bibliotecas externas, para explicar finalmente en el cuarto cómo utilizar un biblioteca X en un proyecto de ZinjaI. En la última entrega de la serie, vamos a ver un caso mucho menos frecuente, pero igualmente interesante: vamos a ver cómo crear nuestras propias bibliotecas con ZinjaI.

Desde hace ya unas cuantas versiones, ZinjaI presenta en el cuadro de diálogo de Opciones de Compilación y Ejecución de Proyecto, una pestaña titulada "Biblioteca". Esta pestaña sirve para crear una biblioteca a partir de una parte del proyecto. Sabemos ya que una biblioteca por sí misma no se puede ejecutar, sino que requiere de un programa cliente. Cuando uno desarrolla una biblioteca va desarrollando al mismo tiempo uno o más programas clientes para probar su funcionamiento.  Entonces, la idea es que en el proyecto de ZinjaI tendremos un conjunto de archivos que conforman los fuentes de una o más bibliotecas, y otro conjunto de archivos que conforman el programa cliente. En la pestaña "Biblioteca" es donde decimos qué bibliotecas generar y con qué archivos. También se puede indicar allí mediante un checkbox que el proyecto solo generará una biblioteca, y no habrá programa cliente si así se quisiera, pero creo que no es lo normal durante el desarrollo, aunque puede ser útil en los perfiles para release.

miércoles, 26 de septiembre de 2012

Compilación y bibliotecas (parte 4): utilizar bibliotecas desde ZinjaI

En este post voy a tratar de ser bien práctico y dar instrucciones precisas de qué hacer para utilizar bibliotecas no estándar en ZinjaI. Lo primero que hay que saber es qué se necesita para utilizar una biblioteca. Repasando los posts anteriores se llega a la conclusión de que se necesitan dos cosas, los archivos de cabecera (.h o .hpp), y los binarios (.dll, .lib, .so, o .a). Los primeros son necesarios para compilar las partes del programa cliente que hacen uso de la biblioteca. Estos fuentes deberán incluirlos (con "#include") para que el compilador no se queje de que no conoce las clases o funciones de la biblioteca al querer utilizarlas. Los segundos son necesarios para enlazar el ejecutable final, ya que los primeros solo decían qué hace la biblioteca, pero no cómo. En algunos casos particulares puede que esté todo en los .h y no haya binarios. Esto suele ocurrir cuando hay templates involucrados (como la biblioteca CImg por ejemplo), o cuando por decisión de diseño porque la biblioteca no es tan compleja, el que la desarrolló lo hizo así para facilitar su utilización (por ejemplo, la biblioteca EasyBMP), pero son excepciones (y aún así requieren binarios del sistema, bibliotecas de Windows por ejemplo), normalmente se requieren las dos cosas (como con GLUT, wxWidgets, SFML, QT, etc, etc, etc).

sábado, 22 de septiembre de 2012

Compilación y bibliotecas (parte 3): ese oscuro y mágico proceso

Centrándonos ahora en el proceso de compilación de un programa C++, hay que aclarar que es un proceso de varias etapas, y que no se realiza por un solo programa, sino por un conjunto. El compilador es en realidad un grupo de programas. En el proceso de convertir el código C++ en código objeto hay que preprocesar los fuentes, analizar la sintaxis y traducir a lenguaje ensamblador, convertir el código ensamblador en código de máquina dentro de un archivo llamado "objeto", aplicando optimizaciones, agregando información para el depurador, etc, y finalmente juntar todos los archivos objetos y, si las piezas encajan, empaquetar todo en un archivo ejecutable final. La parte del preproceso en C/C++ es la que analiza las lineas que empiezan con # (como los #define, #ifdefs, #includes) y otras tareas similares (quitar comentarios y expandir macros por ejemplo). Luego viene la etapa más importante, que sería la verdadera compilación, que es donde se traduce el código fuente a código de máquina. Pero en un proyecto real, el código fuente no es un sólo gran archivo, sino que está distribuido en muchas partes, y se debe traducir cada uno de ellas. Luego, juntando todas esas traducciones, se confecciona el ejecutable. Este último paso se conoce como enlazado. En general, a fines prácticos, al programador sólo le interesa diferenciar entre la etapa de compilación (incluyendo aquí en un solo bloque preproceso, compilación y ensamblado), y la etapa de enlazado.

miércoles, 19 de septiembre de 2012

Compilación y bibliotecas (parte 2): Intérpretes vs Compiladores

A la hora de ejecutar un programa escrito con algún lenguaje de programación hay, en principio, dos grandes opciones: interpretar y compilar. Digo en principio, porque se podría discutir un buen rato sobre dónde ubicar lenguajes administrados, máquinas virtuales por ejemplo, o intérpretes que utilizan algún formato intermedio que no es ni fuente ni objeto, pero simplificando en los dos casos de libro más claros, tenemos intérpretes y compiladores. Un compilador es un programa que "traduce" el código fuente de un lenguaje de alto nivel a código de máquina listo para ser ejecutado. Un intérprete, en cambio, es otro programa que interpreta las instrucciones del código de alto nivel e intenta ejecutarlas él mismo. La diferencia en principio no parece clara, pero pongamos una metáfora. Si quiero escuchar una canción, digamos "Over the rainbow", la hoja con la partitura y la letra serían el código fuente. Un compilador sería un músico que toma la partitura, y graba la canción por ejemplo en un cd de audio que voy a poder escuchar en mi auto cuando quiera aunque el músico se haya retirado, vendido su guitarra y quemado la partitura. Un intérprete en cambio sería un músico que toca la canción en vivo para mi, leyendo de la partitura cada vez que quiero escucharla, y que tengo que llevar sentado con su guitarra en el asiento del acompañante todo el tiempo. Parece poco cómodo, pero tiene sus ventajas. Digamos que quiero probar cómo quedaría si agrego algunos arreglos, o si cambio la letra por otra nueva que se me ocurrió, o si afino la guitarra un semitono más arriba. En el primer caso, tendría que llamar al músico "compilador" para volver a grabar un nuevo cd, mientras que en el segundo tendría que hacer lo mismo de siempre, pedirle al músico "intérprete" que interprete una vez más la partitura, que esta vez estará modificada.

lunes, 17 de septiembre de 2012

Compilación y bibliotecas (parte 1): ¿por qué?

Aprender a programar es difícil. Involucra muchas cosas. Por un lado están la lógica, el nivel de abstracción, los conceptos, los paradigmas; todas esas cosas inherentes a la programación en sí, e independientes del lenguaje. Por otro lado está el mismísimo lenguaje, esa oscura maraña de reglas, en un inglés abreviado y poco amigable para el estudiante, donde tratamos de aplicar todo lo primero para poder ver sus frutos. Mediante mis proyectos PSeInt y ZinjaI intento contribuir a simplificar el aprendizaje en ambos frentes. Siempre digo que aprender las dos cosas al mismo tiempo es demasiado difícil. Para poder separar e ir por partes es que nace el pseudocódigo. El pseudocódigo sería una herramienta didáctica para aprender y practicar lo conceptual sin lidiar con el lenguaje. Allí es donde espero que PSeInt dé una mano, permitiendo hacer alguna práctica más real y entretenida de esa primera parte. Una vez dado ese primer paso, se pasa a estudiar un lenguaje real, y allí es donde (si el lenguaje resulta ser C++) ZinjaI intenta contribuir. Pero el problema acá es que deliberadamente oculté un tercer componente muy muy importante en esto de aprender a programar, que es el compilador. Un lenguaje sin un compilador no es más que ideas en el aire, intangibles y poco prácticas. Es el compilador (o intérprete) el que lo trae a la vida, el que hace el nexo entre ese lenguaje y el conjunto hardware+sistema operativo que representa el mundo real para el programa.

viernes, 7 de septiembre de 2012

Próximamente: SubProcesos en PSeInt!

La posibilidad de crear funciones/subprocesos/subrutinas/como-quieran-llamarlos en PSeInt es por lejos la funcionalidad por la que más veces me han preguntado los usuarios. Desde hace años que joden con eso. Y desde hace años que les digo lo mismo: por razones históricas el código base del intérprete está tan mal diseñado que me es imposible agregarlas en ese estado. Pero con el paso del tiempo, muchos cambios internos fueron sucediéndose. Por ejemplo, la clase que administra la memoria virtual del intérprete es completamente nueva, culpa de esto los tipos de datos se gestionan de forma muy diferente a cómo se lo hacía en las primeras versiones, y gracias a esto la evaluación de expresiones a mejorado muchísimo y se ha vuelto también más flexible. Después de muchos intentos, esas partes quedaron listas para un futuro mejor. Seguro que tienen sus errores, pero comparativamente ha sido un gran paso. Por contraparte, el código que analiza la estructura de las instrucciones y que en verdad parsea la sintaxis más allá de las expresiones sigue siendo bastante horrible en su diseño. De a poco voy puliendo la implementación, pero el diseño no ha cambiado sustancialmente. No me gusta el enfoque actual, pero no estoy seguro de que el enfoque teóricamente correcto para un intérprete sea el mejor en este caso. No obstante, repensando el tema de los subprocesos por enésima vez llegué a la conclusión de que la no tan pequeña evolución de la que hablaba ya era sufiente como para intentar implementarlos sin morir en el intento. Puse manos a la obra, y algo salió. En este post quiero presentar con bombos y platillos la tan pedida y flamante funcionalidad, y explicar algunas cuestiones de diseño desde el punto de vista del lenguaje y la interfaz, ya no desde la implementación del intérprete. Hay muchos puntos grises que aún no me terminan de convencer y que posiblemente cambien en el futuro, así que hay que tomar todo como está, en estado experimental, sin terminar, sujeto a cambios, pero igual ya hay disponible en el repositorio git un intérprete capaz de aceptar la definición de funciones/subprocesos por parte del usuario.