Este es un post invitado escrito por Cristian Vásquez

Velneo v7 es una plataforma que se diseño para solucionar las problemáticas de las aplicaciones de gestión, lo cual hace de una forma sobresaliente.

Para ello tiene su estilo particular de hacer las cosas y al ser una plataforma relativamente cerrada sus principales limitaciones son de integración.

Con el fin de solucionar esto, V7 pone a nuestra disposición el objeto librería externa, mediante el cual podemos usar librerías compartidas win (.dll), mac (.so) y linux (.so) con las cuales podemos usar funcionalidades que no se encuentran definidas en V7 y que requerirían mucho trabajo y tiempo para construir desde cero.

Ahora, el que tengamos el objeto librería externa no significa que V7 este abierto al mundo y que hayan miles de librerías listas para usar directamente.

El conjunto de librerías que puede usar Velneo deben de tener un set de características a cumplir, lo que limita el universo que realmente están disponibles. Por ello te verás en muchas situaciones creando librerías «puente” que, básicamente, serán wrappers alrededor de una librería para acomodar los tipos de entrada y salida a las convenciones que acepta Velneo.

Manos a la obra… y ¿con qué lenguaje?

Ahora que sabes que puedes integrar V7 con el mundo exterior mediante estas librerías

¿qué lenguaje puedes usar para crear las .dlls o .sos ?

Tienes que usar lenguajes que compilen a binario directamente, con capacidad para crear shared libraries y que la multiplataforma no sea un infierno.

Esto te crea un nuevo problema: lenguajes como C, C++ o Rust aunque puede realizar lo anteriormente descrito tienen una curva de aprendizaje muy empinada y en buena medida porque tienen la característica de que tu tienes que manejar la memoria manualmente (pointers, mallocs, mutexes, etc), lo cual los hace ciertamente inseguros o con demasiado boilerplate, por lo que es posible que puedes terminar con un programa que se cuelga cada 2 por 3 o que tiene leaks de memoria.

Go (Golang) al rescate

En este escenario, el problema es la inversión de tiempo vs el retorno. Tu solo deseas incluir una funcionalidad x en tu aplicación V7 pero no por ello quieres aprender un lenguaje nuevo que para colmo lleva su tiempo dominar al punto en el que te sientas cómodo llevando código a producción.

Go viene a cambiar esta problemática. fue creado en 2009 por Google como herramienta para crear soluciones internas teniendo como objetivo la simplicidad, el performance, la concurrencia, bajo tiempo de compilación (siempre se bromea con que el creador de Go lo fue escribiendo mientras esperaba que C++ compilara) y evitar el manejo de memoria mediante un garbage collector.

Además tiene la capacidad de compilar el mismo código para varias plataformas de una forma casi transparente para el desarrollador y desde recientes versiones puede crear shared libraries, para no extenderme en las capacidades y falencias de Go aquí un popular post al respecto.

Hello V7

Para demostrar la simplicidad con la que podemos extender V7 con Go vamos a realizar una integración básica exportando una función que responda un saludo.

Antes de comenzar, lo primero es descargar e instalar Go, para ello tienes la siguente guía aquí , que consiste básicamente descargar Go ejecutar el instalable y verificar que tienes Go corriendo apropiadamente desde la terminal, linea de comandos o powershell; para ello hay n mil guías de instalación y videos que están a una googleada de distancia, cuando tengas tu instalación de Go corriendo y el clásico ejemplo de “hello world” hecho, vuelve aquí.

Ahora si a lo que vinimos, vamos a crear una función en Go y la vamos a llamar desde v7 obteniendo el resultado.

Escribiendo la dll

Si viste un par de ejemplos te será relativamente familiar el clásico ejemplo de “hello world”, esta es una versión con un pequeño un cambio para que el saludo sea una función como tal:

GOPATH/src/hellov7/main.go

package main

import "fmt"

func main() {
   fmt.Println(sayHello("world"))
}

func sayHello(message string) string {
   return "hello " + message
}

La función a la que queremos acceder desde v7 es sayHello, para convertir este código en algo exportable debemos realizar algunos cambios:

package main

import "fmt"
import "C"

func main() {}

//export SayHello
func SayHello(message *C.char) *C.char {
  goMessage := C.GoString(message)
  return C.CString("hello " + goMessage)
}

Cambios:

  • Importamos el paquete “C” que nos servirá para el intercambio de datos.
  • Cambiamos el nombre de la función de “sayHello” a “SayHello” para que Go lo exporte en el paquete “main” (por defecto Go exporta todas las funciones que comiencen con mayúscula).
  • Añadimos el comentario “//export SayHello” directamente encima de la función para que el compilador sepa que deseamos exportarla al crear la librería.
  • Cambiamos el tipo de datos de entrada y salida para que no sean strings nativos de Go sino strings de intercambio de datos de C.
  • Finalmente para leer el *C.char en un string normal de Go, se usa la función C.GoString y para convertir cualquier string de Go al tipo de C se para retornar usa la función C.CString.

Compilando nuestro código a .dll y .so

Ya que tenemos la version exportable de nuestro código, la compilación es bastante sencilla, simplemente en la consola vas a la carpeta donde tienes el proyecto (GOPATH/src/hellov7/) y entras el comando de compilación.

El comando en las tres plataformas tiene la misma intención: “Go creame una librería con la convención c-shared, el nombre del archivo va a ser main.dll(so) y tomas como base el archivo main.go”.

Existe la posibilidad de realizar cross compilation con Go y funciona bien a la primera con binarios y exes, pero en cuanto a la creación de librerías es digamos “inestable” dependiendo de lo que hagas te funciona, así que de momento lo ideal es tener una maquina o VM para cada sistema que queramos soportar y se comparten las carpetas con el código para compilar.

el comando para compilar es:

Windows

go build -buildmode=c-shared -o main.dll main.go

Linux y Mac

go build -buildmode=c-shared -o main.so main.go

Llamando la libreria desde V7

Llamar una librería externa desde V7 es un proceso de 2 pasos:

  1. Tienes que importar en la solución el archivo .dll o .so he identificar para que plataforma y arquitectura esta destinado.

2. Crear un nuevo objeto “librería externa” por cada plataformaa soportar y declarar las funciones que deseas llamar con sus parámetros y retorno.

En primera instancia en la propiedad “Nombre” de la librería debes de poner el nombre EXACTOdel archivo .dll o .so que acabaste de adjuntar, esta es la forma en la que se unen el objeto archivo adjunto y el objeto librería externa, de esta forma cuando se ejecuten las funciones, el objeto librería buscará el archivo en la cache del vClient y tratara de ejecutar la función.

Como puedes ver para Linux se declara la librería externa con la convención C_X64_SYSV, para las diferentes plataformas es:

  • Windows 64 => C_X64_WIN64
  • Windows 32 => C_X68_WIN32_THIS_GNU
  • Linux 64 => C_X64_SYSV
  • Mac OSX => C_PPC32_DARWIN

y luego dentro del objeto librería hay que declarar cada función a la que deseamos acceder, como es en nuestro caso SayHello.

Cosas a notar:

  • La propiedad “Nombre” de la función tiene el nombre EXACTO de la función a la que queremos acceder “SayHello” y el tipo de dato que esperamos como respuesta en este caso un puntero a una string inmutable “const char*” que V7 es capaz de leer y poner a nuestra disposición como una variable alfabética.
  • La función SayHello necesita un parámetro que llamaremos “mensaje”, y le asignamos el tipo de dato que es igualmente un “const char*”, básicamente el intercambio de strings entre v7 y Go se hará mediante const char*, en mis ensayos envié un string de 100Mb parámetro sin problema.

Usar la librería dentro de V7:

Con todo declarado usar la librería dentro de v7 es relativamente simple, en cualquier parte donde se pueda evaluar un expresión puedes usar:

dll:[email protected]("World")
dll:[email protected]("World")
dll:[email protected]("World")

Lo más sano es tener una función v7 que reciba los parámetros y en función del sistema operativo ejecuta mediante el objeto librería externa que corresponda.

la función para detectar el Sistema operativo lucirá como esta:

y finalmente la función para ejecutarse desde V7 luciría así:

El .vin con el ejemplo completo más el código de Go puedes descargarla aquí .

¿Qué más se puede hacer?

Esta pequeña integración es una mera probada para mostrar que es posible extender funcionalidades de Velneo en multiplataforma sin morir en el intento, el poder real viene de poder usar las n mil librerías disponibles para Go, muchas de ellas podrás encontrarlas en Awesome-Go  y otras tantas en las webs de desarrollo de lo que desees integrar, Google por ejemplo tiene librerías en Go para todas su plataformas.

En mi caso he creado 2 plugins para velneo V7:

  • vSftp => Cliente para servidores de archivos SFTP, disponible para win64, mac OSX y Linux 64.
  • vCommand => Plugin para ejecutar comandos del sistema y leer su resultado, disponible para win64, win32, mac OSX y Linux64.

Puedes verlos aquí

Gotchas y datos relevantes

  • Las dlls y .sos van a tener un tamaño mínimo de unos 2Mb porque en cada una se añade el runtime de Go que es quien se encarga de correr el código.
  • Como consecuencia del punto anterior, este enfoque de trabajo tiene la consecuencia de que engrosaras tus .vin añadiendo un par megas por cada plataforma soportada.
  • EL objeto librería compartida solo estas disponible en primer plano, según tengo entendido por temas de seguridad.
  • En algunoscasos puedes encontrarte problemas de encoding al enviar y onbtener dataos desde Go, la solución más simple es hacerle un scape con un encodeUriComponent de javascript dentro de v7 antes de enviar la data (encontraras ejemplo de estas funciones en .vin de demo) y viceversa para la data que recibes de Go mediante el paquete url, url.QueryUnescape para recibir la data y url.QueryEscape para enviársela a V7.
  • Es sano tener vms con las diferentes plataformas a soportar porque algunas librerías compilan más fácil si se el proceso se realiza en la plataforma destino.