miércoles, diciembre 21, 2022

Un poco del sistema extendido de (x)Harbour

Este es el último comentario del año, y he decido cerrar con broche de oro, porque luego se me quejan que estoy "perdiendo el toque" y que ya no publico cosas interesantes y que lo único que hago es meterme con FiveWin, para que mis detractores vean que no es cierto y como un regalito adelantado de Reyes para mis amigos, aquí les va este comentario que creo que será interesante para mas de uno. 

En esta ocasión vamos a hablar del "Sistema Extendido" de (x)Harbour, este comentario lo puse hace algunos meses en el foro privado de Xailer, pero considero de interés de todos los programadores de (x)Harbour tener acceso a esta información. 

Este es un "comentario" simplemente, no pretende ser una guía completa del Sistema Extendido de (x)Harbour pero te va a dar una muy buena idea de como hacer muchas cosas en "C", con instrucciones básicas para integrar rutinas escritas en ese lenguaje en tus programas (x)Harbour, Xailer, MiniGUI o FiveWin. 

Al igual que Clipper, (x)Harbour tiene un "Sistema Extendido" que te permite conectarte con funciones en lenguaje "C" mediante "wrappers" o "envolturas", seguramente si te pasas por la cocina de casa y ves la caja del foil (papel) de aluminio que se usa para envolver el bocadillo o tapar el tupper, verás que la caja dice "Reynolds Wrap":

  

 Ahora que nos ha quedado claro de donde viene la palabra "wrapper", y que es un wrapper, podemos comenzar con la explicación. 

Un wrapper o envoltura sirve, como su nombre lo indica, para "envolver" una función en C y así poder utilizarla desde xHarbour. 

 Un wrapper realiza básicamente 2 trabajos: traducción y ejecución. 

El primer trabajo, el de "traducción", significa que el wrapper "intercepta" los parámetros enviados a la función desde xHarbour y "traduce" dichos parámetros enviados a los tipos de variable utilizados por la función en "C", y para esto cuenta con una serie de funciones que son las funciones llamadas hb_parXXXXX (harbour parameter). 

Veamos un ejemplo: 

Si yo tengo que pasarle un parámetro numérico a una función en "C", desde (x)Harbour no tengo problemas, es un "numeric" simple y sencillo, pero en "C" los números se pueden expresar de muchas formas, como integer, long, float, etc. las funciones hb_parXXXX son las que se encargan del proceso de traducción a los valores en "C" correspondientes. 

Una vez "traducidos" los parámetros de los tipos de datos de (x)Harbour a los tipos requeridos por la función en "C", viene el proceso de "ejecución" en el cual se manda llamar la función en "C" que deseamos, enviándole los parámetros traducidos. 

 Una vez que se ejecuta la función y esta devuelve un valor, dicho valor debe ser traducido nuevamente a un tipo de dato válido que (x)Harbour pueda manipular, si la función retorna por ejemplo un valor "double", este tiene que ser traducido a "numeric" para esto se utilizan las funciones hb_retXXXX (Harbour Return) 

Veamos un ejemplo práctico, tomado del RDDADS de (x)Harbour: 

Supongamos que queremos ejecutar una función llamada:  

AdsSetServerType(integer) -> valor  

Esto quiere decir que la función AdsSetServerType escrita en "C", recibe un parámetro numérico "integer" tipo de dato que NO EXISTE en xHarbour, y devuelve un valor "X" no sabemos de que tipo.

Bien, tenemos que crear un wrapper o envoltorio para poder llamar a esta función escrita en "C" desde nuestro código xHarbour / Xailer / MiniGUI / FiveWin, y ahora verás porque se llaman "envolturas":

Lo primero que debemos hacer es crear el "nombre harbour" para la función esto se hace así:  

HB_FUNC( ADSSETSERVERTYPE ){ } 

Es MUY IMPORTANTE que el nombre de la función vaya TODO CON MAYUSCULAS, las {} indican el principio ({) y el fin (}) de la envoltura. 

Como esto es una función en "C", nosotros debemos definir las variables con las cuales va a trabajar nuestra función, así que las definimos:  

HB_FUNC( ADSSETSERVERTYPE )
{
    int servType;
    UNSIGNED32 ulRetVal = 999999;
}
 

Hemos definido una variable llamad servType de tipo INTEGER y un "entero sin signo" (UNSIGNED32) llamada ulRetVal, y le hemos dado un valor de 99999. 

La primer variable nos servirá para "recibir" el parámetro "numeric" enviado desde xHarbour, y la segunda variable nos servirá para recoger el valor retornado por la llamada a la función en "C" y posteriormente devolverlo en formato xHarbour. 

Como expliqué antes, ServType nos va a servir para recibir el parámetro de entrada, y ulRetVal para regresar el valor devuelto por la función, así que manos a la obra: 

Primero tenemos que "interceptar" el valor enviado como parámetro:

HB_FUNC( ADSSETSERVERTYPE )
{
    int servType;
    UNSIGNED32 ulRetVal = 999999;
    servType = hb_parni( 1 )
}
 

Lo que hicimos fue utilizar una función hb_parXXXX(), en este caso la función hb_parni(), esta función "recupera" el parámentro y en este caso especifico lo convierte en Numeric Integer (ni) parni = parameter numeric integer y lo almacena en servType, ya convertido a un tipo válido de "C". 

La recuperación de los parámetos enviados desde el llamado a la función en xHarbour se realiza entonces con la funcion hb_parXX(), si hay 3 parametros en la función, entonces será 1, 2 ó 3 dependiendo de la posición del parametro, en este caso nuestra función solo tiene un parámetro de entrada, por eso hemos utilizado el número 1. 

Veamos ahora el valor de retorno, quedamos que el valor de retorno también tendría que ser convertido del tipo retornado por "C" a un tipo válido de xHarbour, bien, esto lo hace:

HB_FUNC( ADSSETSERVERTYPE )
{
  int servType;
  UNSIGNED32 ulRetVal = 999999;
  servType = hb_parni( 1 );
 hb_retnl( ulRetVal);
}
 

La función hb_retnl() toma un valor y lo RETORNA previa conversión, en este caso a NUMERIC LONG (nl) retnl = return numeric long

 Para efectos prácticos los tipos numéricos de xHarbour son siempre de tipo LONG para "C". 

Así como está nuestra función en este momento, si la ejecutaramos, obtendríamos este resultado:  

x := AdsSetServerType(4) ? x // 999999  

Siempre retorna 99999 porque NO HEMOS HECHO LA LLAMADA A LA FUNCION EN C QUE NOS INTERESA, así que ahora hacemos la llamada a la función en "C" que curiosamente tiene el mismo nombre:  

HB_FUNC( ADSSETSERVERTYPE )
{
    int servType;
    UNSIGNED32 ulRetVal = 999999;
    servType = hb_parni( 1 );
    ulRetVal = AdsSetServerType( servType );
    hb_retnl( ulRetVal);
}
 

¿ En donde está la función en "C" ? puede estar en una DLL o en un LIB, o bien totalmente escrita dentro del código del wrapper, volveremos a esto mas adelante. 

 Ahora el valor de ulRetVal queda definido por la llamada a la función en "C" AdsSetServerType() la cual recibe como parámetro un valor integer, convertido mediante la función hb_parni. El valor de retorno se convierte en LONG y es devuelto a nuestro programa xHarbour. También podemos contar cuantos parámetros recibe la función mediante la función hb_pcount():  

HB_FUNC( ADSSETSERVERTYPE )
{
 int servType;
 UNSIGNED32 ulRetVal = 999999;
 if( hb_pcount() > 0 )
   {
     servType = hb_parni( 1 );
     ulRetVal = AdsSetServerType( servType );
   }
 hb_retnl( ulRetVal );
}

¡ Y listo !, aquí se ve claramente porque a estas estructuras de programación se les llama "wrappers".

Volviendo al tema sobre donde podemos encontrar funciones en "C" para wrappear, estas pueden venir en archivos .LIB para "C" o en archivos .DLL. 

Si vienen en archivos .LIB no tenemos el menor problema para integrarlas en nuestro programa, simplemente agregamos la LIB al momento de linkar y listo, ya tenemos la librería de funciones integrada con nuestro código xHarbour. 

Si tenemos una DLL de funciones, entonces acceder a ellas desde nuestra aplicación es mucho mas interesante, pero teniendo las herramientas adecuadas es muy simple con (x)Harbour.

Si trabajamos con el compilador C++ de Borland, este tiene una herramienta que te va a permitir hacer algo muy interesante: Crear un archivo .LIB a partir de una .DLL La herramienta en cuestión es un programa llamado IMPLIB.EXE que está en la carpeta \BIN de Borland, su uso es muy sencillo:

 IMPLIB archivo.lib archivo.dll  

Esto genera un archivo .LIB para "C" el cual contiene un "mapa" de las funciones contenidas en el DLL, ojo: NO SON LAS FUNCIONES COMO TALES, simplemente es una librería que te permitirá llamar a las funciones del DLL, por lo tanto deberás linkar la LIB generada con IMPLIB a tu programa, e incluir la DLL con las funciones en la distribución de tu EXE final. 

Recuerda que para poder acceder a las funciones de la LIB tendrás que hacer los wrappers correspondientes y para ello vas a necesitar el manual de las funciones que quieres utilizar, para saber cuales son los parámetros y los tipos de datos retornados para poder realizar correctamente tus wrappers. 

Finalmente, ¿ en donde se coloca el código de los wrappers ?, hay 2 opciones: 

1) Crear un archivo .C y agregarlo al proceso de compilación en "C" de tu programa (x)Harbour
2) Incluirlos dentro de algún archivo .PRG de tu aplicación. 

¿ Código en "C" dentro de un archivo .PRG ? 

Pues va a ser que sí, si es posible hacer esto mediante una estructura de directivas al preprocesador de (x)Harbour:

#pragma begindump
...

#pragma enddump 

(x)Harbour compila en 2 pasos, primero el compilador toma los archivos .PRG y genera código en "C" la directiva #pragma begindump le indica al compilador de (x)Harbour que el código que sigue es código en "C" y que debe ignorar la compilación hasta que encuentre un #pragma enddump, el compilador de "C" se hará cargo mas adelante de compilar este código. 

Así que para incluir wrappers en nuestros .PRG en nuestro código tendríamos algo como esto:  

Static FUNCTION cTempFile(extension)
local cFile
...
...
return cFile
 

#pragma begindump
    HB_FUNC( D2BIN )
    {
        BYTE *Buffer;
   
Buffer = (BYTE *) hb_xgrab( sizeof(double) );
     *( (double *) ( Buffer ) ) = ( double ) hb_parnd( 1 );
  
hb_retclen( ( char *)Buffer, sizeof(double) );
  
hb_xfree(Buffer);
 
}

 HB_FUNC( NLOBYTE )
 
{
         hb_retni( hb_parni( 1 ) & 0x00FF );
 }
 

  HB_FUNC( NHIBYTE )
  {
    hb_retni( ( hb_parni( 1 ) >> 8 ) & 0x00FF );
  }

#pragma enddump 

STATIC FUNCTION FileNoExt(cFileName)
    
LOCAL cVret := ""
    
cVret := SUBSTR(cFileName,1,RAT(".",cFileName)-1)
RETURN (cVret)
 

Si tienes un manual de Clipper, toda la documentación de esto está en el manual del "sistema extendido" el sistema extendido de xHarbour funciona exactamente igual al de Clipper (con mejoras) y algunos cambios en los nombres de las funciones. 

El grado de complejidad que se puede alcanzar con esto es altísimo, puedes hacer wrappers para prácticamente cualquier libreria de "C" y hacerla funcionar con (x)Harbour y todas sus GUIs.

viernes, septiembre 10, 2021

¿ Quien es "alias" ?

Tengo 20 años programando en Clipper, y mas o menos 15 dando clases de programación y análisis y diseño de sistemas, de los cuales 5 estuve dando clases en la Universidad La Salle de México, como catedrático de la Escuela de Ingeniería en la carrera de Ingeniería en Cibernética y Ciencias de la Computación. 

He dirigido algunos trabajos de tesis sobre bases de datos, 2 de ellos específicamente sobre lenguajes Xbase y hasta una tesis sobre programación orientada al Objeto con FiveWin, sí sí, aunque ustedes no lo crean FiveWin fue en su momento (hace como 8 años) el tema de un trabajo de titulación profesional de 2 de mis alumnos. 

El caso es que en todos estos años pegándole a la tecla con Clipper y ahora con (x)Harbour, me he encontrado con que casi nadie saber manejar los archivos DBF en condiciones. Actualmente la idea es ir mas por el tema del SQL, pero hay muchas personas que todavía manejan archivos DBF y que conociendo un poco mas sobre ellos se pueden hacer cosas muy interesante.  

Pero vamos a ver..... ¿ como que nadie sabe usar los DBFs ?, si cientos de programadores del mundo los han usado durante años..... de acuerdo, pero de todos esos cientos de programadores, que levanten la mano aquellos que consideren que saben manejar los DBF BIEN. 

Si levantaste la mano, continúa leyendo, espero poder enseñarte algo nuevo, si no la levantaste, también continúa leyendo, seguro vas a aprender algo nuevo.  

El mito de las 256 "áreas" de trabajo. 

 Los que venimos del dBase III recordamos que en los libros ponían, dBase puede abrir hasta 256 "areas" de trabajo, entendiendo por "areas de trabjo" un espacio de memoria en donde se "abría" la tabla DBF (está mal llamarla "base de datos"), lo que en el SQL moderno de hoy se llama "cursor".

 Estas "areas" estaban numeradas, de tal forma que cuando queríamos abrir una tabla, todos hacíamos (y algunos siguen haciendo) la forma erronea de abrir la tabla:  

SELECT 1 // para seleccionar el "area" donde vamos a abrir la tabla USE archivo.dbf 

Esto quería decir que la tabla ARCHIVO.DBF estaría abierta en el "área" 1, así pues siempre que quisiéramos hacer referencia a los campos o a alguna operación sobre ARCHIVO.DBF, antes que nada tendríamos que hacer primeramente un salto al "área" donde está abierta dicha tabla, algo como esto:

SELECT 1
GO TOP
DO WHILE ! EOF()
    ? Recno()
 
SKIP
ENDDO 

La cosa comienza a ponerse interesante cuando requieres usar mas tablas simultáneamente, digamos unas 5 tablas, entonces erroneamente haces:  

SELECT 1
USE archivo1.dbf
SELECT 2 USE archivo2.dbf
......

SELECT 5 USE archivo5.dbf 

Mal, mal, mal, esto te lleva a hacer lo que yo he llamado "código ping-pong", es decir selección de área, operación, selección de otra área, otra operación, selección de otra area mas, otra operación mas, y luego vamos de vuelta al área original, lo cual nos lleva a un código lleno de SELECTs por todos sitios, que es complicado de mantener. 

Si a esto aunamos que a 16 bits el número de áreas de trabajo estaba limitado por la memoria (las 256 áreas de trabajo que prometía dBase nunca fueron ciertas), entonces nuestro código se volvía un maremagnum de saltos entre áreas, de tener que cerrar tablas para abrir otras en las mismas "áreas" de tener que acordarte en que número de área quedaba tal o cual tabla DBF de tal o cual proceso, vamos, un rollo. 

Y todo por no utilizar el "alias" de los archivos DBF.  

¿ Y quien o qué es ALIAS ? 

En todos los lenguajes de programación no orientados a datos, como Pascal, "C" o el mismo Basic, cuando se manejan archivos de datos, siempre se les asignan 2 nombres, el nombre "físico" que es el nombre del archivo tal cual se almacena en el disco, y el nombre "lógico", que es el nombre con el cual haremos referencia dentro del código de nuestro programa.

Clipper/(x)Harbour no es una excepción a esta regla, porque en realidad, SIEMPRE estas manejando 2 nombres cuando abres una tabla DBF para trabajar sobre ella, es decir, cuando haces algo como esto : 

USE archivo.dbf 

En realidad estas generando un "ALIAS", es decir, un "apodo" para hacer referencia al archivo abierto durante la ejecución de tu programa. 

Si revisas la sintaxis del comando USE, te encontrarás varias cosas interesantes:  

USE [ ; [INDEX ] ; [ALIAS ] ; [EXCLUSIVE | SHARED] ; [NEW] ; [READONLY] ; [VIA ]  

El comando USE tiene una clausula ALIAS, que si no fuera importante, creeme, no la pondrían como parte de la sintaxis. 

Esta clausula ALIAS se utiliza para darle un "nombre lógico" a tu tabla para que puedas manejarla en memoria, si tu omites este nombre, se asigna automáticamente como "alias" el mismo nombre de la tabla, sin extensión. 

 El ALIAS de una tabla es una pieza muy importante de la programación de datos porque si sabes como utilizarlo, te ahorrarás lineas y líneas de SELECTS inútiles. 

Existe un "operador ALIAS", representado por un "->" signo de menos + signo de mayor que, este operador nos servirá para hacer referencia al área de trabajo que deseamos usar sin necesidad de tener que hacer un SELECT 

Por ejemplo:  

USE archivo1.dbf
archivo1->(dbgotop())

DO WHILE ! archivo1->(EOF())
  
? archivo1->(RECNO())
   archivo1->(DBSKIP())

ENDDO

En este ejemplo asumimos que no hemos usado la cáusula ALIAS, luego entonces el ALIAS asignado por omisión será "archivo1" (sin extensión), luego usaremos el operador "alias" (->) para indicar que operaciones deben realizarse sobre él.  

Nota por favor que todas las operaciones sobre la tabla son funciones NO COMANDOS y que todas las operaciones van encerradas entre paréntesis. 

Otra cláusula importante del comando USE es la claúsula NEW, esta clausula te ahorrará miles de lineas de SELECTS ya que esta clausula abre la tabla DBF en la primer área que se encuentre disponible, es decir, no tienes que hacer un SELECT previo a abrir un DBF, simplemente le pones NEW al momento de abrir, y dejas que Clipper/(x)Harbour se haga cargo del resto. 

 USE archivo1.dbf NEW
USE archivo2.dbf NEW
 

No tienes que hacer ningun SELECT para abrir las tablas, ahora, para hacer referencia a los campos de cada archivo, o bien realizar operaciones sobre las tablas, tampoco hay necesidad de hacer SELECT, el ALIAS nos soluciona la vida: 

USE archivo1.dbf NEW
USE archivo2.dbf NEW
archivo1->(DBGOTOP())
archivo2->(DBGOBOTTOM())
DO WHILE ! archivo2->(BOF())
   ? archivo2->(fieldget(1))

       ? archivo1->(fieldget(5))
       IF ! archivo1->(EOF())
      archivo1->(DBSKIP())

       ENDIF
       archivo2->(DBSKIP(-1))
ENDDO 

El manejo de los campos es muy simple, funciona igual, basta con que pongas el nombre del campo despues del operador alias SIN PARENTESIS:  

? archivo1->nombre
? archivo2->iva
 

Los campos también son asignables usando el operador alias, tanto para leer sus valores, como para actualizar o agregar valores, sustituyendo con esto al comando REPLACE:  

cNombre := archivo1->nombre
archivo2->(DBAPPEND()) // el comando APPEND BLANK se sustituye pro su función

archivo2->nombre := cNombre // no hay REPLACE, el valor se asinga directamente
 

Esto de los alias puede ser tan complejo como quieras, un ejemplo típico es buscar en una segunda tabla el valor de un campo contenido en la primera: 

CON SELECTS:  

SELECT 1
cNombre := field->nombre

SELECT 2 SEEK
cNombre
 

CON ALIAS:  

archivo2->(DBSEEK(archivo1->nombre)) 

Efectivamente, con la línea anterior, basta con que hagas referencia a los alias y en vez de usar 4 líneas de código en una sola solucionas el problema y haces exactamente lo mismo. 

Ahora esto del alias es mas versátil porque tu puedes dar TU PROPIO ALIAS  

USE archivo1.dbf ALIAS MiAlias NEW
MiAlias->(DBGOTOP())
? MiAlias->nombre
 

Usando tu propio ALIAS puedes darle un nombre mas identificativo al manejo de tus tablas DBFs. 

Veamos otro ejemplo mas complejo pero sin duda mucho mas útil:  

USE archivo.dbf ALIAS ArchPant SHARED NEW
USE archivo.dbf ALIAS ArchRep SHARED NEW

USE archivo.dbf ALIAS ArchMovs SHARED NEW
 

NO, No hay ningún error, estoy abriendo LA MISMA TABLA (archivo.dbf) 3 veces AL MISMO TIEMPO..... ¿ que no causa esto un error del tipo ALIAS already in use ?.... pues va a ser que no.... y esto es algo de lo mas alucinante y útil de los archivos DBFs. 

Una de las cosas que casi nadie usa es esta poderosa característica de los DBFs, poder abrir la misma tabla 2, 3, 4, o "n" veces al mismo tiempo, esto se logra CAMBIANDO EL ALIAS y abriendo la tabla como compartida (SHARED) como si fuesemos a programar para red. 

Lo interesante de esta forma de usar los DBFs es que cada "alias" mantiene su puntero independiente, es decir, puedes hacer algo tan loco como esto:  

ArchPant->(DBGOTOP())
ArchMovs->(DBGOTO(5))
ArchRep->(DBGOBOTTOM()) 

El puntero de cada "alias" se moverá a distintas posiciones, sin importar que estemos hablando del mismo archivo DBF (recuerda, una cosa es el nombre físico y otra es el nombre lógico).

Y si tu preguntas los valores de los campos :  

ArchPant->nombre
ArchMovs->nombre
ArchRep->nombre 

Seguramente obtendrás valores distintos en cada oportunidad, esto porque sin importar que sea el mismo archivo, para efectos prácticos en la memoria de la computadora esta "abierto" 3 veces bajo 3 alias distintos y cada alias guarda y mantiene su propio estado. 

Solo como comentario, Xailer a través de los DataSets facilita aun mas el manejo de los alias. 

Pero volviendo al tema, para los que no tienen Xailer o tienen la versión personal, aun así podemos seguir haciendo cosas interesantes a base de usar solo los alias: Si yo hago:  

ArchMovs->(DbAppend())
ArchMovs->nombre := "Juan Perez"
ArchMovs->direccion := "Da igual donde viva, Viva El"
ArchMovs->(DBCommit())
ArchMovs->(DBUnlock()) 

El registro añadido será visible EN TODOS LOS ALIAS QUE APUNTEN AL MISMO ARCHIVO FISICO, esto es maravilloso, porque es como dar de alta al mismo tiempo en 3 tablas distintas. 

La pregunta que puede surgir en este momento es .... ¿ y esto como para que me serviría en la vida real ?.... pues para facilitarte la existencia: 

Supongamos que tu quieres mostrar en pantalla un browse del archivo de clientes, donde tienes además 2 índices, uno por clave y otro por nombre, y que además quieres hacer operaciones de mantenimiento como altas bajas y cambios y para rematar quieres hacer reportes que pueden ir ordenados por clave o por nombre, entonces haríamos algo como esto:  

USE clientes ALIAS CliBrow SHARED NEW
CliBrow->(OrdSetFocus("nombre")) // arrancamos ordenado por nombre del cliente
CliBrow->(DBGOTOP())

USE clientes ALIAS CliMovs SHARED NEW
CliMovs->(OrdSetFocus("clave")) // esta segunda instacia del archivo va ordenado por clave

USE clientes ALIAS CliReps SHARED NEW

CliRep->(OrdSetFocus("clave") // idem anterior. 

Si yo quiero meter la primer instancia del archivo de clientes a un browse, basta con hacer: 

CliBrow->(Browse())  

¡¡¡Así de simple !!!!! 

El usuario podrá moverse sobre el browse y podrá cambiar los criterios de ordenación sin afectar a los otros 2 alias que apuntan a la misma tabla. 

Ahora viene lo bueno, si yo quiero hacer un reporte o hacer un ABM sobre la tabla clientes NO LO HAGO SOBRE EL ALIAS DEL BROWSE, lo hago ya sea sobre el alias CliMovs o sobre el alias CliReps para no molestar al alias del browse. 

Cuando trabajas con un solo alias, y quieres dar un mantenimiento a una tabla mostrada en un browse, entonces tienes que "guardar" el estado del área de trabajo que muestras en el browse, es decir, en que registro estás, sobre que indice estas trabajando, etc. y luego hacer las operaciones de mantenimiento sobre ese mismo alias que pueden vincular, mover el puntero sobre la tabla, agregar un registro nuevo, cambiar el orden del índice actual, realizar búsquedas, etc, y luego de la operación, debes regresar todo a como estaba antes. 

Esta técnica te evita eso precisamente. 

 Por ejemplo, si tu quieres agregar un registro el código sería algo como esto:  

CliMovs->(RLOCK())
CliMovs->(DBGOBOTTOM())
nSiguienteClave := CliMovs->clave+1
CliMovs->(DbAppend())

CliMovs->clave := nSiguienteClave
CliMovs->nombre:= cVariableConElNuevoNombre
CliMovs->(DbCommit())
CliMovs->(DBUnlock()) 

Nota que toda la operación de mantenimiento la hacemos sobre el Alias CliMovs, no sobre el Alias CliBrow, de esta manera no tenemos que guardar posiciones ni estados, porque CliBrow no se va a mover su puntero y su índice se quedan como están, en cambio CliMovs hará toda la labor de mantenimiento y el cambio se verá reflejado automáticamente en CliBrow y en CliRep en cuanto haya terminado la actualización CliMovs, el Browse ni si quiera se va a mover, a menos claro, que la operación de mantenimiento afecte al registro en el cual estaba el puntero del alias CliBrow. 

Lo mismo pasa para los reportes, si yo quiero un reporte, usualmente ese reporte abarca un rango de datos, para no molestar al Browse ni a su alias, pues el reporte lo emitimos sobre el alias CliRep:

CliRep->(OrdSetFocus("nombre"))
CliRep->(OrdScope(0,"A")) // reporte de todos los clientes cuyo nombre empiece con "A"
CliRep->(OrdScope(1,"A"))

CliRep->(DBGOTOP())
DO WHILE ! CliRep->(EOF())
 
... aqui val el codigo del reporte...
    CliRep->(DBSKIP())
ENDDO 

Y el CliBrow ni se va a enterar que hay un alias llamado CliRep con un Scope sobre la tabla moviendo el puntero para generar un informe.
 

Esta técnica de programación es ideal para Windows, sobre todo si estás trabajando en entornos MDI porque puedes asignar a cada ventana hija los alias que vaya a necesitar para trabajar, en los entornos MDI una de las cosas mas complicadas de manejar es precisamente esa, evitar que el puntero en la ventana "A" se mueva cuando en la ventana "B" se está realizando otra operación sobre la misma tabla DBF. 

Puedes incluso abrir todas las tablas al momento de arrancar tu programa, porque en (x)Harbour no existe la limitación de las 256 "areas" de dBase, en (x)Harbour prácticamente no hay limite en la cantidad de archivos que puedas abrir simultáneamente. 

En Xailer Pro y Enterprise no tienes los problemas de Alias cuando trabajas en entornos MDI porque Xailer te provee de los DataSets que usualmente ya van "atados" a cada formulario y como expliqué antes, te ahorran el tema de tener que lidiar con alias, en FiveWin o en la versión Personal de Xailer, el uso de esta técnica es indispensable para trabajar con entornos MDI, en (x)Harbour modo consola, esta ténica te puede ayudar a eliminar muchos vicios de programación y a ahorrar muchas líneas de código. Cabe señalar que esto mismo es aplicable a Advantage DataBase Server, de hecho el manual de ADS recomienda que todas las tablas estén abiertas desde que arranca la aplicación, esto es aplicable tanto a archivos DBF con NTX o CDX o bien a tablas ADT con índices ADI. 

 En un próximo artículo discutiremos otra técnica relacionada con esta llamada "alias dinámicos" que hará que no tengas que preocuparte prácticamente por nada que tenga que ver con el acomodo de las tablas en memoria.

miércoles, febrero 10, 2021

Que alguien me explique.... porque no lo comprendo

Como diría mi admirado Condorito (saludos Chile) ¡ Exijo una explicación !. 

Me gustaría saber.... ¿ porque muchos de los programadores de (x)Harbour y sus entornos visuales quieren usar una base de datos basada en SQL ? ..... 

¡¡¡¡ SI NO VAN A USAR EL SQL !!!!!!! 

¡ Que alguien me explique por favor ! 

 Abundan las preguntas en los foros del tipo: 

¿ como uso MySQL con xHarbour ?, ¿ Puedo leer tablas de Oracle con xHarbour ?, Quiero cambiar de base de datos y usar SQL Server en vez de DBFs, ¿ como lo hago ?, estas preguntas se repiten a puños en los foros..... tooooodo el mundo quiere usar un SQL ¡ Pero muchos programadores lo quieren usar o lo usan como si fueran DBFs ! como sucedería en la tira cómica de Condorito .... ¡ PLOP !

Que alguien me explique porqué, si el producto se llama MySQL, SQL Server, Postgre SQL,..... ¿ porqué "·%&·$"!"·@# lo quieren trabajar como si fuera un DBF y no por SQL? 

Y me respondo yo mismo: porque no conocen el lenguaje SQL, lo cual me parece increíble ya que SQL es un lenguaje de programación que se basa básicamente en 7 instrucciones, no entiendo entonces como un programador de (x)Harbour que se aprende mas de 20 comandos de Xbase, no puede aprender a usar 7 estúpidas instrucciones: SELECT, INSERT, UPDATE, DELETE, DROP, CREATE, ALTER. 

¡ Que alguien me explique ! Es como comprarse un Ferrari sin saber conducir ni tener licencia, o lo que es peor, es comprarse el Ferrari y pretender conducirlo como si fuera una bicicleta, porque no sé conducir un coche, pero la bicicleta la llevo bien. 

Lo peor de todo es que (x)Harbour fomenta seguir trabajando del modo equivocado con herramientas como el RDDSQL o el ADORDD.... vamos a ser serios por favor, si vamos a clavar un clavo en la pared, vamos a usar un martillo, no la suela del zapato, si es una herramienta SQL, vamos a trabajarla como SQL, no con parches ni apaños con el pretexto de "no conozco el lenguaje SQL", aprender SQL no tiene la menor complicación, unas 3 tardes de lectura y listo, no digo que te vas a volver un experto, pero si podrás comenzar a explotar a SQL. 

Hoy por ejemplo estaba revisando el foro de (x)Harbour y un colega preguntaba porqué no le funcionaba un SET RELATION con el RDDSQL y MySQL..... ¿ perdón ?..... ¿ como para qué tendrías que hacer un SET RELATION con MySQL si con un simple query (instrucción SQL) puedes obtener el resultado que necesitas ?, eso no es matar pulgas a cañonazos, es matar microbios con misiles. 

Los SQL son bases de datos RELACIONALES, es decir, las relaciones son parte de su naturaleza y las pueden resolver directamente, tú no tienes porqué hacer el trabajo del servidor, si no haz aprendido eso, apaga y vámonos, regresa a los DBFs, aprende a usar SQL y cuando sepas SQL, regresa, no te vas a arrepentir, pero por favor, pretender trabajar un SQL como si fuera un DBF es un desperdicio de recursos, una pérdida de tiempo, de líneas de código y de esfuerzo, créeme por mas que lo intentes, el mar no cabe en un vaso de agua, solo cabe un poquito. 

Si estás decidido a aprender SQL, te advierto una cosa.....SQL es una tecnología que causa adicción, es como el iPod, una vez que te enganchas..... a ver como te lo quitas de encima.... si no me crees, trabaja un mes con SQL, y te aseguro que nunca mas querrás volver a usar un DBF, cualquiera que haya hecho el cambio no me dejará mentir. 

Quizá la parte mas complicada de trabajar un SQL es como leer el "cursor" es decir, como acceder a los datos devueltos por la ejecución de un query, para hacer las cosas mas simples de entender, un "cursor" equivale al "alias" de un DBF, y para ello, ADO (Activex Data Objects) se pinta solo, conjunta lo mejor de 2 mundos: el Poder del SQL, con la facilidad de uso del modelo Xbase, automatizando mucho del trabajo de acceso a los servidores de datos. 

ADO tampoco es complicado de entender, son 3 objetos fácilmente accesibles desde xHarbour por medio de OLE (Object Linking and Embeding), si quieres saber mas sobre ADO, mi amigo Gabriel Maimò tiene estupendos artículos al respecto en su blog: BielSys

Así que para que gastar en herramientas externas como RDDSQL, Mediator, etc, cuando puedes usar todo el poder del SQL con un rato de estudio y además gratis usando (x)Harbour y te funciona tanto para modo consola como para entrono gráfico. 

Otro punto importante cuando trabajas con una base de datos SQL: Las bases de datos de este tipo son poderosas por la arquitectura sobre la cual están diseñadas: son Cliente / Servidor, ojo a la ultima palabra: SERVIDOR. 

Mientras te comienzas a sentir cómodo con el SQL, entiendo que programes los queries (instrucciones SQL) como parte de tu código fuente, pero eso solo será al principio, la idea es PONER A TRABAJAR AL SERVIDOR (por eso se llama Cliente / Servidor), para ello se inventaron los "Procedimientos almacenados" ó "Stored Procedures", ¿ que son ?, pues son como "funciones" de Clipper, pero que están escritas en SQL, y están almacenadas en el servidor como parte de la estructura de la base de datos, esto facilita muuuuuuucho la programación del SQL, porque no tienes mas que llamar a los procesos almacenados desde tu código fuente con parámetros, si tu quieres hacer algún cambio en un proceso almacenado, no tocas el código fuente de tu programa (x)Harbour, lo modificas directamente en el servidor de la base de datos, eso es parte de la "arquitectura de 3 capas". 

Resumiendo: Si estas usando un SQL Cliente / Servidor (= 100%) , sin la parte del SQL ( -50%), ni la parte del Servidor (-25%), lamento informarte, que estás usando solo un 25% de todo lo que podrías hacer si supieras explotar toda la herramienta, ahora imaginate, si estás usando SQL Server, sin parte del SQL ni la parte del Server......pues nos ha jodido, ¡ No estas usando nada ! y si no estás usando nada ...... ¿ para que quieres usar un SQL ?. 

¡ Que alguien me explique !

Programar bien y programar bonito

Buena pregunta..... 

¿ Tu programas bien o programas bonito ?

 ¿ Tienes estándares de programación ? 

¿ Haces códigos fuentes de menos de 1000 lineas ? 

¿ Usas notación húngara ? 

¿ Tus tablas están diseñadas siguiendo el modelo Entidad-Relación ? 

¿ Los nombres y tipos de tus campos son coherentes entre las tablas que los usan ? 

Eso en cuanto a programar bien, ahora en cuanto a programar bonito 

¿ Comentas tus códigos fuentes ? 

¿ Identas correctamente el código ? 

¿ DISEÑAS TU INTERFACE CORRECTAMENTE ? 

En este último punto es donde todos fallamos, me incluyo. Hace muchos años, durante la época del MS-DOS, realmente no existía un estándar para desarrollar una interfaz con el usuario, así que teníamos la libertad de hacer lo que nos diera la gana al momento de diseñar la interfaz de nuestros programas, eso era el salvaje oeste, y lo que para algunos podría parecer un asco de interfaz, para otros era útil, sencilla o práctica o simplemente los usuarios se han acostumbrado a ella, pero en 1987 las cosas cambiaron radicalmente. 

En 1987 IBM propuso un estándar de interfaz con el usuario a la que llamó CUA (Common User Access), el estándar CUA es (se sigue usando y tu lo usas todos los días aunque no lo sepas) un conjunto de especificaciones detalladas y reglas estrictas sobre la apariencia y la manera en que las aplicaciones deberían de funcionar, este fue el primer intento de poner en orden a las aplicaciones basadas en DOS, que como comenté anteriormente, hasta ese momento implementaban diferentes interfaces con el usuario. IBM adoptó este estándar en todas sus aplicaciones y sistemas operativos, y también lo hizo Microsoft, así que, mi querido lector, tu usas CUA todos los días cuando trabajas con Windows, ya que las interfaces de este sistema operativo están ampliamente basadas en CUA. El estándar CUA, a pesar de tener 33 años de edad, especifica cosas haces todos los días en tu trabajo diario, y que los programas que tu desarrollas deberían tener, como por ejemplo:

  • Todas las operaciones se deben poder hacer con el ratón, pero también con teclado, sin intervención del roedor.
  • Las opciones del menú se deben abrir usando la tecla Alt+Letra subrayada
  • Un grupo de opciones de un programa debe ser capturado usando dialogos, no ventanas
  • Las opciones en un diálogo se dividen usando pestañas, no se hacen dialogos larguísimos que se tengan que desplazar para capturar todas las opciones necesarias
  • La navegación dentro de un campo en un diálogo es con las flechas del cursor, la navegación entre los campos hacia adelante se hace con la tecla Tab; la tecla Shift+Tab navega hacia el campo anterior.
  • Los diálogos deben tener siempre un botón CANCELAR cuya operación debe relacionarse con la tecla ESC, y siempre se debe usar para descartar los cambios realizados en los campos capturados dentro del diálogo.
  • Los diálogos deben tener siempre un botón ACEPTAR, el cual acepta los cambios realizados en el diálogo y esta asociado siempre con la tecla ENTER
  • Todas las aplicaciones deben tener una opción de AYUDA en el menú, esta opción debe ser SIEMPRE la última del menú, la ayuda en línea para un caso específico debe ser invocada por la tecla F1
  • La primera opción del menú debe ser SIEMPRE la opción ARCHIVO, seguida siempre por la opción EDITAR
  • El comando CORTAR son las teclas SHIFT+DEL, COPIAR son las teclas CTRL+INS y PEGAR son las teclas SHIFT+INS
  • etc., etc., etc.

Detengo la lista hasta aquí porque estoy seguro que TOOOODOS tus programas siguen al pie de la letra el estándar CUA, el cual, dicho sea de paso, hace que tu aplicación califique como "compatible con Microsoft Windows". 

Seguramente tu no eres de esos que pone degradados de colores en los fondos de sus programas, no no no no, ¿ como lo vas a hacer ?, eso no está documentado en CUA, ni pones botones de 30 colores diferentes con bitmaps y seguramente sabes usar perfectamente una RibbonBar, no la pones en tus programas nada mas porque la acaban de incluir en la última versión de tu herramienta de programación o porque viene en Office 2007, estoy seguro que sabes como dividirla y cuando usar cada componente, después de todo programas BIEN para Windows ¿ no ?. 

Bueno, como todo lo anterior es cierto (programas BIEN en Windows), gracias por leer este artículo, nos veremos próximamente con algo más interesante, ¡ Hasta la próxima ! 

.... 

.... 

.... 

.... 

¿ Como ?...... 

¿ sigues ahí ?..... 

Ok, ok, si sigues ahí es porqué seguramente te sentiste identificado con algo que mencioné que tu NUNCA haces en tus programas. 

Hace unos días estaba mirando en Facebook las imagenes que puso un colega programador de su aplicación...... oh my dog !.... con RibbonBar, OutlookBar, Menú, todo metido en la ventana principal, dialogos con fondos degradados, mensajes por todos lados, imagenes de distintos estilos por todos sitios.... y lo peor, los mismos colegas programadores comentado ....oooohhh que bonita aplicación......

 No digo que la aplicación no se viera bonita, pero hay una línea muy fina entre lo bonito y lo grotesco, la verdad no sabría yo calificar si eso era bonito, o era grotesco. 

Hasta ahora no he visto ninguna aplicación, salvo el WinZip o el Office 2007 que utilicen una RibbonBar como se debe y son muy pero muy muy muy pocos programas que me ha tocado usar, especialmente de desarrolladores independientes y aún de empresas que se dedican al desarrollo de software que pasarían una certificación de Microsoft. 

No basta con que tu programa corra en Windows con una interfaz gráfica, tampoco basta decir que tu programa es "Compatible con Microsoft Windows" (y encima nos damos el lujo de decir con Windows XP y superiores 32 o 64 bits), hay que demostrar que tu programa realmente es 100% Windows compatible. 

Para que un programa sea 100% Windows compatible, tiene que seguir el estricto estándar que Microsoft ha impuesto para el desarrollo de aplicaciones Windows compatibles. 

Cada vez que Microsoft libera una versión de Windows, también publica una "guía" de la interacción que el usuario debe tener con el sistema operativo, elegantemente le llama "User Experience Interaction Guidelines" y está disponible en el MSDN como archivo PDF, te voy a ahorrar la molestia de buscarla, pero vas a tener que seguir leyendo hasta el final. 

En este documento, de 867 páginas y 41 Mbytes de tamaño, Microsoft explica todos los controles que se usan en la interfaz de Windows, así como también explica detalladamente como y cuando deben de ser utilizados, como deben ser emplazados dentro de un diálogo o ventana, que distancia debe de existir entre ellos. 

Otra cosa que también documenta es la lista de "violaciones" mas comunes que hacemos los programadores cuando estamos diseñado la interfaz de nuestros programas, como por ejemplo no usar textos en color azul porque el usuario asume que son links de internet, siempre coloca un boton CANCELAR, usa tooltips solo cuando el control no tenga etiqueta, asigna aceleradores a todas la etiquetas de controles, el orden de TAB siempre debe ser de izquierda a derecha y de arriba hacia abajo, en los mensajes de tus programas no uses las palabras ERROR, FALLA, ILEGAL, VIOLACION, ABORTAR, FATAL, eso espanta a los usuarios, etc. etc. etc. 

Lo que mas me gustó de la guía es que incluye una sección sumamente detallada del uso de controles, como usarlos adecuadamente, cuando tal control puede ser reemplazado por otro de otro tipo, cuando NO usar tal o cual control, baste con decir que la guía es tan detallada que solo la sección que trata del Checkbox ocupa 5 paginas, indicando con todo detalle ejemplos y casos de uso, solo como comentario, ¿ Sabias que la distancia que debe existir entre un LABEL y un CHECKBOX es de 5 pixeles y que la distancia entre cada CHECKBOX cuando hay varias opciones debe ser de 7 pixeles y que el tamaño del control debe ser de 10 pixeles de altura ?, bueno así como se detalla el Checkbox, se detallan el resto de los controles. 

Mención por separado merece la documentación de la creación y uso de la RibbonBar, esta si es una obra maestra de la documentación, en 33 páginas te documenta, detale a detalle, cada elemento de una RibbonBar, y la forma correcta de usarlo, una vez que leas esta documentación te darás cuenta si realmente la RibbonBar está bien soportada e implementada en tu lenguaje de programación. 

A estas alturas te podrías estar preguntando ¿ y realmente hay programas que respeten todo lo que dice el documento ?.... ¡ pues claro que los hay !, TODOS los programas que vienen en el sistema operativo de Windows y en genera TODOS los programas de Microsoft son diseñados siguiendo estrictamente las reglas de la UX Guide. 

 La UX Guide la puedes descargar haciendo Click aquí. La mala noticia es que está todo en inglés y no existe versión en Español, por lo menos hasta el día en que descargué el documento, de todas formas la guía incluye una estupenda información que te ayudará a hacer programas mas poderosos y bonitos. 

Ahora sí, programarás bien y programarás bonito.

Si programas no vendas, si vendes no programes

A raíz de los comentarios publicados en el post anterior por varios de ustedes, me tomo la libertad de compartir mis experiencias en este mundillo de comprar y vender software. 

Yo empecé como desarrollador en el año 1989, cuando aun la computadora no era un electrodoméstico mas y costaban mas o menos el precio que un viaje de 15 días de México a Europa. 

Como los equipos eran caros, pocas personas o empresas contaban con ellos, por lo tanto hacer software hace 20 años era un estupendo negocio. 

Hoy las cosas han cambiado y mucho, la computadora ha dejado de ser un objeto de lujo para convertirse en un artículo de consumo, pero lo que no ha cambiado es que siempre, al final de la cadena hay un usuario final que tiene necesidades de cubrir al cual le importa un cacahuate (o al menos debería importarle un cacahuate) en que está hecho el programa que usa, lo importante es que le resuelva el problema. 

Yo catalogo a las empresas de software en 3 tipos en base a su tiempo de vida: 

1) Empresas con buenos programadores pero con malos o ningún vendedor, son las que duran menos.

2) Empresas con buenos vendedores y con programadores regulares o malos. Que duran un poco mas que las anteriores, pero tampoco mucho mas. 

3) Empresas que equilibran buenos vendedores con no menos estupendos programadores, que son las que están "condenadas al éxito". 

Veamos el caso 1. Juanito programador decide lanzarse a la aventura de vender sus productos de software, es un programador extraordinario, en lo que sea, no vamos a etiquetar a ningún lenguaje, hace programas maravillosos, útiles y funcionales, pero no sabe vender, después de todo es su primer experiencia vendiendo sus productos, tampoco tiene dinero para contratar vendedores o publicidad, así que se lanza a hacer un software business do-it-yourself. 

Este tipo de empresa está condenada al fracaso en corto tiempo, ¿ Porqué ? porque Juanito programador tendrá que lidiar con 2 problemas: buscar clientes para sus productos, y encima crear y darles mantenimiento a los mismos (de hecho no es recomendable tener mas de un producto si es tu primera incursión en el mundo del negocio del software), con lo cual, como no puede hacer bien una cosa ni la otra, Juanito conseguirá 2 o 3 clientes, pero deberá de seguir dándole manteniendo al software y por conservar a sus 2 o 3 clientes les va a cumplir cuanto capricho le pidan, después de todo, son funcionalidades que quizá algún futuro cliente (que nunca va a llegar) le pida.

En poco menos de 1 año este tipo de empresa bajará la cortina, porque no es rentable, el super programador Juanito buscará un trabajo regularmente pagado, pero por lo menos llegará a fin de mes. 

El caso 2 tiene un poco mas de tiempo de vida, o al menos resultará economicamente viable, en este caso tenemos a Pedrito Vendedor, el comercial mas fiero, que le vende refrigeradores a los esquimales del Polo Norte y calefacción a los beduinos del Sahara, Pedrito sabe vender, tiene los contactos, la personalidad, el carisma y ese "algo" que tiene el vendedor nato, pero no tiene conocimientos de informática, computación o programación, sin embargo, sabe vender, así que ubica algún programa informático interesante (ojo, he dicho interesante, no he dicho BUENO o UTIL, o PRACTICO), contacta con el autor, llega a un acuerdo, y se lanza a comercializarlo. 

Como Pedrito tiene conocimientos de publicidad y una red de contactos en poco tiempo ese producto ha llegado a un montón de personas, ha producido una buena utilidad peeeeeroooo... el producto no era ni tan bueno, ni tan útil, ni tan práctico como Pedrito vendedor lo había dibujado y lo peor de todo, el programador tampoco era tan bueno. 

Viene ahora corregir todas las quejas, bugs, fallas, etc. y todos sabemos que la cantidad de quejas es proprocional a la cantidad de productos vendidos, por lo tanto, Pedrito se verá agobiado de quejas de sus clientes, le pedirá al programador que haga algo para solucionar los problemas, pero como el programador no es muy bueno entonces pasará de hacerlo, se contratará a otro programador para que ayude al autor, sin embargo como el autor es un mal programador no lo entiende nadie, y tarde o temprano la empresa cierra porque no hay quien se haga responsable del mantenimiento del programa. 

En este tipo de empresas rara vez la versión 2.0 del producto llega a ver la luz, sin embargo algo de dinero hizo (después de todo vendió montones de humo), así que, algo se ganó con la experiencia, Pedrito Vendedor ahora venderá GPS's, cepillos de dientes o cualquier otra cosa, y venderá montones, porque lo suyo es vender. 

El balance perfecto lo tiene aquella empresa que sabe que es tan importante tener buenos vendedores y no menos mejores programadores, conozco muchos caso de empresas que son rentables (y muy rentables) con un cuerpo de 4 o 5 buenos programadores (o menos) y un equipo de fieros vendedores y mercadologos, después de todo, como siempre he dicho, la característica mas importante de un programa, aparte de tener una bonita interfaz, ser robusto, fácil de usar y práctico es ..... que los posibles clientes lo conozcan..., si el producto no lo conoce nadie, entonces de nada vale toda la tecnología que tenga dentro ni todo el tiempo invertido. 

¿ Recuerdas el caso de un individuo llamado Bill Gates ?, sin duda un buen vendedor, consiguió un buen producto, el famoso MS-DOS y era tan buen vendedor que se lo vendió a IBM.... el resto es historia. 

Aparte de tener vendedores buenos y de invertir en publicidad, una empresa de software sin importar su tamaño deber saber que su materia prima es el programador, es su activo mas importante, le deben su existencia y por lo tanto, la estrella de la empresa debe ser el programador, por lo mismo hay que tenerlo mimado, contento y bien alimentado, si queremos tener productos finales de calidad, la materia prima debe ser de también de buena calidad. 

La semana pasada aprovechando una visita que por motivos de trabajo tuve que hacer al norte del país, tuve el gusto de cenar un par de veces con un estupendo amigo de muchos años, programador y dueño de su empresa de software. Me dió muchismo gusto verlo y ver lo ha hecho gracias al software (hecho en (x)Harbour por cierto), pasó a buscarme en un cochazo ultimo modelo, fuimos a cenar a unos sitios magnificos, y me comentó una cosa que me hizo pensar.... - a mi que me dejen programar, y que los vendedores hagan su trabajo -, -yo-, me dijo,- no vendo, para eso tengo vendores, yo programo, lo mio es hacer el software y que los vendedores hagan su trabajo -, siguiendo esta filosofía mi amigo ha logrado tener mas del 60% del mercado del software de punto de venta de su región, y ha penetrado el mercado de Estados Unidos con presencia en ciudades como San Diego y Los Angeles y el único producto que tiene es una Terminal Punto de Venta. 

¿ Cual es el secreto de este tipo de empresas de software exitosas ?, no es el precio del producto, definitivamente, lo que pasa es que han encontrado un NICHO de negocio, un software de alta especilización para un mercado específico. 

¿ Que no hay montones de TPV en el mercado ?, si, hay muchos, pero como dice mi amigo... - yo a mis clientes le vendo la experiencia que he tenido con todos mis otros clientes -, así por ejemplo tiene versiones de su Punto de Venta específicamente diseñadas para carnicerías, con control para básculas, para farmacias, con un catálogo precargado de mas de 5 mil medicamentos, lo que le ahorra al usuario tener que cargar los productos, versiones para puestos de periódicos y revistas, para dulcerías, panaderías, versiones especiales para cadenas de supermercados, etc. aún dentro de un área de software específica es posible encontrar aún mas especialización. 

Conozco otra empresa de software donde saben que sus programadores son sus estrellas, y el dueño (que también conoce de programación) es un vendedor nato, basta escucharlo hablar de las bondades de su producto para enamorarse de él (del producto). 

Una de las cosas que mas admiro de esta empresa es que no se cortan un pelo en comprar las herramientas que les piden sus programadores: pagan cursos, adquieren editores, reporteadores, bases de datos, lenguajes, equipos de escritorio y portátiles, todo de última generación para que el programador no se detenga y además se encuentre a gusto (eso sin contar el aire acondicionado las oficinas de cada uno) ¿ será por eso que sus programadores tienen mas de 10 años con ellos ?, y no son "todologos", solo hacen un software de nómina y solo eso. Y no son empresas del extranjero como Google o Fog Creek Software, son empresas donde trabaja algún colega programador que conoces muy bien, solo que estas empresas han entendido la importancia del programador. 

Aún siendo programador independiente, definitivamente te debes mimar y tratar de tener las mejores herramientas para realizar mejor tu trabajo, no pretendo que seas Stefan Didak, pero si es importante que tengas un espacio adecuado donde te sientas a agusto y la musa de la programación pueda correr libremente. 

 ¿ Que tal un comedor en la empresa con horno para asar un cordero incluído y una estupenda colección de vinos ? ¿ o una zona de descanso con máquinas expendedoras de café, refrescos y golosinas para que no tengas que salir a la calle ? ¿ Que me dices de unas oficinas en Cancún ? ¿ o programar tranquilamente sentado en una cafetería mirando al mar ? ¿ que tal tener vacaciones familiares en Orlando, Las Vegas o Nueva York cuando sientas que las necesitas ?, o poder tener un iPad o un MacBook Air como equipos de "capricho" ¿ que tal un BMW Serie 5 o un Mercedes descapotable afuera de tus oficinas ?, si crees que no hay programadores que hagan o tengan eso, estas muy muy muy equivocado, todas las descripciones anteriores corresponden a empresas donde yo he estado y veo como cuidan a sus programadores, lo mas curioso de todo, es que en todas esas oficinas y negocios que parecen salidos de un cuento de hadas informático programan en (x)Harbour y quizá alguna vez te hayas topado con algún programador que trabaja ahí ya sea en el messenger o en un foro. 

¿ Donde está el secreto ?.... podría resumirlo en lo siguiente: 

1) Venden software que cubre una necesidad muy especifica. 

2) Están enfocados en un nicho del mercado muy particular, es decir, con muy pocos competidores. 

3) Sus programas funcionan, hacen lo que tienen que hacer, y lo hacen bien, porque tienen buenos programadores. 

4) Generan muchas ventas porque tienen un equipo de vendedores comprometido, que sabe lo que están vendiendo. 

5) Cuentan con herramientas y tecnología, los programadores no tienen que estar perdiendo el tiempo buscando el crack de tal o cual programa, porque si lo necesitan, simplemente lo compran y así terminan antes. 

6) El medio ambiente de trabajo es agradable. y lo mas importante, que es donde se genera el flujo económico para la empresa: 

7) Le solucionan un problema al usuario final. 

Podría seguir hablando de estrategias para hacer rentable un negocio de software, creo que sería un buen tema de un futuro post de O.P. 

Moraleja: Si sabes programar pero no vender... ni lo intentes, si sabes vender pero no programar... tampoco lo intentes.

jueves, marzo 07, 2019

Imagenes y campos memo, guia de supervivencia con ADS

Mis chicos favoritos, como casi todo el mundo sabe, es la gente de Sanrom's Software de México, con quien me une algo mas que una amistad de muchos años.

Hace un par de años, por estas fechas, concluíamos la migración a 32 bits de sus productos de control escolar, con la promesa de que la nueva versión estaría hecha en Xailer, desafortunadamente nos ha ganado el tiempo, ha llegado el momento de lanzar una nueva versión, que aunque no será en Xailer, si tendrá mejoras significativas a la versión de 32 bits y aunque ya casi está terminado el primer producto Sanroms hecho con Xailer, aún no ha sido probado a conciencia, con esto en mente, la prudencia aconseja seguir por el mismo camino que a la fecha nos ha dado resultado.

En el proceso de migración cambiamos de tabla de datos, de DBF/CDX, se decidió cambiar al formato nativo de Advantage Database Server: ADT/ADI por varias razones, primero por la tecnología cliente/servidor, segundo por la seguridad de la información y tercero por desempeño de la aplicación, hoy, a 2 años de haber lanzado la primera versión con ADS, yo creo que la mas estable hasta el momento en cuanto a manejo de tablas, no hemos tenido ningún "susto" ya se que los usuarios trabajen con el servidor local o bien tengan un servidor remoto instalado.

Uno de los grandes atractivos de esta nueva revisión, es la capacidad de tener acceso a los datos vía Internet, esta mejora se hizo por la necesidad de algunos colegios con varios planteles remotos de acceder a los datos centralizados, en esta nueva revisión se hizo la adaptación al software para que hiciera uso del Advantage Internet Server.

La implementación del acceso remoto no fue complicada, en realidad a parte del diccionario de datos, poco mas se tuvo que mover del programa original, el verdadero problema se presentó cuando hubo que manipular imágenes.

Los sistemas Sanrom's tienen ya muchos años en el mercado, programados originalmente en FiveWin 2.0 a 16 bits y con Clipper 5.3 e índices CDX, la manipulación de las imágenes, en este caso las fotografías de alumnos y maestros, se hacia mediante un campo cadena de caracteres que hacía referencia a un archivo de imagen (.bmp, jpg, gif, etc) dentro del disco duro del servidor, esto no planteaba problema alguno en la red de área local, porque la imagen se lee por referencia al archivo, pero para Internet si que presenta un problema, ya que usando el AIS, no es posible acceder al disco duro remoto, únicamente a los archivos de datos ADS.

Para que el sistema funcionara eficientemente sobre Internet, había que añadir la imagen un registro de alguna tabla, ya no por referencia, es decir, ya no como una ruta a un archivo, sino que había que poner el archivo de imagen completo dentro de un campo de la base de datos, a fin de que la imagen estuviera disponible en el equipo cliente tanto si estuviera conectado en la red de área local, como si estuviera conectado por Internet, el mecanismo de despliegue en pantalla se explicará mas adelante.

Bien, la primera aproximación fue trabajar en un campo memo, en ADS, al igual que con DBF, el campo memo se almacena en un archivo independiente a la tabla de datos, con la extensión ADM, en nuestro primer intento tratamos de hacer algo como esto

field->campomemo := MemoRead("\\servidor\recurso\directorio\imagen.jpg")

Es decir, directamente, leer el archivo como si se tratara de un texto y guardarlo en el campo memo correspondiente, mala idea, mas adelante veremos porqué.

Para el despliegue de la imagen en pantalla, había que generar un archivo de imagen a partir del dato almacenado, ya que la clase TIMAGE de FiveWin, solo puede hacer visualización de una imagen que esté almacenada en un recurso, o bien de un archivo de imagen que exista en disco (nota: Xailer puede visualizar las imágenes leídas directamente del campo, sin necesidad de crear un archivo a disco.), para lo cual, la ruta mas práctica aparentemente era:

MemoWrit("imagentemp.jpg",field->campomemo)

Sin embargo, al hacer el despliegue en pantalla del archivo "imagentemp.jpg", esta aparecía cortada, solo aparecía aproximadamente una tercera parte de la imagen, ya que el proceso de MemoRead() no marcaba ningún error, y aparecía un valor en el campo, no pensamos que la lectura y guardado de la imagen, como un texto, en un campo memo, pudiera estar causando problemas, antes bien, pensábamos equivocadamente que el proceso de escritura era el que estaba dañando la imagen.

Después de darle mucho la vuelta (toda una tarde) y buscar por el lado equivocado, el de la escritura, decidimos hacer un "back to the basics" y analizar el proceso de lectura, ¡ oh sorpresa !, nos pusimos a comparar los tamaños en bytes del archivo original, contra el LEN(MemoRead(...)) y ambos valores eran el mismo, por decir algo, 90 kbytes, el problema venía en la asignación al campo memo, cosa que descubrimos cuando al hacer un LEN(field->campomemo), nos devolvió 30 kbytes, cuando, si el proceso de guardado hubiese hecho bien todo, este valor tendría que ser 90 kbytes, por esta razón al hacer el MemoWrit(), solo aparecía una tercera parte de la imagen.

Dependiendo de la imagen, algunas veces lo hacía bien, y otras lo hacia mal, cuando la imagen media menos de 30 kbytes, hacía el guardado y el despliegue en pantalla perfectamente, pero cuando era mayor de este tamaño, la imagen quedaba truncada a 30 kbytes.

Evidentemente el formato de almacenamiento estaba fallando, así que Israel y yo nos pusimos a estudiar un poco el tema y encontramos que dentro de ADS, para el tipo de tabla ADT, los campos memo puede ser de 3 tipos:

Memo - para almacenar solo texto, igual que en un DBF
Binary - para almacenar cualquier cosa binaria, por ejemplo un documento de word
Image - para almacenar, como su nombre lo indica, una imagen, el formato es lo de menos.

Con esto en mente, procedimos a hacer el cambio de tipo de campo, modificando la estructura de la tabla, y cambiando el campo tipo MEMO por IMAGE, una cosa que descubrimos fue que cuando se define un campo del tipo Binary ó Image dentro de la estructra del archivo ADT, al momento de crear la estructura nueva, los campos se "renombran" como de tipo "BLOB" (Binary Large OBject), así que para efectos de definición se pueden declarar de 3 formas, como IMAGE, como BINARY o bien directamente como BLOB, que es otro tipo válido de campo en una tabla ADT de Advantage.

Una vez redefinido el tipo, procedimos a hacer lo que estábamos haciendo anteriormente, es decir, field->campoblob := Memoread().... otro error, cuando intentábamos hacer esta asignación el sistema marcaba un error de Field type mismatch, es decir, el tipo de campo no coincide con el tipo de variable que se le está asignando, lo cual es entendible, el campo es tipo BLOB, y le estabamos tratando de meter una cadena de caracteres, es como si trataras de meter una cadena en un campo numerico o fecha o lógico, sin embargo, el tipo BLOB no existe en (x)Harbour, ¿ que podíamos hacer entonces ? ¿ como transformar una cadena de caracteres para que sea reconocida como BLOB ?.

De vuelta al manual de ADS, y encontramos que en ADS existen un par de funciones que quizá nos podía ayudar: AdsBinaryToFile() y AdsFileToBinary(), así que vimos sus parámetros, las metimos en el código fuente, y nos llevamos una desliusión cuando vimos aparecer el temido "Unresolved external HB_FUNC_ADSFILETOBINARY()", que indica que dichas funciones no estaban definidas dentro del RDDADS.LIB, ¿ que acaso nadie había trabajado con blobs antes con ADS ?

Antes de ponernos a pegarle la "C" para hacer wrappers para llamar a las funciones que nos faltaban, se nos ocurrió revisar el código fuente del RDDADS, mismo que se puede encontrar dentro de las contribuciones de xHarbour, y en el archivo ADSFUNC.C encontramos 2 funciones que nos salvaron de estar perdiendo el tiempo con código en "C": AdsBlob2File() y AdsFile2Blob(), estas funciones son wrappers muy elaborados para las funciones AdsFileToBinary() y AdsBinaryToFile(), solo que sus parámetros son mas simples de manejar.

La sintaxis es:

AdsFile2Blob(,,)

Donde:

es el nombre del archivo que deseamos leer
es el nombre del campo blob donde se guardará el archivo leido
es el tipo de campo (imagen o binario) que esta definido en una constante predefinida dentro del archivo ACE.H y que es:

#define ADS_BINARY 6 /* BLOB - any data */
#define ADS_IMAGE 7 /* BLOB - bitmap */


Con lo cual, en vez de estar haciendo experimentos con el MemoRead() un simple:

AdsFile2Blob("nombredelaimagen.jpg",campoblob,ADS_IMAGE)

Se encargaron de todo, la imagen se lee perfectamente y se guarda en el campo sin perder un solo byte, lo anterior es válido no importa que tipo de campo blob tengas, solo tienes que cambiar el tercer parámetro, lo mismo puede ser una imagen, en cuyo caso usaremos ADS_IMAGE, que un documento de word o una hoja de excel, para los cuales usaremos ADS_BINARY.

Para la operación contraria, es decir, crear un archivo de imagen a partir del campo blob, utilizamos la función contraria cuya sintaxis es:

AdsBlob2File(, )

De tal forma que un simple

AdsBlob2File("imagen.jpg",campoblob)

Crea el archivo que necesitaremos para visualizar con FiveWin, sin necesidad de estar enrredando con MemoWrit().

Una vez descubiertas estas 2 funciones, el resto fue fácil, para cargar todas las imagenes dentro del campo de la base de datos hicimos:

USE alumnos
GO TOP
DO WHILE ! EOF()
AdsFile2Blob(alumnos->foto, alumnos->imagen, ADS_IMAGE)
SKIP
ENDDO

Donde el campo alumnos->foto era el que contenía una cadena de caracteres con la dirección del archivo de foto, y alumnos->imagen es el campo blob que contendrá la imagen dentro de la tabla.

Eso si, después de hacer la importación de foto de un colegio de 2,500 alumnos, el archivo memo ADM quedó de 170 megas de tamaño, mas grande que el archivo ADT, que contiene los datos, tal ves pudieras pensar que con ese tamaño la tabla sería lentísima de acceder a los datos, pues va a ser que no, la tabla tiene el mismo rendimiento que si no tuviera las imágenes pegadas, los indices se generan a la velocidad de ADS, vamos que no perdemos nada de performance por tener las imágenes como parte de la tabla de datos.

Para el acceso via internet tampoco tenemos problema, porque la imagen se genera dentro del disco duro de los clientes y se borra cuando se ha dejado de utilizar, con lo cual podemos enviar esos archivos de imagenes a los equipos clientes sin necesidad de que tengan acceso al disco del servidor, cosa que por internet puede ser lentisima.

Eso nos abre nuevas posiblidades de trabajo, ahora podemos añadir practicamente cualquier tipo de archivo como un campo de la base de datos, eso me esta dando nuevas ideas.....

martes, septiembre 12, 2017

Usando SQL con Advantage de la manera fácil


Una de las grandes carencias del lenguaje XBase es la capacidad de hacer queries (búsquedas) sobre una o varias tablas DBF extraer los resultados y mostarlos todos en una "vista", en XBase hay que hacer ese trabajo a mano, mediante índices, SET RELATION, SCOPES incluso SET FILTER y otras cosas mas. El resultado no siempre se obtiene de la manera mas rápida ni con la menor cantidad de tiempo invertida en la programación, si tuviéramos un SQL, la cosa resultaría mucho mas rápida.

Desafortunadamente el modelo XBase tal cual como lo conocemos ha sacrificado el modelo "relacional" por el modelo "navegacional", dando preferencia a mostrar las tablas en listados en vez de tratar de obtener la menor cantidad de información posible para mostrar en la pantalla, como lo hace SQL.

Si pudieramos tener el poder del modelo navegacional del XBase, de la mano del poder relacional del SQL, sobre nuestros veteranos archivos DBFs, pues otro gallo nos cantaría podríamos trabajar y obtener resultados en menos tiempo y realizar complejísimas búsquedas sobre uno o mas archivos DBF, relacionar sus campos y obtener un resultado rápido y ordenado de un criterio de búsqueda dado.

¿ La solución ?, ADS de la mano del RDDADS de xHarbour.

Lo que vamos a analizar a continuación está disponible tanto con el servidor local como con el servidor remoto de ADS, pero solo puede usarse con el RDDADS de (x)Harbour a 32 bits, lo siento chicos de Clipper, el cliente ADS para Clipper no soporta el uso de SQL. (En los próximos días publicaré un artículo para que todo el mundo le pierda el miedo a (x)Harbour, de la mano con una sorpresa por parte del Equipo Xailer).

Manos a la obra, ¿ qué necesitamos ?:

1) Un (x)Harbour cualquier versión (también funciona con cualquier interfaz grafica: Xailer, FiveWin, VisualxHarbour o Harbour MiniGUI)
2) El RDDADS.LIB y el ACE32.LIB para linkear a nuestro EXE
3) Las DLLs del cliente ADS (ACE32.DLL, AXCWS32.DLL y ADSLOC32.DLL para el servidor local)
4) Un código fuente .PRG con las instrucciones correspondientes.

Modo de preparación:

Antes de meternos en el mundillo del SQL con ADS, tenemos que analizar un poco como es que funciona un motor de datos SQL.

En un sistema gestor de bases datos relacional, como MySQL, SQL Server, y el mismo ADS, el concepto de "BASE DE DATOS" cambia con respecto a lo que conocemos como "BASE DE DATOS" en el modelo XBase. Mientras que en el modelo XBase tradicional erroneamente le llamamos "base de datos" a UN archivo o tabla .DBF, en el modelo relacional una base de datos es una COLECCION de tablas, indices, usuarios, esquemas de seguridad, vistas, procesos almacenados, etc.

En todos los productos basados en SQL, es necesario realizar una CONEXION a la base de datos, esto se hace comúnmente mediante un Middleware como ODBC ó ADO y mediante una serie de instrucciones desde el lenguaje de programación que realizan la conexión primero con el servidor de base de datos y segundo con la propia base de datos, para tener acceso a los datos almacenados en sus tablas.

El modelo cambia un poco dentro de ADS, ya que ADS soporta 2 tipos de conexiones, lo que conocemos como TABLAS LIBRES, es decir archivos DBF o ADTs individuales que manipulamos usando nuestro lenguaje XBase tradicional, o bien un modelo de conexión mas aproximado a lo que es el modelo relacional, que es el modelo basado en DICCIONARIO DE DATOS.

Un DICCIONARIO DE DATOS para ADS es similar a una base de datos de un SQL, es decir, un DD (Diccionario de Datos) guarda referencia a las tablas DBF o ADT individuales, y al mismo tiempo guarda el esquema de seguridad de los usuarios, procesos almacenados, integridad referencial, triggers, replicación y en general todo lo que puedes esperar de un verdadero sistema gestor de base de datos relacional.

Usar DDs con ADS tiene grandes ventajas contra el modelo de tabla libre, como por ejemplo el poder mover la seguridad de la base de datos a ser controlada por propio servidor, en vez de tener que controlarla por programa, el evitarte tener que abrir todos las tablas individualmente una a una, ya que en el modelo tradicional XBase hay que hacer un "USE" por cada tabla que quieras usar, con un diccionario no, simplemente conectas desde tu programa con tu diccionario de datos, y listo, todas tus tablas están disponibles sin necesidad de tener que saber en que directorio las tienes, el DD se encarga de todo.

Pues bien, siguiendo el modelo relacional, para poder utilizar un querys con ADS, lo primero que tenemos que hacer es realizar una CONEXION con la base de datos, si tengo un diccionario de datos no tengo ningún problema, el RDDADS provee de la función ADSConnect60() que me permite "conectar" a un DD desde mi programa xHarbour....

¿ Pero que pasa si no tengo DD y estoy trabajando con tablas libres ?

No hay ningún problema ADS reconocerá como "base de datos" al contenedor de dichas tablas libres, y el "contendor" de las tablas libres es, simplemente la carpeta o directorio del disco duro donde se encuentran alojados los DBFs con sus NTX o CDX o bien los ADTs con sus respectivos ADIs.

¿ Cómo hacemos entonces para "conectar" con una carpeta del disco duro ?

El RDDADS de (x)Harbour provee de la función ADSConnect() mediante la cual podremos "conectarnos" con un directorio del disco duro, simplemente indicando la ruta donde se encuentran nuestros DBF o ADTs dentro del disco duro:

ADSConnect("C:\programas\datos\")

Si queremos saber si ya hay una conexión previamente establecida , utilizamos la función ADSConnection(), de tal forma que podríamos hacer algo como esto:

IF ADSConnection() == 0
ADSConnect("C:\programas\datos")
ENDIF

Ya "conectamos" con nuestra "base de datos" (directorio donde están los archivos), ¿ que sigue ahora ?

El siguiente paso es "preparar" la ejecución del query SQL, para ello vamos a utilizar la función ADSCreateSQLStatement() a la cual pasaremos 2 parametros: el primero es el nombre de un "alias" (si si, a lo Xbase, un alias) para el resultado del query (lo que en SQL se llama "cursor") y el segundo parámetro es el tipo de tabla sobre el cual estamos trabajando, siendo los valores válidos : 1 = DBFNTX, 2 = DBFCDX, 3 = ADTADI, así que la preparación de nuestro query quedaría así:

ADSCreateSQLStatement("mialias",2) // 1= DBFNTX 2 = DBFCDX 3=ADTADI

Y ahora si, podemos ejecutar el Query que nosotros queramos, de acuerdo a las instrucciones y funciones SQL soportadas por el motor SQL de ADS llamado Xtremeline SQL, esto lo haremos mediante la función ADSExecuteSQLDirect():

ADSExecuteSQLDirect("Select nombre, dire, tel from clientes order by nombre where nombre like '%Juan%'")

Ahora viene lo sorprendente: El resultado de este query ¡¡¡¡ SE COMPORTA EXACTAMENTE IGUAL QUE SI FUERA UN DBF !!!!!, de tal forma que tu podrías hacer algo como esto:

mialias->nombre := "Juan Pérez"
mialias->(DBGOTOP())
DO WHILE ! mialias->(EOF())
...
...
...
mialias->(DBSKIP())
ENDDO

¿ Y si quiero "browsear" el resultado de esto ?

Con xHarbour modo consola muy fácil:

mialias->(Browse())

y con Xailer, basta pegar el ALIAS a un objeto DBBrowse:

::oDBBrowse1:SetDBF("mialias")

En ninguno de los 2 casos necesitas cambiar los bloques de código que definen la navegación porque ADS "engaña" a xHarbour mediante el RDDADS, haciendole creer que lo que está visualizando es un DBF y no el resultado de un Query SQL ;-).

Cuando hallas terminado de utilizar este query, pues simplemente haces:

mialias->(DBCLOSEAREA())

Y listo, ya puedes ejecutar otro query de SQL.

El ejemplo completo para (x)Harbour en modo consola sería algo como esto:

Function Main
REQUEST ADS
RDDSETDEFAULT("ADS")

ADSSetFileType(2)
ADSSetServerType(7)

IF ADSConnection() == 0
ADSConnect("C:\programas\datos")
ENDIF

ADSCreateSQLStatement("mialias",2) // 1= DBFNTX 2 = DBFCDX 3=ADTADI

ADSExecuteSQLDirect("Select nombre, dire, tel from clientes order by nombre where nombre like '%Juan%'")

CLS

mialias->(DBGOTOP())
DO WHILE ! mialias->(EOF())
? mialias->nombre, mialias->dire, mialias->tel
mialias->(DBSKIP())
ENDDO

mialias->(DBGOTOP())
mialias->(BROWSE())

RETURN

Los queries pueden ser tan complejos como quieras, pudiendo incluso ejecutar procesos almacenados, puedes incluso realizar proceso "ciegos" como INSERT INTO, UPDATE o DELETE, solo que estas instrucciones no devolverán un cursor y por lo tanto no podras visualizar nada del resultado de estas instrucciones SQL.

He creado un pequeño manual con las instrucciones SQL soportadas por el XtremeLine SQL de ADS junto con sus funciones, este manual lo he extraído de mi libro de Advantage Database Server y de lo puedes descargar haciendo click aqui.

jueves, mayo 25, 2017

De Clipper a (x) Harbour con XEdit

Este artículo me lo han pedido muchísimos programadores de Clipper, y hasta ahora había tenido tiempo de escribirlo con calma, de hecho me ha llevado poco mas de una semana.

Lo que explicaré a continuación es la forma fácil y rápida de migrar tus programas CA-Clipper a (x)Harbour, con las menores molestias posibles, utilizando una herramienta muy interesante : XEDIT.

XEdit es una herramienta hecha en Xailer, de hecho, XEdit es un buen ejemplo de lo que se puede lograr programando con Xailer, pero además es un prototipo de lo que después se convertiría en el IDE de Xailer.

El Equipo de Xailer ha decidido mejorarlo y dejarlo como un producto individual independiente de Xailer para ayudar a los programadores novatos en (x)Harbour en los procesos de compilación y enlazado de sus aplicaciones, tanto para modo consola, como para Windows hechas con FiveWin, así es..... puedes usar XEdit para compilar y enlazar tus programas FiveWin. ;-)

Si trabajas con XEdit para migrar tus programas de Clipper a (x)Harbour, posteriormente la migración de tus programas a Windows con Xailer te será mucho mas fácil, ya que XEdit comparte muchas de las características de su hermano mayor: El IDE de Xailer, como son: El Gestor de Proyectos, El Editor de código fuente, El Depurador visual (solo para aplicaciones modo Consola, no para aplicaciones Windows, la depuración para aplicaciones Windows solo está disponible en Xailer), El Explorador de DBFs (DBU) y El Editor de SQLite., estos componentes no los voy a explicar en este artículo, pero haz click sobre los enlaces anteriores y obtendrás una descripción detallada de cada uno.

Antes de comenzar debo aclarar que todo lo aquí expuesto está basado en XEdit 5.0.4 de Noviembre 2017, más adelante encontrarás información sobre como obtenerlo.

Bien comencemos.

Los que ya llevamos una temporada trabajando con (x)Harbour sabemos que si bien el compilador es poderoso y versátil, no es nada fácil el proceso de compilación y enlazado de los programas, sobre todo porque tenemos por medio un compilador de C, del cual poco o nada sabemos, y es en este punto es donde el proceso de compilación y enlazado del EXE final se vuelve terrible.

Recordemos como hacíamos un programa EXE en Clipper:

archivo.prg -> archivo.obj -> archivo.exe

Escribimos nuestro código fuente en archivos .PRG, luego con el compilador Clipper compilamos esos archivos .PRG y obtenemos archivos .OBJ, posteriormente tomamos esos archivos .OBJ, y por medio de un enlazador como RTLINK, BLINKER, EXOSPACE etc, uníamos los archivos .OBJ con los archivos de librerías de Clipper (clipper.lib, extend.lib, terminal.lib, dbfntx.lib) y el resultado del proceso de enlazado o "linkeado" era un archivo .EXE.

En (x)Harbour las cosas son "un poquito" mas complejas:

archivo.prg -> archivo.c ->archivo.obj -> archivo.exe

En (x)Harbour tomas tu código fuente en archivos .PRG (el mismo código fuente que tienes en Clipper en este momento, los cambios que tienes que hacer son mínimos), en vez de utilizar el compilador Clipper compilamos con el compilador Harbour (los parámetros de compilación exactamente los mismos que Clipper), pero el resultado de la compilación NO SON ARCHIVOS .OBJ, SON ARCHIVOS ".C", efectivamente, Harbour "traduce" tu código Clipper a código en lenguaje "C", que no es en realidad un código "C" estándar, vamos que si eres experto en "C" y editas el contenido de un archivo resultado de la compilación con Harbour, el código te parecerá mas Ensamblador que "C", esto es porque el código generado es un "PCODE", es decir, un código que solo puede ser interpretado por un motor especial llamado "PMACHINE", la "PMACHINE" para interpretar este PCODE viene en las librerías de Harbour que tienes que enlazar en el archivo .OBJ para generar el archivo .EXE.

Un momento.... entonces sí deben de existir archivos .OBJ en alguna parte del proceso de compilación, ¡ claro que existen !, los archivos .OBJ provienen de compilar el código en "C" para ello necesitas un compilador de C, y aquí es donde empieza la fiesta porque actualmente (x)Harbour es compatible con varios compiladores de "C" como el Microsoft Visual C++ desde la versión 6 hasta la versión 8, el Pelles C, el MingW32 ó el Open Watcom, pero la versión mas popular es la desarrollada para Borland C++, ya que Borland ha liberado al dominio público sin restricciones de uso el compilador Borland C++ Ver. 5.5.

Otra ventaja adicional, es que al generar (x)Harbour código en "C", entonces puedes hacer programas con el lenguaje Clipper que ya conoces para otros sistemas operativos, como Linux, MacOS, OS/2 de IBM, MS-DOS de 32 bits (DR-DOS), eso sí, no serán programas gráficos con ventanas ni nada (Windows forms), serán programas MS-DOS (para modo consola). Para cada uno de estos sistemas operativos existen versiones de (x)Harbour disponibles, obviamente los compiladores de C para estas plataformas son distintos a los usados para aplicaciones Windows.

Retomando el tema, el Compilador de "C" volverá a compilar nuestros archivos generados con (x)Harbour, y ahora sí, generará los archivos .OBJ, posteriormente esos archivos .OBJ hay que "linkearlos" junto con las librerías de (x)Harbour para generar el archivo .EXE final. Todos los compiladores de "C" incluyen su propio enlazador (linker), por lo que no requieres ningún otro producto adicional, así que te puedes olvidar de Blinker, Exospace, etc.

El verdadero problema de generar un EXE con (x)Harbour viene en la parte de proceso de compilación con "C", y es aquí donde XEdit entra en acción.

Así que lo que primero que necesitamos para migrar nuestras aplicaciones es conseguir xHarbour, aunque xEdit soporta compilación con Harbour, también lo puede usar con xHarbour, por otro lado siendo el Borland C++ el compilador de C preferido por los programadores, entonces necesitaremos obtener una versión de xHarbour para Borland C++. Puedes ir directamente a la pagina de  xHarbour.org para descargar la versión para Borland C++.

No hay proceso de instalación, simplemente descarga este archivo .ZIP y desempácalo en una carpeta que se llame de preferencia c:\xharbour (los ejemplos que se mostrarán mas adelante los he construido basándome en esta ubicación).

Luego vas a necesitar el compilador C++ de Borland (ahora Embarcadero Technologies), el uso de este compilador es gratuito, pero no es de libre distribución, tienes que obtenerlo desde la página que Embarcadero ha establecido para tal fin, previo registro. Puedes acceder a la página para obtener el Borland C++ haciendo click aquí.

El archivo que se descarga se llama FreeCommandLineTools.Exe simplemente ejecútalo y realizará la instalación del compilador en la carpeta c:\Borland.

Finalmente debes descargar la ultima version de XEdit de:


Descarga XEdit desde la web de Xailer


Xedit viene con un programa de instalación con lo cual solo tendrás que ejecutarlo e instalarlo en tu disco duro, no hay nada mas que hacer.

A continuación te explicaré los primeros pasos en el uso de XEdit, que deberán ser suficientes para poder compilar cualquier aplicación Clipper actual a 32 bits con xHarbour.

Ejecuta el archivo XEDIT.EXE desde la carpeta donde lo haz instalado, y procederemos a configurar el comportamiento básico de la herramienta:

Selecciona:

Menú Principal / Herramientas / Opciones Generales

Aparece una ventana como esta:


Esta pantalla solo contiene configuraciones básicas que no te afectarán para tu programa, excepto la opcion que dice "Xailer". No es necesario que tengas Xailer instalado, configura los directorios a donde se encuentre XEdit, si es que el sistema no lo ha hecho por default.


Para poder compilar un programa, necesitamos crear un entorno de programación. Un entorno de programación es una configuración especial donde le indicaremos a XEdit las carpetas adicionales y las opciones de los compiladores tanto de xHarbour como de Borland, necesarias de acuerdo a cada necesidad específica, por ejemplo, para compilar una aplicación en (x)Harbour para modo consola se necesitan ciertos parámetros de compilación, mientras que para compilar una aplicación para Windows hecha con FiveWin, se necesitan incluir algunas librerías y definir la ubicación de los archivos de cabecera.

Para crear un entorno:

Menú Principal / Herramientas / Entorno de programación.

Puedes tener tantos entornos de programación como necesites, por ejemplo para xHarbour en modo consola, para FiveWin, para Harbour MiniGUI, etc.

Para agregar un nuevo entorno de programación haz click en el botón con el signo "+" que se encuentra en la parte inferior de la plantalla de configuración de entornos:



Lo que sigue es simple, solo tienes que darle un nombre a tu entorno y si lo requieres puedes copiar otro entorno similar para que no tengas que volver a dar todos los datos:



Una vez que haz dado nombre a tu entorno, procederemos a configurarlo con las opciones que muestran los folders de la pantalla de configuración de entorno de programación:

General:

Sirve para indicar las rutas a los archivos INCLUDE y LIB así como los archivos LIB adicionales que necesita tu aplicación.

En el caso de una aplicacion (x)Harbour para modo consola simple y llana, no sera necesario llenar ningun dato de esta pestaña, si tu aplicación es con Harbour MiniGUI o FiveWin, aquí deberás indicar la ruta a donde se encuentran los archivos de cabecera (include) y la ruta a la ubicación de las librerías adicionales que necesite tu aplicación.

Por ultimo para esta pestaña, deberás indicar EL NOMBRE DE LOS ARCHIVOS .LIB que utilice tu programa, EN EL FORMATO DEL COMPILADOR DE C QUE VAYAS A USAR. En el caso del Borland C++ simplemente se indican los nombres de los archivos anteponiendo un signo de "+" y separandolos por comas.

Este es un ejemplo de la configuración para FiveWin:

 
Compilador xBase:

En esta pestaña configuraremos el compilador de (x)Harbour que deseamos utilizar.

Oficialmente existen 3 versiones actualmente de (x)Harbour, el xHarbour como tal, que como recordarás es un "fork" del proyecto Harbour original, del cual no han hecho muchos cambios ultimamente y 2 versiones de Harbour, la 2.x  la 3.x, ¿ Cual es la diferencia ?.

En su momento un grupo de programadores por diferencia de opinión con otros programadores del proyecto Harbour original, decidieron crear su propia versión de Harbour, con miras a crear un producto comercial al que llamaron xHarbour (eXtended Harbour), e incluyeron en el muchas monerias que no encontrarás en Harbour como las estructuras SWITCH, el manejo de archivos XMLs, los "hashes", etc.

Luego vino la gente de Harbour y dijo.... nos estamos quedando atrás y evolucionarion el compilador a la versión 3 incluyendo muchas cosas de lo que xHarbour tenía, optimizando ademas los nucleos y haciendolo mucho mas rapido que xHarbour, además de tener versión de 64 bits, cosa que xHarbour no tiene.

Regresando a nuestro tema, en esta pestaña tendrás que configurar el compilador de (x)Harbour que desees utilizar:


En la parte superior, en los radio botones puedes seleccionar la versión de (x)Harbour que desees usar, y automaticamente en la parte inferior se añadirán las librerias necesarias dependiendo de la versión que vayas a utilizar. Lo unico que tienes que configurar es la ruta donde está tu compilador de (x)Harbour.

TIP: Hay muchas versiones de Harbour, muchas versiones de Xailer o de FiveWin, y en muchas ocasiones hay determinadas versiones del compilador que funcionan con tal o cual versión de otros productos, un consejo práctico: coloca la versión de Harbour compatible con tu herramienta en el mismo directorio donde tienes tu herramienta principal, esto te ahorrará muchos dolores de cabeza en el futuro.


Compilador de C:

(x)Harbour puede usarse con distintos compiladores de C, los mas populares: El Borland C++ porque fue con el que empezó todo y MinGW porque es un compilador de C totalmente open source, le sigue el Pelles C porque es gratuito y compatible con el Microsoft C++.

Quiero hacer aquí un paréntesis para mencionar que aunque no utilices el compilador de C de Pelles C, es conveniente descargarlo porque te ofrece 2 herramientas que si programas en FiveWin o MiniGUI te serán de gran utilidad:

El editor de recursos, que si estas acostumbrado a usar el Borland Resource Workshop y que no funciona en sistemas operativos de 64 bits, lo puede sustituir perfectamente a 32 bits y 64 bits.

El compilador de recursos PORC que además de ser muy rapido, opitimiza perfectamente el ejecutable final.

Puedes descargar el Pelles C haciendo click aqui

Dependiendo del compilador de C que vayas a utilizar las opciones se cargan automaticamente, solo tienes que establecer la ruta donde se encuentra tu compilador, en este ejemplo he seleccionado el compilador C++ de Borland:


Quedan 2 pestañas por analizar que no utilizaremos en esta primera aproximación a XEdit:

Compilador de recursos: Se utiliza en caso de que quieras usar otro compilador que no sea el del C++ nativo que vayas a usar, por ejemplo el PORC de Pelles C.

Enlazador: Similar a lo anterior, si no quieres usar el enlazador (linker) de tu compilador de C++ puedes usar esta pestaña para configurar otro distinto.

Una vez que hemos creado nuestros entornos de programación, hemos terminado con el proceso de configuración, ahora llegó el momento de comenzar a migrar nuestros programas a 32 bits.

Antes de comenzar, tenemos que entender como trabaja XEdit.

XEdit trabaja en base a "proyectos", un proyecto no es mas que una carpeta del disco duro que a su vez tiene subcarpetas donde se guardan los distintos componentes de la aplicación, por ejemplo el código fuente va en una carpeta, los archivos de cabecera .ch en otra, se hace otra para los archivos de recursos .rc, y así sucesivamente.

Para crear un proyecto nuevo:

Menú principal / Archivo / Nuevo Proyecto....

Puedes crear carpetas nuevas para tus proyectos desde la ventana donde se te pide que le des un nombre al proyecto, personalmente lo que yo hago es crear una carpeta llamada PROYECTOS debajo de la carpeta XEDIT2 y ahí voy creando subcarpetas para guardar cada proyecto.

Creemos pues la capeta "PRUEBA1" y pongamosle el mismo nombre a nuestro proyecto.

La configuración de cada proyecto se guarda en archivos XPJ (Xailer/Xedit ProJect) que no son mas que archivos .XML con información sobre el proyecto que estamos usando.

Pulsamos el botón de ACEPTAR y aparece esta ventana, que configura las propiedades del proyecto:


Aquí indicaremos si queremos crear un EXE, una LIB o una aplicación WEB (de momento no disponible), una descripción del aplicativo que estamos desarrollando, misma que se "pegará" al EXE final, deberemos indicar el nombre del archivo de salida, en este caso prueba1.exe y MUY IMPORTANTE debemos indicar el ENTORNO DE PROGRAMACION, previamente creado, que vayamos a utilizar para compilar este programa, en este caso "FW xHB", también deberemos indicar el nombre del módulo principal, pero de momento dejaremos en blanco este espacio, presionemos el botón ACEPTAR para guardar los cambios (puedes volver a esta ventana posteriormente desde Menú Principal / Proyecto / Propiedades del proyecto).

Al presionar el botón Aceptar XEdit te pondrá el siguiente mensaje de aviso:



Indicándote que se van a crear subcarpetas para guardar los distintos componentes de tu aplicación, simplemente haz click en el botón "SI".

Ya estamos listos para hacer un pequeño experimento, vamos a crear un pequeño .PRG de ejemplo y a compilarlo usando xEdit.

Seleccionaremos:

Menu Principal / Archivo / Nuevo / PRG.

Veremos en el editor de código fuente que aparece una pestaña nueva con el nombre MODULE1.PRG, escribiremos lo siguiente y guardaremos este código fuente :



Como verás es un programa Clipper puro y duro, con instrucciones básicas de modo consola.

Llegó el momento de compilar este programa, simplemente selecciona:

Menu Principal / Proyecto / Compilar
ó presiona Ctrl+F9.

Los mensajes del compilador los verás en la parte inferior de la ventana del editor de código:



Si la última línea es 1 Files, X Warnings, 0 Errors .... ¡ Felicidades ! haz logrado compilar tu primer programa a 32 bits.

Para ejecutarlo, haz doble click sobre él en el explorador de Windows y verás aparecer una ventana de MS-DOS con esto:



Para compilar mas de un .PRG, tienes que hacer lo siguiente:

Copia todos los .PRG que vayas a compilar a la carpeta SOURCE, y luego selecciona:

Menu Principal / Proyecto / Añadir Fichero al proyecto

Selecciona todos los .PRGs que necesites, realiza esta misma operación si tienes archivos .CH que desees integrar en tu sistema, estos los deberás colocar en la carpeta INCLUDE de tu proyecto.

Si en alguno de tus programas utilizas una FUNCTION MAIN() entonces en las propiedades del proyecto (Menu principal / Proyecto / Propiedades del Proyecto) en la opción de MODULO PRINCIPAL selecciona el PRG que tiene la función MAIN(), si no tienes función Main en algunos de tus programas, entonces selecciona el .PRG que arranca tu aplicación.

Para compilar todos los programas simplemente presiona Ctrl + F9.

Si estás programando con FiveWin, entonces hay que cambiar algunas cosas y añadir otras:

Los .PRGs para un programa con FiveWin se agregan al proyecto de la misma manera que como se hace para un proyecto para xHarbour modo consola.

Para integrar los recursos gráficos (archivos .RC, .BMP, .ICO, CUR, etc.) al proyecto XEdit, procederemos la siguiente manera:

Primero tenemos que crear un archivo de recursos vacío, esto se hace usando el Gestor de Recursos de XEdit, para activarlo selecciona:

Menu principal / Ver / Gestor de recursos

Y aparecerá la siguiente pantalla:



Presiona el botón que tiene hoja en blanco, del lado izquierdo de la pantalla, ese botón sirve para crear un archivo de recursos .RC vacío, aparecerá una ventana para dar nombre al archivo de recursos, esta pantalla te sugiere el nombre del .RC, en este caso será el mismo nombre que el de tu proyecto, pero mi sugerencia es: dale el mismo nombre que el archivo .RC que utilizas en tu programa FiveWin, esta operación creará un archivo RC vacío, pero quedará registrado el nombre del RC en las propiedades del proyecto para que al momento de compilar XEdit lo incluya en los archivos que deben ser enlazados para generar el EXE final.

DESPUES de crear el RC vacío, puedes copiar todos tus recursos a la carpeta RESOURCE de tu proyecto XEdit, y XEdit hace el resto, compilará y enlazará los recursos dentro de tu programa .EXE, es MUY IMPORTANTE que primero crees el .RC vacío, ya que si copias primero los recursos a la carpeta RESOURCE y luego creas el .RC con el Gestor de Recursos entonces TE VAS A CARGAR TU .RC, con todos los diálogos, cursores y bitmaps que tenga pegados adentro, así que el que avisa no es traidor, estás advertido.

Aprovechando que estamos hablando del tema de los recursos, la siguiente información te puede ser útil: ¿ sabías que puedes programar XEdit para "lanzar" cualquier aplicación que quieras desde el menú principal ?, esta característica es sumamente útil para enlazar por ejemplo tu editor de recursos como una opción del menú del Xedit, y no tener que ir navegando por todo el disco duro para encontrar el EXE adecuado, yo por ejemplo tengo configuradas las herramientas que más uso la opción HERRAMIENTAS del menú principal:


Para agregar herramientas, que pueden ser no solo archivos .EXE sino también archivos de ayuda .CHM hacemos lo siguiente:

Menu Principal / Herramientas / Configurar Herramientas

Aparece la siguiente pantalla, presiona el boton AÑADIR y luego selecciona el archivo .EXE o .CHM que quieras añadir al menú:



¡ y listo !, haz agregado una herramienta a tu menú, la cual se ejecutará de manera automática cada vez que la selecciones del menú.

Ahora bien, si el compilador de recursos de Borland te está dando problemas con el tamaño de los archivos .RC o con los bitmaps muy grandes o de muchos colores, también puedes cambiar el compilador de recursos para usar otro que dé menos problemas, para saber como lee mi artículo: Cambiando el compilador de recursos, dentro de este mismo blog, si compilas tu programa FiveWin con XEdit, puedes usar los trucos ahí mencionado, recuerda que XEdit es muy similar al IDE de Xailer.

Retomando el tema de FiveWin con xEdit, otra cosa importante son las librerías adicionales que suelen llevar los programas FW, para empezar las 2 básicas: FIVEHX.LIB y FIVEHC.LIB.

Para la gestión de librerías (archivos .LIB) Xedit posee un gestor de librerías, para acceder a el:

Menú Principal / Proyecto / Propiedades del Proyecto y seleccionar del árbol:LIBRERIAS


Aparece una pantalla similar a esta:


Pulsando el botón AÑADIR, podrás agregar las librerías que quieras incluir en tu proyecto, por default XEdit te incluirá que las de xHarbour y Borland C++, y tu podrás seleccionar las adicionales que necesites haciendo click en el checkbox, en este caso he añadido las 2 librerías de FiveWin. También es posible definir el orden de linkeado, subiendo y bajando las librerías con los botones que tienen flechas.


Existen mas trucos y herramientas disponibles con XEdit, pero ahora te toca a tí descubrirlas. El programador de Clipper que quiera migrar a xHarbour, encontrará en XEdit LA HERRAMIENTA que le facilitará el proceso de migración, además de que si en un futuro decide desarrollar aplicaciones para Windows usando Xailer, pues ya tendrá un buen trecho andado, porque el IDE de Xailer y XEdit comparten muchas herramientas comunes.

El programador de FiveWin también encontrará en XEdit un valioso aliado, conozco muchos programadores que todavía compilan sus programas con archivo .BAT o .MAK desde una ventana de MS-DOS o todavía utilizan el EDIT de MS-DOS o el NOTEPAD para escribir sus programas, con XEdit podrán automatizar mucho de ese trabajo, con el consabido ahorro de tiempo, tendrán un editor de código profesional, así como otras herramientas interesantes todas integradas en un mismo lugar, además de que podrán probar "un poquito" sobre como se desarrolla usando Xailer.

Espero que con este tutorial muchos de ustedes se animen a darle las gracias al nuestro viejo y querido Clipper y que se adentren en el mundo de la programación a 32 bits.