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 ::nCounter := 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 other construction is also possible:
nTotal := 0 FOR EACH cChar IN @cString nTotal += Asc( cChar ) cChar := Upper( cChar ) NEXT
Not detected at compile-time, but at run-time.
At()
with 3 parameters to hb_At()
. In xHarbour, the At()
function allows 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 allow 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 allows 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 break 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_SetCodePage()
to hb_CdpSelect()
. Detected at linking time.Super:
into ::Super:
. In xHarbour and Xailer 2.7's Harbour release, Super:
is used to access parent class members from a child class. Later, this reserved word was removed, and now it has to be used ::Super
to access parent class members.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()
. In 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 may 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 )
or array[ Len( 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()
constructor method. In xHarbour, any parameter passed to the class function, was passed to its New()
method. For example:::oBtn := TButton( Self )
was equivalent to:
::oBtn := TButton():New( Self )
But not in Harbour. You must call the object method New()
. In the case of Xailer objects, it's specially important because or first (and normally only) parameter of the New()
constructor is oParent
, that could become Nil
causing important errors and in some cases difficult to find. Not detected at compile-time, nor at run-time.
TForm
to a PROTECTED
control's property, xHarbour erroneously permitted it because TForm
is also inherited from TControl
. But really, that property belongs to another object, the control (although it's in the same hierarchy), and not to the form, and therefore should be a mistake. This is the biggest difference to fix. It is not detected at compile-time and although some run-time errors arise, not always it depends on the conditions. For example the same PROTECTED
member of a class maybe accessed from a method of a child class but only if this member belongs to the same object that makes the call. In the practice, when working with PROTECTED
methods, expression like ::Property
or Self:Property
are normally correct, but expressions like Object:Property
are not.PRIVATE
scope works in a different manner in Harbour than xHarbour. On both cases, this scope means that the member can only be accessed from a method of its own class and not from outside of it or any child class. But in Harbour, if a PRIVATE
method is overloaded, a new member is created with the same name, but in the rest, completely different from its parent class. This implies that when the parent class private member change its value, the child class member does not change, and the opposite. In all aspects the two members are completely different. This was not the behavior in xHarbour. A overloaded PRIVATE
method there was only a member on the object with a unique value.ErrorNew()
function that creates an Error
object in xHarbour supports many parameters to indicate the type of error: cSubsystem
, nGenCode
, cOperation
, cDescription
, aArgs
, ModuleName
, cProcName
and nProcLine
. However in Harbour as in CA-Clipper it does not receive such parameters. Therefore, in Harbour the Error
object created by ErrorNew()
will be created meaningless. The simplest way to fix this is to create a MyErrorNew()
function to get the parameters used on xHarbour and that function will create the error object and set the value of its members. Not detected at compile-time nor at run-time but produces useless error objects.hb_par???()
and hb_stor???()
do not allow the extra parameter to be used for array handling. When used with arrays you must change them with the functions hb_parv???()
and hb_storv???()
. Detected at compile time.hb_parc()
is of type const char *
, instead of char *
, so you need to make a cast to (LPSTR)
or declare the variables as const char *
. Detected at compile time.hb_arrayGetCPtr()
is also of type const char *
so you must do the same than hb_parc()
. Detected at compile time.hb_parl()
returns FALSE
if the parameter is not of type logical (p.ej. numeric). In xHarbour returned TRUE
if the parameter was numeric and different of 0. This can arise logical errors. Not detected at compile time, and since internally C treats the BOOL
type as INT
, neither generates run-time errors, therefore is really difficult to detect.hb_itemPutCPtr()
to hb_itemPutCLPtr()
. Detected at compile time.hb_retcAdopt()
does not exist, must be changed to hb_retc_buffer()
. Detected at compile time.hb_retclenAdopt()
and hb_retclenAdoptRaw()
do not exist, and must be changed to hb_retclen_buffer()
. But notice that hb_retclen_buffer()
adds an extra chr(0)
to the end of the buffer, so the buffer have to be one byte larger than what it's really needed. Detected at compile time.HB_ITEM
structure. You must always use its pointer of type PHB_ITEM
and create the item using hb_itemNew( NULL )
. Detected at compile time.PHB_ITEM
is declared as void *
, therefore you can not use directly any member of the HB_ITEM
structure. Instead, you must use the Harbour API functions to do the job. For example, instead of pItem–>type
, you should use hb_itemType( pItem )
. Detected at compile time.HB_DYNS
, HB_SYMBOL
, etcetera. This implies that you can not make function calls like hb_vmPushSymbol( hb_dynsymFindName( “METHOD” )–>pSymbol )
. Instead, you must use hb_vmPushDynSym( hb_dynsymFindName( “METHOD” ) )
. So, in the practice, you must change calls to hb_vmPushSymbol()
for hb_vmPushDynSym()
. Detected at compile time.XA_ObjSend()
to assign a NIL
value to a property. Instead you must use the new function XA_ObjSendNil()
.ISNIL()
, ISNUM()
, ISCHARACTER()
, etcetera., have been renamed. Instead you must use the same names with the HB_
prefix. (For example HB_ISNIL()
, HB_ISNUM()
, etc.). Detected at compile time.DATA
with PROTECTED
scope with the same name of the event but with an F before, as we always did with property real values. The actual content of this new DATA
can not be used or assigned in any way, as it represents memory pointers, and handling it only can cause GPFs. For practical purposes, the only thing that affects is to be avoided naming any member of a class with the same name as an event with an F before.AS
in a property, and this is of type object array, you should put the characters []
after its name. For example:PROPERTY aForms AS TForm[]
PRIVATE
scope, all of them have been changed to PROTECTED
.SetKey
is a reserved word. We have changed the method name SetKey
of THotkey
for SetHotKey
. Not detected at compile time. May cause wrong application behavior.