Xailer Wiki

El entorno de desarrollo definitivo en Xbase

Herramientas de usuario

Herramientas del sitio


migrar.de.xharbour.a.harbour

GUÍA DE MIGRACIÓN A HARBOUR

Como bien sabemos, Harbour y xHarbour tienen un origen y una base común. De hecho, xHarbour es un fork o un derivado de Harbour, y desde que ese hecho se produjo ambos han seguido caminos separados aunque paralelos. Durante todo este tiempo ha habido ocasiones en las que se han llevado partes de Harbour a xHarbour para mantener la compatibilidad, y lo contrario también ha ocurrido.

No obstante, y aunque ambos compiladores son compatibles entre sí en un 99%, hay algunas diferencias que hay que tener en cuenta si queremos que los programas escritos en Xailer para xHarbour puedan funcionar en Xailer para Harbour.

El presente documento pretende mostrar dichas diferencias y cómo superarlas. Está basado en la propia experiencia de migración de los fuentes de Xailer de xHarbour a Harbour, por lo que debería cubrir prácticamente todos los casos que se puedan dar a cualquier programador.

Esta lista de diferencias está dividida en tres bloques, según afecte al código PRG, C o al propio de Xailer. Algunas de las diferencias serán fácilmente detectadas por el compilador, bien a nivel PRG o bien a nivel C, pero otras no son detectadas en tiempo de compilación, lo que hace más difícil su corrección. Esto se indica en cada diferencia.

A nivel PRG

  • No existen las variables globales, por lo tanto no existen las sentencias GLOBAL ni GLOBAL EXTERNAL. Lo más parecido a las variables globales son las variables públicas, que se crean con la sentencia PUBLIC y se declaran como MEMVAR en los módulos donde se vayan a utilizar. Se detecta al compilar.
  • No existe el operador IN, hay que utilizar el operador $, que es el mismo que siempre ha tenido Clipper. Se detecta al compilar.
  • Sentencia SWITCH: cambiar DEFAULT por OTHERWISE. En xHarbour, DEFAULT indicaba la parte de código a ejecutar cuando no se daba ninguna condición anterior dentro de la sentencia. En Harbour, esta cláusula del comando es OTHERWISE, y funciona exactamente igual. Se detecta al compilar.
  • Sentencia TRY / CATCH / ALWAYS / END: en Harbour ha sido emulada. No debe haber ningún problema, aunque quizás pudiera haber alguna ligera diferencia de comportamiento en casos muy extremos.
  • No se pueden utilizar propiedades u otros miembros de objetos como índices en los bucles FOR / NEXT. Esto significa que una construcción del tipo:
       FOR ::nContador := 1 TO 100
          ...

    hay que modificarla necesariamente por algo así:

       FOR n := 1 TO 100
          ::nContador := n
          ...

    Se detecta al compilar.

ACTUALIZACIÓN: Esto fue corregido posteriormente en Harbour, y solamente afecta a Xailer 2.7. Las versiones posteriores están libres de esta limitación.

  • No se pueden utilizar índices de tipo array en cadenas de caracteres. Hay que utilizar la función Substr() para extraer caracteres individuales. Esto es una ampliación que se hizo en xHarbour hace mucho tiempo, y aunque trajo algunos problemas (sobretodo porque hacía muy propicio cometer errores que no eran fáciles de detectar), tenía algunas ventajas en determinados casos. No obstante, esto nunca se ha implementado en Harbour. P.ej.:
       nSuma := 0
       FOR n := 1 TO Len( cString )
          nSuma += Asc( cString[ n ] )
       NEXT

    hay que cambiarlo por:

       nSuma := 0
       FOR n := 1 TO Len( cString )
          nSuma += Asc( Substr( cString, n, 1 ) )
       NEXT

    Sin embargo esta construcción si es posible:

       nSuma := 0
       FOR EACH cChar IN @cString
          nSuma += Asc( cChar )
          cChar := Upper( cChar ) // Observe como se puede cambiar incluso cada carácter de cString
       NEXT

    No se detecta durante el proceso de compilación, pero sí al ejecutar el programa.

  • Sustituir At() con 3 parámetros por hb_At(). En xHarbour, la función At() admite un tercer parámetro que indica el carácter desde donde comenzar la búsqueda. En Harbour, la función At() permanece exactamente igual que en Clipper, es decir con dos parámetros, y cuando sea necesario utilizar ese tercer parámetro, hay que utilizar la función hb_At(). Se detecta al compilar.
  • La función Trim() no admite un tercer parámetro, que indica que se deben eliminar también los caracteres TAB, CR y LF del final de la cadena. Se ha añadido la función XA_Trim() a Xailer para suplir esta carencia. Por lo tanto, basta con sustituir las llamadas a Trim() o RTrim() que den error por XA_Trim(). Se detecta al compilar.
  • Sustituir ADel() con 3 parámetros por hb_ADel(). En xHarbour, la función ADel() admite un tercer parámetro, que indica que el array se tiene que reducir de tamaño en un elemento, en vez de dejar un valor Nil en la última posición. Harbour ignora este tercer parámetro, por lo que habrá que sustituirlo por hb_ADel() o redimensionar el array manualmente con ASize() despúes de borrar el elemento. No se detecta ni al compilar ni al ejecutar, aunque puede provocar comportamientos erróneos en las rutinas que la utilicen.
  • Sustituir AIns() con 3 parámetros por hb_AIns(). En xHarbour, la función AIns() admite un tercer parámetro, que indica que el array se tiene que agrandar de tamaño en un elemento, en vez de perder el último elemento. Harbour ignora este tercer parámetro, por lo que habrá que sustituirlo por hb_Ins() o hacer crecerr el array manualmente con Aadd() antes de insertar. No se detecta ni al compilar ni al ejecutar, aunque puede provocar comportamientos erróneos en las rutinas que la utilicen.
  • No existe la función ASizeAlloc(), ni hay nada parecido que pueda sustituirla, pero es suficiente con eliminar cualquier llamada a la misma. Se detecta al enlazar.
  • Sustituir hb_SetCodePage() por hb_CdpSelect(). Se detecta al enlazar.
  • Sustituir Super: por ::Super:. En xHarbour y en la versión de Harbour de Xailer 2.7 se utiliza Super: para acceder a los miembros de la clase padre desde una clase hija. En la versión de Harbour de Xailer 3.0 y posteriores, esta palabra clave fue eliminada, y en su lugar hay que utilizar el miembro ::Super de la clase.
  • Sustituir HB_QWith() por :__WithObject(). En xHarbour existe la función HB_QWith() que devuelve el objeto que se está utilizando actualmente en el último nivel (el más interior) de una construcción WITH OBJECT / END WITH. En Harbour no existe dicha función, pero se puede utilizar el método __WithObject() de cualquier objeto para obtener el mismo objeto, por lo que una llamada a :__WithObject() dentro de la construcción WITH OBJECT / END WITH permite obtener exactamente el mismo resultado. Se detecta al enlazar el programa, por lo que se puede hacer una búsqueda en todo el proyecto y corregirlo.
  • Sustituir HB_EnumIndex() por <obj>:__enumIndex(). En xHarbour existe la función auxiliar HB_EnumIndex(), que devuelve el índice actual dentro de un bucle FOR EACH / NEXT. En Harbour no existe dicha función, y para obtener el índice actual del bucle hay que llamar al método __enumIndex() sobre la variable que estemos utilizando en el bucle. P.ej.:
       FOR EACH oControl IN ::aControls
          LogDebug( oControl:__enumIndex() )
       NEXT

    Se detecta al enlazar el programa, por lo que se puede hacer una búsqueda en todo el proyecto y corregirlo.

  • Algunas funciones de fecha tienen el prefijo hb_ en Harbour. Estas funciones son las siguientes: DateTime(), Hour(), Minute(), TToS(), SToT(), TToC() y CToT(). Sólo hay que añadir el prefijo hb_ delante del nombre de la función. Se detecta al enlazar.
  • No se pueden utilizar índices negativos en los arrays. Hay que utilizar ATail( array ) o array[ Len( array ) ]. En xHarbour se podían utilizar índices negativos en los arrays, lo que permitía acceder a elementos de la cola de dicho array. P.ej, aData[ -1 ] devolvía el último elemento del array. En Harbour no existe esta posibilidad, y no hay más remedio que utilizar ATail(), que devuelve el último elemento del array, o calcular el índice basándonos en la longitud del array. No se detecta al compilar, pero sí al ejecutar el programa.

Puede buscar dentro de un proyecto cualquier expresión que contenga un signo menos (-) dentro de unos corchetes con esta expresión regular: \[[^\]]*-[^\[]*\]

Con esto, obtendrá todos los posibles casos conflictivos. Lógicamente saldrán más casos, que no son incorrectos, pero ya deberá discriminarlos manualmente.

  • Al crear un objeto con la función de clase solamente, no se pasan los parámetros a su método New(). En xHarbour, si al construir un objeto se pasaban parámetros a su función de clase, estos valores se pasaban automáticamente al método New() del objeto. P.ej.,
       ::oBtn := TButton( Self )

    era equivalente a:

       ::oBtn := TButton():New( Self )

    Pero en Harbour no es así, y hay que llamar expresamente al método New() del objeto. En el caso de los objetos de Xailer es especialmente importante, puesto que el primer (y muchas veces el único) parámetro de New() es oParent, que podría quedar a Nil con el consiguiente cambio de comportamiento. No se detecta al compilar, y tampoco se produce ningún error al ejecutar el programa, pero produce cambios de comportamiento o errores que pueden ser más difíciles de detectar.

  • La validación del ámbito de los miembros de las clases es más estricta y correcta en Harbour. xHarbour daba por buenos algunos ámbitos de forma incorrecta, mientras que Harbour lo hace bien, y provoca que afloren errores que antes no se producían. P.ej., si desde TForm accedemos a una propiedad PROTECTED de un control, xHarbour lo permitía erroneamente, porque al derivar TForm también de TControl, creía que podía acceder a esa propiedad. Pero en realidad, esa propiedad pertenece a otro objeto (aunque sea de la misma jerarquía) que no es el formulario (pertenece al control) y por lo tanto debería producirse un error. Esta es la diferencia más difícil de solucionar. No se detecta al compilar, y aunque sí se generan errores en tiempo de ejecución, dependen de las condiciones. P.ej., un mismo miembro PROTECTED de una clase puede ser accedido desde un método de una clase hija, pero sólo si dicho miembro pertenece al objeto que hace la llamada. En la práctica, cuando se trate de miembros PROTECTED, podemos dar por bueno todo el código del tipo ::Propiedad o Self:Propiedad, mientras que las llamadas del tipo Objeto:Propiedad suelen ser incorrectas.
  • El ámbito PRIVATE en las clases funciona de forma completamente distinta en Harbour que en xHarbour. En ambos casos, este ámbito significa que ese miembro de la clase sólo puede ser accedido desde un método de la propia clase, y no desde fuera de la misma ni desde una clase hija. Pero además en Harbour, si se sobrecarga un miembro PRIVATE (es decir, se vuelve a declarar en una clase hija), se crea un nuevo miembro con el mismo nombre, pero en todo lo demás es completamente distinto al de su clase padre. Esto implica que cuando la clase padre cambia el valor de una propiedad PRIVATE, la clase hija no ve ese valor. Y lo mismo ocurre si la clase hija lo cambia. A todos los efectos son dos propiedades distintas, que no pueden ser accedidas desde fuera de la propia clase, aunque tengan el mismo nombre en la clase padre y en la clase hija. Esto no era así en xHarbour, que cuando se sobrecargaba una propiedad PRIVATE en una clase hija, se podía acceder a su valor tanto desde la clase padre como desde la hija.
  • La función ErrorNew() que crea un objeto Error en xHarbour admite muchos parámetros para indicar el tipo de error: cSubsystem, nGenCode, cOperation, cDescription, aArgs, ModuleName, cProcName y nProcLine. Sin embargo en Harbour al igual que en CA-Clipper dicha función no recibe ningún parámetro. Por lo tanto, en Harbour el objeto Error creado con ErrorNew() se creará vacío de contenido. Lo más sencillo es crear una función MyErrorNew() que reciba los parámetros que usaba la versión de xHarbour y que esta se encargue de llamar a ErrorNew() y luego establecer el valor de sus miembros. No se detecta al compilar, y tampoco se produce ningún error al ejecutar el programa, pero produce objetos Error inusables.

A nivel C

  • hb_par???() y hb_stor???() no admiten el parámetro extra que permiten usar estas funciones en arrays. Cuando se usan con arrays hay que sustituirlas por hb_parv???() y hb_storv???(). Se detecta al compilar.
  • hb_parc() es de tipo const char *, en vez de char *, por lo que hay que hacer casting a (LPSTR) o declarar las variables como const char *. Se detecta al compilar.
  • hb_arrayGetCPtr() también es de tipo const char * por lo que se aplica exactamente lo mismo que a hb_parc(). Se detecta al compilar.
  • hb_parl() devuelve FALSE si el parámetro no es de tipo lógico (p.ej. numérico). En xHarbour devolvía TRUE si el parámetro era numérico y su valor era distinto de 0. Esto puede provocar errores lógicos o de comportamiento. No se detecta al compilar, ya que internamente en C, el tipo BOOL es un INT, ni genera errores en tiempo de ejecución, por lo tanto es difícil de detectar y corregir.
  • Hay que sustituir hb_itemPutCPtr() por hb_itemPutCLPtr(). Se detecta al compilar.
  • No existe hb_retcAdopt(), hay que cambiarla por hb_retc_buffer(). Se detecta al compilar.
  • No existen hb_retclenAdopt() ni hb_retclenAdoptRaw(), hay que cambiarlas por hb_retclen_buffer(). Hay que tener en cuenta que hb_retclen_buffer() añade un chr(0) extra al final del buffer, por lo que el buffer tiene que ser un byte más grande de lo que realmente se necesite. Se detecta al compilar.
  • No se puede utilizar una estructura HB_ITEM directamente. Siempre hay que usar un puntero de tipo PHB_ITEM y crear el item usando hb_itemNew( NULL ). Se detecta al compilar.
  • PHB_ITEM esta declarado como void *, por lo que no se pueden utilizar los miembros de la estructura HB_ITEM directamente. En su lugar, hay que utilizar las funciones del API de Harbour. P.ej., en vez de usar pItem–>type, hay que usar hb_itemType( pItem ). Se detecta al compilar.
  • Tampoco se pueden utilizar los miembros de otras estructuras de la VM de Harbour, como HB_DYNS, HB_SYMBOL, etc.. Esto implica que no se pueden utilizar llamadas del tipo hb_vmPushSymbol( hb_dynsymFindName( “METODO” )–>pSymbol ). En su lugar, hay que utilizar hb_vmPushDynSym( hb_dynsymFindName( “METODO” ) ). Es decir, en la práctica, hay que sustituir todas las llamadas a hb_vmPushSymbol() por hb_vmPushDynSym(). Se detecta al compilar.
  • No se puede utilizar XA_ObjSend() para asignar una propiedad con el valor Nil. En su lugar hay que utilizar la nueva función XA_ObjSendNil().
  • Las macros ISNIL(), ISNUM(), ISCHARACTER(), etc., han sido renombradas. En su lugar hay que utilizar los mismos nombres con el prefijo HB_ (p.ej. HB_ISNIL(), HB_ISNUM(), etc.). Se detecta al compilar.

Dentro de Xailer

  • Es necesario utilizar la distro de Harbour proporcionada por Xailer, debido a una modificación necesaria en el módulo classes.c para poder aplicar la ampliación del motor de objetos de Xailer. La modificación consiste solamente en la adición de dos pequeñas funciones al final de dicho módulo.

ACTUALIZACIÓN: Esto sólo se aplica a Xailer 2.7. En Xailer 3.0 ya no es necesario, y se puede utilizar cualquier versión nightly-build de Harbour, aunque lógicamente sólo se dará soporte a la versión de Harbour que publica oficialmente Xailer en su área de descargas.

  • Ahora, los eventos se guardan en una DATA interna con ámbito PROTECTED y con el mismo nombre del evento con una F delante, al igual que siempre se han guardado los valores reales de las propiedades. El contenido real de dicha DATA no se puede utilizar ni asignar de ninguna forma, puesto que representa punteros de memoria, y su manipulación sólo puede provocar GPFs. P.ej., el evento OnClick, se guarda en la DATA interna FOnClick de ámbito PROTECTED. A efectos prácticos, en lo único que afecta es en que hay que evitar nombrar a algún miembro de una clase con el mismo nombre de un evento con una F delante.
  • Cuando se utilice la cláusula AS en una propiedad, y ésta sea de tipo array de objetos, pondremos los caracteres [] detrás del nombre de la clase. P.ej.:
       PROPERTY aForms AS TForm[]

    Esto es así por las limitaciones de la cláusula AS, que sólo admite los tipos básicos, y no clases concretas. P.ej., se puede utilizar AS NUMERIC, pero no AS TForm. Se ha modificado el motor de objetos para que se elimine completamente la cláusula AS con aquellos tipos que no sean básicos, y evitar errores posteriores. No obstante, si se incluyen los caracteres [] detrás del tipo, la cláusula AS se convierte a AS ARRAY.

  • Para evitar problemas con el funcionamiento del ámbito PRIVATE, se cambian todos los PRIVATE por PROTECTED.
  • SetKey es una palabra reservada. Ha habido que cambiar el nombre del método SetKey de THotkey por SetHotKey. No se detecta al compilar, pero provoca cambios de comportamiento.
  • Si utiliza el compilador MinGW tenga en cuenta que la sintaxis de nombre de librerías y módulos compilados cambia completamente. en el caso de las librerías sus extensión ha de ser “.a” pero además el nombre ha de comenzar necesariamente con el texto “lib”. La extensión de los módulos compilados ya no es “obj”, sino “o”.
migrar.de.xharbour.a.harbour.txt · Última modificación: 2015/09/15 10:52 (editor externo)

Herramientas de la página