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.
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.IN
, hay que utilizar el operador $
, que es el mismo que siempre ha tenido Clipper. Se detecta al compilar.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.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.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.
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.
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.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.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.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.ASizeAlloc()
, ni hay nada parecido que pueda sustituirla, pero es suficiente con eliminar cualquier llamada a la misma. Se detecta al enlazar.hb_SetCodePage()
por hb_CdpSelect()
. Se detecta al enlazar.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.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.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.
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.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.\[[^\]]*-[^\[]*\]
Con esto, obtendrá todos los posibles casos conflictivos. Lógicamente saldrán más casos, que no son incorrectos, pero ya deberá discriminarlos manualmente.
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.
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.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.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.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.hb_itemPutCPtr()
por hb_itemPutCLPtr()
. Se detecta al compilar.hb_retcAdopt()
, hay que cambiarla por hb_retc_buffer()
. Se detecta al compilar.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.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.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.XA_ObjSend()
para asignar una propiedad con el valor Nil
. En su lugar hay que utilizar la nueva función XA_ObjSendNil()
.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.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.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
.
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.