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.

No hay comentarios.: