¡Esta es una revisión vieja del documento!
As we know, Harbour and xHarbour have an origin and a common basis. In fact, xHarbour is a fork or derived from Harbour, and since that incident occurred the two have walked through different paths, but in parallel. During all this time there have been occasions when some parts of xHarbour were ported into Harbour to maintain compatibility, and the reverse has also happened.
However, and although both are compatible compilers by 99%, there are some differences to be taken into account if we want that our programs written on Xailer for xHarbour will also function on Xailer for Harbour.
This paper aims to show and how to overcome those differences. It is based on the experience of migration Xailer sources of xHarbour to Harbour, so it should cover almost all cases that can be given to any programmer.
The list of differences is divided into three blocks, as affecting the PRG code, C code, or Xailer itself. Some of the differences are easily detected by the compiler, either PRG level or C-level, but others are not detected at compile time, making it more difficult to fix. This is indicated in each difference.
GLOBAL
nor GLOBAL EXTERNAL
sentences. The closest thing to the global variables are the public variables, which are created with the sentence PUBLIC
and are declared in the modules which use them with the sentence MEMVAR
. It is detected by the compiler.IN
operator does not exist, use the operator $
instead, which is the Clipper standard. It is detected by the compiler.SWITCH
sentences, change DEFAULT
with OTHERWISE
. On xHarbour, DEFAULT
indicates the part of code to execute when none of the previous CASE
statements were true. In Harbour, this clause uses the command OTHERWISE
, and with the same functionality. It is detected by the compiler.TRY / CATCH / ALWAYS / END
: in Harbour has been emulated. You should not have any problem, but maybe you can get some minor behavior difference on extreme cases.FOR / NEXT
. This means, that code like this:FOR ::nCounterr := 1 TO 100 ...
should be modified by something like this:
FOR n := 1 TO 100 ::nCounter := n ...
Detected by the compiler.
Substr()
function to extract individual characters. This extension was made on xHarbour long time ago, although it brought some problems (run-time errors difficult to detect), it had greatest advantages on some cases. However, this was never implemented in Harbour. For example:nTotal := 0 FOR n := 1 TO Len( cString ) nTotal += Asc( cString[ n ] ) NEXT
should be changed to:
nTotal := 0 FOR n := 1 TO Len( cString ) nTotal += Asc( Substr( cString, n, 1 ) ) NEXT
Although this construction is also possible:
nSuma := 0 FOR EACH cChar IN @cString nTotal += Asc( cChar ) cChar := Upper( cChar ) NEXT
Not detected at compile-time, but yes at run-time.
At()
with 3 parameters to hb_At()
. In xHarbour, the At()
function admits a third parameter which indicates the first position to start the search. In Harbour, the At()
function remains exactly the same as in Clipper, with two parameters and when you need the third parameter you must use the hb_At()
function. Detected by the compiler.Trim()
function does not admit the third parameter to indicate extra characters to delete like TAB
, CR
and LF
from the end of the string. We have added the XA_Trim()
function to overcome this lack. Detected by the compiler.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.ASizeAlloc()
, ni hay nada parecido que pueda sustituirla, pero es suficiente con eliminar cualquier llamada a la misma. Se detecta al enlazar.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.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_rectAdopt()
, hay que cambiarla por hb_retc_buffer()
. 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.