¡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()
with 3 parameters for hb_ADel()
. On xHarbour, the ADel()
function admits a third parameter to reduce the array size. Harbour ignores this third parameter and you should change to hb_ADel()
or resize the array manually with the ASize()
function. Not detected at compile-time nor at run-time but may clearly brake your code.ASizeAlloc()
does not exist, there is nothing similar to this, you just need to eliminate it from your code. Detected at link time.HB_QWith()
with :__WithObject()
. In xHarbour the HB_QWith()
function returns the active object used on a WITH OBJECT / END WITH
statement. On Harbour there is no such function, but you may use the method __WithObject()
from any object to get the same result. Detected at link time.HB_EnumIndex()
with <obj>:__enumIndex()
. Inn xHarbour the function HB_EnumIndex()
returns the current active index on a FOR EACH / NEXT
loop. In Harbour there is no such function, to retrieve the same index loop value you can call the method __enumIndex()
over the variable that stores the current loop value. For example:FOR EACH oControl IN ::aControls LogDebug( oControl:__enumIndex() ) NEXT
Detected at link time.
hb_
prefix in Harbour. This functions are: DateTime()
, Hour()
, Minute()
, TToS()
, SToT()
, TToC()
and CToT()
. Detected at link time.ATail( array )
. In xHarbour you could use negative index values in arrays, which lets the possibility to access the array elements from last to first. For example aData[ -1 ]
returned the last element on the array. In Harbour there is not such functionality and you must use ATail()
instead. Detected at run-time. \[[^\]]*-[^\[]*\]
.
With this, you will get all the possible conflicting cases. Surely some more cases will arrive that will not be incorrect, but it will help on doing the job.
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.