4.1. Visual LISP como Cliente ActiveX


De las nuevas cosas que nos ofrece Visual LISP, quizás la más llamativa sea la posibilidad de gestionar los procedimientos ActiveX. Hasta ahora seguramente creímos que la interacción con otras aplicaciones Windows era una parcela reservada a Visual Basic. Pues ahora veremos que no lo es tanto.

Para demostrarlo desarrollaremos una función destinada a escribir en una hoja de cálculo Excel. Como explicábamos en nuestro anterior artículo, hay dos maneras de acceder a los métodos de una aplicación capaz de actuar como servidor ActiveX. Podemos importar su librería de tipos mediante vlax-import-type-library, lo que presenta una serie de inconvenientes, o podemos emplear las funciones vlaxinvoke-method, vlax-get-property y vlax-put-property que nos permiten acceder a sus métodos y propiedades de una manera más simple y directa. Este segundo procedimiento es el que utilizaremos en el programa que hoy proponemos. Hemos implementado una función de uso general, Lista->Excel que recibe como argumento una lista y utiliza la información que esta lista contiene para llenar con ella una hoja de cálculo Excel. El formato de esta lista deberá ser la de una lista con tres niveles de anidación. La lista de nivel superior -delimitada por los paréntesis más externos- representa la hoja de cálculo completa. Esta lista debe contener a su vez otras listas anidadas -listas de segundo nivel-en número variable, cada una de las cuales representa una fila de la tabla. Cada una de estas sublistas se compone a su vez de otras listas cuya cabeza (primer término de la lista que puede extraerse mediante la función car) representa el significado del dato o datos que contiene el resto o cola de la lista (devuelta por la función cdr). Cada una de estas listas de tercer nivel representa una celda de la correspondiente fila. Las listas de segundo nivel (filas) están organizadas como listas de asociación de igual manera que las listas que representan las propiedades de los objetos CAD en AutoLISP, es

Figura 1. Ejemplo de Lista de Asociación

Para demostrar cómo opera esta función propongo la función TablaCírculos que obtiene las listas de entidad de todos los círculos del dibujo y llena tabla con dichos datos. Esta es sólo una manera de probar la conversión del formato de lista LISP al de Hoja de Cálculo Excel. Cualquier otra lista que respete la estructura descrita más arriba puede servir como argumento para la función Lista->Excel. Como encabezamiento o título de las columnas se utilizarán las claves de la lista de asociación. Es evidente que un resultado correcto sólo se obtendrá si todas estas listas de segundo nivel tienen la misma composición, es decir, las mismas claves ordenadas de igual manera, por lo que la función de prueba lo comprobará, rechazando las listas que no cumplan este requisito.

Vinculación y Liberación de los objetos Excel

El primer paso será establecer la conexión con la aplicación Excel, añadir la hoja de cálculo y obtener su conjunto de celdas, en las cuales se realizará la escritura. Para ello se ha desarrollado la función IniciaExcel.

Figura 2. Función IniciaExcel

La función vlax-get-or-create-object establece el vínculo inicial obteniendo el objeto Aplicación Excel, guardando un puntero al mismo en la variable LISP *AplExcel*. Esta, así como las otras variables que apuntan a objetos VLA obtenidos de Excel se tratarán como variables globales, es decir que mantienen su valor una vez terminada la función donde se crean. Para distinguir éstas de las variables locales, cuyo valor sólo se mantiene dentro de la función donde se crean, los nombres de las variables globales suelen comenzar y terminar en asteriscos1. Si Excel no estuviera activo, se pondría en marcha. A partir de ahí se continúa obteniendo los distintos objetos hasta llegar al conjunto (Range) de celdas. Como dijimos más arriba, todos estos objetos VLA obtenidos se asignan a variables. Para obtener la información necesaria sobre la jerarquía de objetos propia de Excel deberá recurrirse a la Ayuda de dicho programa, bajo el epígrafe Referencia a Visual Basic para Microsoft Excel. Describiré brevemente el camino seguido:
  • A partir del objeto aplicación se obtiene su propiedad "Workbooks". Esta propiedad devuelve un conjunto Workbooks que representa todos los libros abiertos. Se guarda un puntero a este objeto en la variable LISP *ColeccionLibros*
  • A este conjunto añadiremos un nuevo libro vacío mediante el método "Add", asignándolo a la variable *NuevoLibro*
  • A partir del nuevo libro vacío obtendremos mediante la propiedad "Sheets" un conjunto que representa todas las hojas del libro especificado y lo guardamos en *ColeccionHojas*.
  • Mediante la propiedad "Item" del conjunto Sheets obtendremos con el parámetro 1 la primera hoja del libro creado que asignamos a *Hoja1*.
  • Por último, la propiedad "Cells" de la hoja 1 devuelve un objeto Range que representa las celdas de la hoja de cálculo (no sólo las celdas que se usan actualmente). La propiedad Item es la propiedad predeterminada del objeto Range, permitiéndonos especificar el índice de fila y columna para cada celda en particular. El objeto Range se asigna a la variable *CeldasExcel*.

Esta función se ha diseñado de la forma más simple posible. Pudiera modificarse para obtener resultados como el de añadir distintas hojas al mismo libro, etc.

Figura 3. Función TerminaExcel

Tan importante como establecer el vínculo con Excel resulta terminarlo de la forma adecuada. Para ello se ha diseñado la función TerminaExcel que aplica la función vlax-release-object a los distintos objetos VLA generados. Mientras un objeto VLA apunte a un objeto, AutoCAD retendrá la memoria necesaria para contenerlo. La función vlax-release-object le indica que ya ese objeto no es necesario y que por ello la memoria que ocupaba puede ser recuperada. Al liberar un objeto éste no será ya accesible a través del puntero VLA. La memoria no se libera necesariamente de inmediato, pero de ser necesario, AutoCAD puede reclamarla una vez que todas las referencias a ese objeto hayan desaparecido. Dada la importancia de ese proceso, se ha incorporado una llamada a la función TerminaExcel en la rutina de tratamiento de errores apl-err que se incorpora al programa, evitando que un fallo en la ejecución deje estos objetos sin liberar. Si no se empleara vlax-release-object la única manera de liberarlos sería terminar la sesión de trabajo con AutoCAD.

Figura 4. Función apl-err

Las funciones IniciaExcel y TerminaExcel son invocadas desde la función Lista->Excel. Esta función también sustituye la rutina de error standard por la rutina apl-err (habiendo guardado antes la original en la variable oer) y llama a la función vl-load-com que cargará las funciones vlax- necesarias. Debe recordarse que las funciones vlax-tienen un carácter opcional, por lo que si no se ha realizado antes una llamada expresa a vl-load-com el programa que las utilice terminará con un error. La escritura de la tabla en sí se incluye en la función ProcesaTabla que recibe como argumento una lista de las características antes explicadas. Al final de la función se restablece la rutina de error original.

Función ProcesaTabla

La escritura de datos a la Hoja de Cálculo Excel se realiza mediante la función ProcesaTabla. En esta, como en las rutinas siguientes, se hace un uso extenso de las funciones específicas LISP de tratamiento de listas como foreach, mapcar y apply. Aunque una explicación detallada de estas funciones estaría fuera del alcance de este artículo, haremos una breve explicación de su manera de operar:

Figura 5. Función Lista->Excel

(foreach símbolo lista [expresión ...])
La función foreach recorre una lista, asignando cada elemento de la lista a una variable (símbolo), y evalúa la expresión (una o varias) tomando dicho elemento como argumento. Por ejemplo:(foreach n '(a b c) (print n))equivale a (print a)(print b)(print c)

(mapcar function lista1... listan)
Devuelve una lista con los resultados de ejecutar una función tomando como argumentos los términos individuales de una o más listas. Por ejemplo:
(mapcar 'cons '(clave1 clave2 clave3) '(dato1 dato2 dato3))devolverá ((CLAVE1 . DATO1) (CLAVE2 . DATO2) (CLAVE3 . DATO3)).

(apply ’función lista)
Pasa una lista de argumentos a una función y la ejecuta. Ejemplos: (apply 'min '(1 200 -4 18 22 99)) devolverá -4 mientras que (apply 'max '(1 200 -4 18 22 99)) devolverá 200

La función ProcesaTabla a su vez llama a otras dos funciones definidas para este programa: DatoCelda y ProcesaFila. Lo primero que hace la función ProcesaTabla es escribir la fila de títulos de las columnas, utilizando para ello las claves de la primera sublista (car lista) recibida. La escritura de cada celda se realiza mediante la función DatoCelda que será descrita más abajo. Para llenar el resto de la tabla se establece un bucle while donde se pasa en cada ciclo la cabeza de la lista a la función ProcesaFila, que es la encargada de llenar las celdas de la fila determinada por el valor de la variable numFila. Para cada iteración se incrementa en 1 el valor asociado a la variable numFila y se elimina el primer término de la lista, de manera que en la siguiente vuelta (car lista) sea el próximo término de la lista original:

(setq numFila (1+ numFila) lista (cdr lista))

La inserción de los datos en cada celda se realiza mediante la función DatoCelda. Esta función se ha concebido como una función de uso general, aplicable en cualquier programa que deba realizar esta operación. Recibe como argumentos los índices de fila y columna (como enteros) que determinan la casilla en que se desea escribir, y el valor que se desea introducir en la casilla.

Figura 6. Función ProcesaTabla

Este último valor puede ser de cualquiera de los tipos admitidos por Visual LISP. El valor de la casilla es una propiedad del objeto Range de Excel, que obtuvimos en la función IniciaExcel y al que apunta la variable global *CeldasExcel*. Resulta entonces aplicable la función Visual LISP vlax-put-property. Obsérvese que la condición de cadena del valor que se pasa se asegura mediante la nueva función Visual LISP vl-princ-to-string que nos devuelve, convertida en cadena de caracteres la representación impresa tal como se obtendría en pantalla usando la función princ.

Figura 7. Función DatoCelda

La función ProcesaFila no hace más que incorporar la rutina DatoCelda antes descrita en un bucle implementado a partir de la función foreach. Aquí el número de fila se mantiene constante (lo recibe como argumento desde la función ProcesaTabla) y el número de columna (guardado en la variable numCol) se incrementa en la unidad para cada iteración. Se utiliza (cdr campo) para extraer la cola de la lista, que será el valor pasado a DatoCelda.

Figura 8. Función ProcesaFila

Exportación a una Tabla Excel de los datos de entidad


Figura 9. Dibujo con Círculos utilizado como demostración

Para terminar proponemos una sencilla función que tiene como único objetivo la demostración de cómo insertar la utilidad Lista->Excel en un programa. Se trata simplemente de leer los datos de entidad de todos los círculos presentes en un dibujo y exportar dichos datos a una hoja de cálculo. Los datos de entidad se obtienen directamente como listas de asociación mediante la función entget. Previamente se ha creado un conjunto de selección utilizando un filtro que selecciona sólo los objetos círculo. Una vez almacenado este conjunto de selección en la variable objetos, se inicia un ciclo while que recorre dicho conjunto de selección incorporando las listas obtenidas como sublistas de la lista de primer nivel que representa la tabla. Como explicábamos antes, estas sublistas deben tener la misma composición, tanto en su número de términos como en el orden en que aparecen sus datos. Para verificar esto se ha dispuesto el siguiente procedimiento: Se crea una lista similar a la original, pero en la que las sublistas sólo contienen como dato las claves. Para ello se recorre la lista de primer nivel con la función foreach, aplicándole a cada término de la lista de segundo nivel la función car, que extrae la cabeza de la lista de tercer nivel. En realidad la lista es similar, aunque invertida, producto de construirla con la función cons. Esto es perfectamente válido a nuestros fines.


Figura 10. Función TablaCirculos

Una vez creada la lista de claves se comparan las listas de segundo nivel (que ahora sólo contienen las claves) de dos en dos, comparando la primera con la segunda lista, la segunda con la tercera y así sucesivamente. Esta comparación se logra con una expresión que bien puede servir de ejemplo del poder de síntesis que se logra con LISP:

(apply 'and (mapcar 'equal claves (cdr claves)))

LISP es capaz de aceptar funciones como argumentos que se pasan a otras funciones. Esto es lo que sucede con la función mapcar, a la que se le pasa como primer argumento la función equal, destinada a comprobar si dos expresiones son iguales. Obsérvese que el nombre de la función pasada como argumento debe ir precedida de un signo apóstrofe (') que indica que se trata de un símbolo que se pasa sin evaluar. Además de la función equal, mapcar recibe dos listas. La primera se trata de la lista completa y la segunda se trata de la cola de esa misma lista, es decir quitándole el primer término mediante la función cdr. Con ello, mapcar aplica la función equal sucesivamente a los términos de cada lista, el primero de una con el primero de la otra, el segundo con el segundo y así sucesivamente para terminar en el momento en que una de las listas se agota. Los resultados de todas las comparaciones efectuadas se devuelven agrupados en forma de lista. Para obtener un resultado único recurriremos a la función apply, que también recibe otra función como primer argumento, en este caso el operador lógico and y lo aplica a la secuencia de términos de la lista. De esta manera obtendremos como resultado T (cierto) en caso de que todos los resultados de la lista fueran también T o nil (falso) en caso de que alguno no lo fuera. En el caso de los círculos, las listas de entidad serían diferentes en su composición si incluyéramos objetos con algunas propiedades (por ejemplo, color o tipo de línea) específicas y otras PORCAPA. Del dibujo de la Figura 9 obtendríamos una Hoja de Cálculo Excel como se muestra en la Figura 11.

Figura 11. Hoja de Cálculo Excel obtenida

Conclusiones

Hemos propuesto una solución a algo que con frecuencia se expone como argumento a favor de otros lenguajes de programación, en especial VBA o Visual Basic. Me parece evidente que un programador LISP puede estar tranquilo en cuanto a que no tiene por qué echar en falta la capacidad para interactuar con otras aplicaciones del entorno Windows. Quizás este primer programa sea algo difícil de seguir para quienes se inicien en la programación. Nos proponemos en futuros artículos abordar también temas que puedan servir a los que se inician en estas labores.

Reconocimientos

Ni decir hace falta que el código publicado está totalmente a la disposición de nuestros lectores para cualquier uso que deseen darle. De utilizarlo como parte de sus propios programas, agradecería que se mencionara la fuente de la cual se obtuvo. Como quiero yo hacer con Harold Carr y Robert Holt, cuyo artículo de 1998, The AutoLISP Platform for Computer Aided Design, me sugirió la idea central de este programa.


1 Las variables locales se declaran como tales en la lista de parámetros que en la forma especial defun sigue al nombre de la función. En este caso las variables que apuntan a los objetos VLA se declaran como locales para la función Lista->Excel (ver Fig. 5), por lo que al concluir esta función se evaluarían como nil. El evaluar a nil la variable no libera el objeto VLA al que apunta, por lo que antes deberá llamarse como aquí se hace a la función TerminaExcel.