Harbour for beginners

Alexander Kresin
2003 - 2016

3. Differences from Clipper - a brief description

3.1 Overview

Harbour was created as a compiler fully compatible with Clipper. This means, that any program written in Clipper, Harbour should compile without problems and work in the same way as if it had been compiled with Clipper. In many cases it is so, but, of course, problems can also arise - not everything goes as planned, Harbour was not free from errors and defects.
Another possible source of problems in the transition from Clipper to the Harbour - it's the additional libraries, inserts of the C and assembler, if you use them - 16-bit objects can not be linked to the 32-bit application. If you have the source of these modules, you can try to recompile them, though the success is not guaranteed here - not all of the features of a 16-bit C compiler are available in 32-bit.

But, of course, the Harbour does not simply repeat the functionality of the Clipper, it significantly expands it - otherwise, it wouldn't make sense! In addition to extensions related to the transition from 16 to 32 bit code ( removal of restrictions on memory, on the dimension of the array and the length of the strings ), the Harbour offers the language extensions, and additional functionality. They will be discussed below.
I just want to warn that this list will be incomplete - something I could forget, something I might have not written simply because of the lack of time and patience. I'll start gradually, and then I will expand this description. I recommend that you also use the documentation on the the Harbour official site, and also on the constantly renewable collection on the docs.google.com. Highly recommend is also the already mentioned here English-language document xhb-diff.txtthat can be found in any Harbour distributive in the Doc directory. There you can find a description of the most important language extensions of the Harbour ( compared with Clipper ) and the differences between their implementations from xHarbour.

And a couple words about the language changes. Personally I use them only when extreme necessiry. Don't forget that the farther you are from the traditional Clipper code the more problems you will have with having to go to any other compiler - who knows, what in this life you may need. Even during the transition from Harbour to xHarbour problems can arise , because there are many of the innovations implemented in different ways - see. xhb-diff.txt. And even if the transition to a different compiler may look like a distant prospect, the need to browse/convert/modify your dbf with the third-party tools may appear in any moment - that's why I never use the new types of fields in the dbf files, the more so as all the necessary functionality can be implemented without them.

3.2 Some language extensions

Here we consider several new operators appeared in the Harbour. Let's start with FOR EACH:

      FOR EACH var1 [,var255] IN expr1 [,expr255] [DESCEND]
         [EXIT]
         [LOOP]
         ...
      NEXT
   
- expr - can be a regular array, a hash array, or text string;
- in the variable var<n> ( var1, ... ) ( enumerator - so we will continue to call it ) there is a reference to an element of the array or a string (if the string passed by the reference - see the example below ) expr<n>, thus, when you give it a value, we change the corresponding element of the array or string.
- after the completion of the cycle the enumerator has the same value as before the cycle;
      Local c := "alles", s := "abcdefghijk"
      FOR EACH c IN @s
         IF c $ "aei"
            c := UPPER( c )
         ENDIF
      NEXT
      ? s      // AbcdEfghIjk
      ? s      // alles
   
- enumerator has properties that can be accessed using OOP syntax:
   :__enumindex - the index number of the current element in an array/string;
   :__enumbase - the expr<n> ;
   :__enumvalue - value of the variable;
   :__enumkey - the key element of the hash array.

Another new operator - WITH OBJECT:

      WITH OBJECT expression
         ...
      ENDWITH
   
It allows to simplify the code for the reasonable use :
      // you can write
      WITH OBJECT myobj:a[1]:myitem
         :message( 1 )
         :value := 9
      ENDWITH

      // instead of
      myobj:a[1]:myitem:message( 1 )
      myobj:a[1]:myitem:value := 9
   

And, finally, SWITCH:

      SWITCH expr
        CASE value
          [EXIT] 
        ... 
        OTHERWISE 
      ENDSWITCH
   
It looks like this:
      SWITCH a
      CASE 1
         ? "FOUND: 1"
      CASE "2"
         ? "FOUND: 2"
      OTHERWISE
         ? "other"
      END
   
This is a C-like operator, it is similar to the Clipper's DO CASE ... ENDCASE and IF ... ELSEIF ... ELSE ... ENDIF, but it doesn't allow to use the expressions as a condition, and due to the peculiarities of the implementation it works much faster, so that it makes sense to use it in cycles.

In the Harbour there are essentially expanded possibilities of using macros. So, now in macros ( and in codeblocs ) you can use the operators ++, --, +=, -=, etc., nested code-blocs, expressions of arbitrary length. Also you may use the lists:

      cMacro := "1, 150, 'Alexey Ivanov', 20, .T."
      { &cMacro } // массив
      SomeFun( &cMacro ) // Список аргументов

      cMacro := "1,10"
      SomeArray[ &cMacro ] // индексы массива
   
Macros can be used for the names of the variables of objects:
      proc main()
         memvar var
         local o := errorNew(), msg := "cargo"
         private var := "CAR"

         o:&msg := ""
         o:&( upper( msg ) ) += ""
         ? o:&var.go
   

Harbour provides extended codeblocks. Such a codeblock includes a multiline code snippet, which allows any any language elements, including the Local and Static variables and is, in fact, function, nested within a function:

      Function Main

         Local b1, b2

         b1 := Fnc1( Seconds() )
         ? Eval( b1 )

         ? "Press any key"
         inkey(0)

         b2 := Fnc1( Seconds() )
         ? Eval( b2 )

         ? "Press any key"
         inkey(0)

         ? Eval( b1,Seconds() )

         ? "Press any key"
         inkey(0)

         ? Eval( b1 )
         ?

      Return Nil

      Function Fnc1( nSec )

         Local bCode := {|p1|
            Local tmp := nSec
            IF p1 != Nil
               nSec := p1
            ENDIF
            Return tmp
         }

      Return bCode
   

This example is not as simple as it seems at first glance, it shows an interesting feature of the codeblocks, which existed in Clipper, but to fully was able to open up to these extended codeblocks. Note the nSec parameter, which is a local variable of Fnc1(). It is external to the codeblock bCode, but after the completion of Fnc1() continues to "live" with bCode. At first glance it may seem that such a variable - an analogue of Static, but it is not - the fact is that every instance of codeblock will have its own copy of this variable, which is initialized during the call and work of the Fnc1(). This is called closure ( see the Wikipedia entry ) - element, well known for some modern programming languages. In Javascript, for example, it is widely used and is considered as one of the most interesting and powerful tools.

The elements of the array, hash-array and objects in the Harbour you can pass by the reference:

      function main()
      Local arr := { 1,2,3 } 

         ? arr[1], arr[2], arr[3]   // 1   2   3
         p( @arr[2] )
         ? arr[1], arr[2], arr[3]   // 1  12   3

      return nil

      Function p( x )
         x += 10
      return nil
   

Functions with a variable number of parameters. Harbour allows you to declare in the function declaration the named parameters and then unnamed. These unnamed parameters can then be used with the help of "...":

      // as the array elements
      proc main()
         AEval( F( "1", "2", "A", "B", "C" ), {|x, i| qout( i, x ) } )

      func f( p1, p2, ... )
         ? "P1:", p1
         ? "P2:", p2
         ? "other parameters:", ...
      return { "X", ... , "Y", ... "Z" }

      // as indexes of the array:
      proc main()
         local a := { { 1, 2 }, { 3, 4 }, 5 }
         ? aget( a, 1, 2 ), aget( a, 2, 1 ), aget( a, 3 )

      func aget( aVal, ... )
      return aVal[ ... ]

      // as the parameters of the function:
      proc main()
         info( "test1" )
         info( "test2", 10, date(), .t. )

      proc info( msg, ... )
         qout( "[" + msg +"]: ", ... )
   
The function hb_aParams() returns a list of the passed parameters, named and unnamed.
The function hb_arrayToParams() converts an array to a list of elements, which then can be used as the function parameters, the array indices, etc.:
      // hb_arrayToParams(  ) -> [ 1 ] [, [ N ] ]
      // Например:
      proc main( ... )
         local aParams := hb_aParams(), n
         /* remove the options that start with '--' */
         n := 1
         while n < len( aParams )
            if left( aParams[ n ], 2 ) == "--"
               hb_adel( aParams, n, .t. )
            else
               ++n
            endif
         enddo
         ? "Public parameters:", hb_arrayToParams( aParams )
      return
   

A new type of data - TIMESTAMP, it is defined in RDD (i.e., can be used as the field type in the dbf), and in the VM (virtual machine). For the TIMESTAMP variable the function Valtype() returns "T", it is possible to produce operations of addition, subtraction and comparison. Constants of this data type can be declared in the program as follows:

      // { ^ [ YYYY-MM-DD [,] ] [ HH[:MM[:SS][.FFF]] [AM|PM] ] }
      // t"YYYY-MM-DD [H[H][:M[M][:S[S][.f[f[f[f]]]]]]] [PM|AM]"
      // t"YYYY-MM-DDT[H[H][:M[M][:S[S][.f[f[f[f]]]]]]] [PM|AM]"
      proc main()
         Local t1 := {^ 2012-11-28 10:22:30 }   // или {^ 2012/11/28 10:22:30 }
         Local t2 := t"2009-03-21 17:31:45.437" // или t"2009-03-21T17:31:45.437"

         ? t1 + 1      // 11/29/12 10:22:30.000
         ? t1 - t2     // 1347.701905
   
By the way, since we were talking about the announcement of the constants - in the Harbour you can declare constants of DATE type as below:
      Local d1 := 0d20121201  // Template: 0dYYYYMMDD
   
And string constants can be declared, as in Cusing literal e"...":
      Local str1 := e"Helow\r\nWorld \x21\041\x21\000abcdefgh"
   
Integers can be declared in hexademical format:
      Local n := 0x1F
   

Another interesting innovation in the Harbour - pointers to functions. They can be created on the stage of building the application, using the expression @<funcName>(), or dynamically during execution with the help of the macro: &("@<funcName>()"). Valtype() returns S for such pointers.

      proc main()
         local funcSym
         funcSym := @str()
         ? funcSym:name, "=>", funcSym:exec( 123.456, 10, 5 )
         funcSym := &( "@upper()" )
         ? funcSym:name, "=>", funcSym:exec( "Harbour" )
      return
   

3.3 Classes and objects

Implementation of OOP ( object oriented programming ) is perhaps the first thing added by developers of the Harbour to the standard language. Clipper's latest versions contained elements of the OOP. There was several predefined classes, you could create their objects, but it wasn't possible to create own classes. There were however some of the undocumented possibilities, which allowed to do it, several 3rd party libraries that implement the OOP for Clipper were based on them, the most known of them are Fivewin (more precisely, Objects.lib from the Fivewin) and Class(y). To use the classes in the Harbour you don't need to link additional libraries, the OOP support is part of the language and, as the funds are the part of the kernel, the Harbour provides more possibilities than Clipper with all third-party libraries.

So, to create a new class, use the command CLASS ... ENDCLASS:

      [CREATE] CLASS <cClassName> [ FROM | INHERIT <cSuperClass1> [, ... ,<cSuperClassN>] ]
                [ MODULE FRIENDLY ] [ STATIC ] [ FUNCTION <cFuncName> ]

      [HIDDEN:]
         [ CLASSDATA | CLASSVAR  | CLASS VAR <DataName1>]
         [ DATA | VAR  <DataName1> [,<DataNameN>] [ AS <type> ] [ INIT <uValue> ]
                [[EXPORTED | VISIBLE] | [PROTECTED] | [HIDDEN]] [READONLY | RO] ]
         ...
         [ METHOD <MethodName>( [<params,...>] ) [CONSTRUCTOR] ]
         [ METHOD <MethodName>( [<params,...>] ) INLINE <Code,...> ]
         [ METHOD <MethodName>( [<params,...>] ) BLOCK  <CodeBlock> ]
         [ METHOD <MethodName>( [<params,...>] ) EXTERN <funcName>([<args,...>]) ]
         [ METHOD <MethodName>( [<params,...>] ) SETGET ]
         [ METHOD <MethodName>( [<params,...>] ) VIRTUAL ]
         [ METHOD <MethodName>( [<params,...>] ) OPERATOR <op> ]
         [ ERROR HANDLER <MethodName>( [<params,...>] ) ]
         [ ON ERROR <MethodName>( [<params,...>] ) ]
         ...
      [PROTECTED:]
         ...
      [VISIBLE:]
      [EXPORTED:]
         ...

      [FRIEND CLASS <ClassName,...>]
      [FRIEND FUNCTION <FuncName,...>]

      [SYNC METHOD <cSyncMethod>]

      ENDCLASS [ LOCK | LOCKED ]
   

A class can be declared as the heir of one or more parents (especially multiinheritance - multiple inheritance we will consider later) with the help of the clause FROM or INHERIT. This means that it receives from the parent class all its variables and methods (except those marked as HIDDEN). Any of the inherited methods can be overridden. The parent method can be invoked as Super:<methodName>() or ::<cSuperClass>:<methodName>() (if it is not HIDDEN).

I want to mention one interesting peculiarity related to inheritance. If the child class has a variable with the same name as the parent, then each object contains two variables with the same name. One of them should be adressed to as obj:<varName>, the other - obj:<cSuperClass>:<varName>.

A class can be declared as STATIC - i.e. it cannot be used outside of the current module.

A class can be declared as MODULE FRIENDLY - this means that all classes and functions, which are declared in the current module, are his friends, i.e., have an access to his HIDDEN and PROTECTED variables and methods. And since we're talking about the friendly functions pay attention to clauses FRIEND CLASS and FRIEND FUNCTION - they determine friendly for this class classes and functions.

The words FUNCTION <cFuncName> in the declaration of a class define it as a scalar class.

Clauses DATA or VAR - a definition of variables of a class. During this there can be strictly defined the type of the variable AS <type> (Character, Numeric, Date, Logical, Codeblock, Nil), can be set its initial default value INIT <uValue> and defined Scope (Hidden,Protected,Exported,Readonly). Scope can be set for each variable separately ( in the DATA clause ), and for the group of variables and methods:

    - HIDDEN - variable or method are only available within the class, where they are identified and are not inherited;
    - PROTECTED - are available only to the class, where they are determined for the heirs;
    - EXPORTED ( or VISIBLE ) - are available on every side;
    - READONLY ( or RO ) - if the variable is defined as EXPORTED, then modification of its value can only be done from his class and his heirs, if as PROTECTED, then only from his class.

Variables can belong to the object and to the whole class - the last are declared as CLASSDATA, CLASSVAR or CLASS VAR. Such a variable is available from any object of a class as a obj:<DataName>but it is stored not in the areas of data objects, but in one place, in the field of the class data , and therefore it has the same value for all the objects of a class. There is another difference in the implementation of CLASSDATA on the one hand, and CLASSVAR or CLASS VAR on the other. These variables can be declared as SHARED. This means that if the parent class has such a variable, then the derived classes use the same single copy of it, share it. If the same SHARED is not declared, then the derived classes inherit it in the usual way, creating a new copy of the variable for each class of the heir. So, for the CLASSVAR and CLASS VAR it works, as it was described above, and CLASSDATA variables behave like SHARED regardless of whether you use this word in the ad or not. It was for the backward compatibility with Fivewin programs for Clipper.

Clause METHOD - this is the definition of the methods of the class. In general case if the method is not INLINE, not BLOCK and not VIRTUAL, after the definition of the class (after ENDCLASS) you must put the implementation of the method:

      METHOD ( [] ) CLASS 
         ...
      RETURN Self
   
The method should return a reference to the object ( Self ), if it is used as a constructor.

The following are different ways of declaring a method:

    - INLINE allows you to put the implementation of the method in the same line, where it is declared. This method can be used when the code is fairly small, it is making an announcement of the method more simple and demonstrative.
    - BLOCK similar to the INLINEbut he expects the record representes method in the form of codeblock and allows you to send arbitrary number of parameters.
    - EXTERN <funcName>([<args,...>]) is applied, if the external function <funcName>() implements what this method needs.
    - SETGET is used for the data to be calculated, it allows you to use the name of the method as the name of the variable to be read / write data. For example, if your class is a variable, value of which is not desirable to install directly, since it should have a certain format or should be only in a certain range. You define this variable as HIDDEN to exclude its direct change, and define SETGET the method that will be used to set the value of a variable, it checks the passed value, formats it as it should be and so on. Now, to set the value of a variable, you write: obj:<methodName> := <value> .
    - VIRTUAL this method does nothing. It can be used for basic, parental classes - in this case, the concrete realization of the method will be made in the heir classes.
    - OPERATOR <op> is used to overload the operator <op> ( "+", "-", ... ) - i.e. when the <op> action on the object in the program is taking place, this action is implemented by the method, which is indicated with the word OPERATOR. As a parameter to the method the second object participating in the operation <op> is passed.

Another way of declaring a method is to set it with the help of ERROR HANDLER or ON ERROR. This method is invoked in the case, if the object class to which it belongs to, was sent a message not specified for this class; in other words, a method or variable which is not present in this class is being called to. You can, of course, use such a method for the direct purpose - for the treatment of errors, but there are more interesting ways to use it. So, it can be used dynamically to simulate the availability of the the object's variables, which were not defined in the class declaration, and in different objects of the same class different methods and the variables can be simulated.

SYNC METHOD is used to synchronize the execution of the methods in the multithread application. If a method is marked as SYNCthen it will not run two or more threads simultaneously for one object.

Option LOCK or LOCKED of the ENDCLASS prevents subsequent modification of this class, it can be used in the design of large projects of the group of developers, if you do not want, that someone hacks your class.

To create and test a simple class it is enough to write:

      #include "hbclass.ch"
      FUNCTION Main
      Local obj := myFirstClass():New(3)

         ? obj:x       // 3
         ?
      RETURN Nil

      CLASS myFirstClass
         VAR   x
         METHOD New( n )   INLINE ( ::x := n, Self )
      ENDCLASS
   

I announced here the INLINE (built-in) method to do only the design of the CLASS ... ENDCLASS and do not describe the method New(n) separately. Here we should pay attention to the following moments:

    - you must include the hbclass.ch in the prg, containing the class declaration;
    - object variable inside a method of this object is preceded by a double colon ::x;
    - the object inside the method is called Self;
    - the method used to create a new object must return a reference to this object: Self;
    - how to create a new object: <className>():<method>() ( obj := myFirstClass():New(3) ).

Now one more complicated example:

      #include "hbclass.ch"
      FUNCTION Main
         Local obj1, obj2

         obj1 := mySecondClass():New( 10,"Alex" )
         ? "Всего объектов mySecondClass:", mySecondClass():nKolObj
         obj2 := mySecondClass():New( 11,"Mike" )
         ? "Всего объектов mySecondClass:", mySecondClass():nKolObj
         ? obj1:x, obj2:x
         ? 
      RETURN Nil

      CLASS myFirstClass
         VAR   x
         METHOD New( n )   INLINE ( ::x := n, Self )
      ENDCLASS

      CLASS mySecondClass  FROM myFirstClass
      HIDDEN:
         VAR   cStringS

      EXPORTED:
         CLASS VAR  nKolObj     INIT 0
         VAR        cString1    INIT "Sample"
         METHOD New( n, s )
      ENDCLASS

      METHOD New( n, s ) CLASS mySecondClass

         Super:New( n )
         ::nKolObj ++
         ::cStringS := s
      RETURN Self
   
Here appeared such new features:
    - we have two classes, myFirstClass and mySecondClass, and the mySecondClass is the heir of myFirstClass;
    - two variables, nKolObj and cString1, in the declaration of a class the initial values are assigned, they will stay for any new object of the class;
    - the method New of the class mySecondClass is described separately with the help of METHOD <methodName> CLASS <className>;
    - in this method Super:New(n) is used to call an identically named method of the parent class to initialize the object's variables inherited from a parent.
    - mySecondClass variable cStringS has the attribute (scope) HIDDEN (i.e., it is only available from methods of the class), and the rest of the variables and the method of New marked as EXPORTED (i.e., accessible from everywhere);
    - in the mySecondClass the CLASS VAR nKolObj appeared - variable, which belongs not to the object, but to the whole class, here it is used as a counter of objects of this class;

Another example showing the method declarations as BLOCK and EXTERN:

      #include "hbclass.ch"
      function Main()
      Local o1 := HSamp1():New()
      Local o2 := HSamp2():New()

         ? o1:x, o2:x                        // 10       20
         ? o1:Calc( 2,3 ), o2:Mul( 2,3 )     // 60      120
         ? o1:Sum( 5 )                       // 15
         ?

      return nil

      CLASS HSamp1

         DATA x   INIT 10
         METHOD New()  INLINE Self
         METHOD Sum( y )  BLOCK {|Self,y|::x += y}
         METHOD Calc( y,z ) EXTERN fr1( y,z )
      ENDCLASS

      CLASS HSamp2

         DATA x   INIT 20
         METHOD New()  INLINE Self
         METHOD Mul( y,z ) EXTERN fr1( y,z )
      ENDCLASS

      Function fr1( y, z )
      Local o := QSelf()
      Return o:x * y * z
   
Note the function call QSelf(), it returns a reference to the object, in the context of which it is located, i.e., the object which is called Self in the methods of the class.

And here is an example of use of ERROR HANDLER:

      #include "hbclass.ch"
      FUNCTION Main
         LOCAL oTable

         oTable := Table():Create( "sample.dbf", { {"F1","C",10,0}, {"F2","N",8,0} } )
         oTable:AppendRec()
         oTable:F1 := "FirstRec"
         oTable:F2 := 1
         oTable:AppendRec()
         ? oTable:nRec                // 2
         oTable:nRec := 1
         ? oTable:nRec                // 1
         ? oTable:F1, oTable:F2       // "FirstRec"     1
         ?

         RETURN nil

      CLASS Table

         METHOD Create( cName, aStru )
         METHOD AppendRec()   INLINE dbAppend()
         METHOD nRec( n )     SETGET

         ERROR HANDLER OnError( xParam )
      ENDCLASS

      METHOD Create( cName, aStru ) CLASS Table

         dbCreate( cName, aStru )
         USE (cName) NEW EXCLUSIVE
      RETURN Self

      METHOD nRec( n ) CLASS Table

         IF n != Nil
            dbGoTo( n )
         ENDIF
      RETURN Recno()

      METHOD OnError( xParam ) CLASS Table
      Local cMsg := __GetMessage(), cFieldName, nPos
      Local xValue

         IF Left( cMsg, 1 ) == '_'
            cFieldName := Substr( cMsg,2 )
         ELSE
            cFieldName := cMsg
         ENDIF
         IF ( nPos := FieldPos( cFieldName ) ) == 0
            Alert( cFieldName + " wrong field name!" )
         ELSEIF cFieldName == cMsg
            RETURN FieldGet( nPos )
         ELSE
            FieldPut( nPos, xParam )
         ENDIF

      Return Nil
   
This shows extremely simplified class Table, every object of oTable should correspond to the newly created or opened ( method Open is omitted here ) table (dbf file). We want to refer to the fields of the table as to the properties (variables) of an object oTable. But we can't enter the names of the fields in a class declaration, because they, generally speaking, are not known at the time of writing the class, and they are simply different for different tables. Here ERROR HANDLER will help. When a non-existent variable ( or methods ) of the class is called, an error is produced and the method is called defined as ERROR HANDLER ( or ON ERROR ). From this method you can get the same non-existent name of the called variable with the help of the function __GetMessage(). Moreover, if there was an attempt to write something in this variable, then the name is preceded by the symbol "_" and the method is passed by the recorded value as the first parameter. All the rest I think is clear from the example.
In the code above we saw also an example of the use of SETGET method. Note that a call to SETGET method nRec occurs as a variable of the object. If we try to set it ( oTable:nRec := 1 ), the method gets the set value as a parameter and it produces the movement in the database, but if we read ( ? oTable:nRec ), it simply returns the result Recno().

A list of functions for the manipulation of classes and objects follows:

lExist := __objHasData( oObject, cName )Returns a Boolean value, specifies whether the object oObject has a variable name cName
lExist := __objHasMethod( oObject, cName )Returns a Boolean value, specifies whether the object oObject has the method with the name cName
aNames := __objGetMsgList( oObject, [lData], [nClassType] ) Returns an array of the names of all the variables and methods of an object oObject; lData specifies that the required methods ( .F. ), or variables ( .T., the default value ); nClassType specifies what variables should be included in the list. 3 values are possible defined in hboo.ch:
        HB_MSGLISTALL    0   все переменные
        HB_MSGLISTCLASS  1   переменные класса CLASS DATA
        HB_MSGLISTPURE   2   переменные объекта DATA
        
aNames := __objGetMethodList( oObject ) Returns an array of the names of all the methods of an object oObject
conveys := __objGetValueList( oObject, [aExcept] )Returns a two-dimensional array of all the variables of the object oObject ( name and value ); aExcept - an array of the names of variables, which should not be included in the result.
oObject := __ObjSetValueList( oObject, conveys )Sets the value of the object's variables oObject, conveys - two-dimensional array of name / value
oObject := __objAddMethod( oObject, сMethodName, nFuncPtr )Adds a method in the already existing class to which the object belongs in oObject; сMethodName - the name of the method, nFuncPtr is a pointer to a function that implements this method ( the pointers to the functions, see the section 3.2:
        oHappy := HBClass():New( "THappy" )
        __objAddMethod( oHappy, "Smile", @MySmile() )
        ? oHappy:Smile( 1 )

        Static Function MySmile( nType )
        Return Iif( nType==0,":)",":):)" )
        
oObject := __objAddInline( oObject, cInlineName, bInline )Adds the inline method in the already existing class to which the object belongs in oObject; сInlineName - the name of the method, bInline - a codeblock that implements this method.
oObject := __objAddData( oObject, cDataName )Adds a variable in the already existing class to which the object belongs in oObject; cDataName - the name of the new variable
oObject := __objModMethod( oObject, сMethodName, nFuncPtr )Replaces the implementation of the method in the existing class to which the object belongs in oObject; сMethodName - the name of the method, nFuncPtr is a pointer to a function that implements this method ( see description of functions __objAddMethod() )
oObject := __objAddInline( oObject, cInlineName, bInline )Replaces the implementation of inline method in an existing class to which the object belongs in oObject; сInlineName - the name of the method, bInline - a codeblock that implements this method. ( see description of functions __objAddInline() )
oObject := __objDelMethod( oObject, сMethodName )Delete method, or inline method with the name of сMethodName the class to which the object belongs in oObject
oObject := __objDelInline( oObject, сMethodName )Delete method, or inline method with the name of сMethodName the class to which the object belongs in oObject
oObject := __objDelData( oObject, сDataName ) Removes a variable with the name of сDataName the class to which the object belongs in oObject
lIsParent := __objDerivedFrom( oObject, xSuper ) Returns a Boolean value, indicates whether the class is xSuper (which can be set as an object or a class name ), a parent for the class to which the object belongs in oObject
oNew := __objClone( oSource ) Clone an object.
xResult := __objSendMsg( oObject, cName [,xParams...] )Send a message to an object oObject: may be used to get a value of an object variable cName or to set a new one ( in this case the variable name should be prefixed by "_", or to call an object method.

3.4 Lang API (the language of the program)

This subsystem is intended for output of alarm messages and the result of CMonth(), CDow() functions - but, unlike Clipper, it allows you to to change language of the program during execution.
To link it to your application, you must specify the hblang.lib library in the link - script and include in chief prg file the REQUEST sentenses with the names of the languages, which you intend to use, for example:

     REQUEST HB_LANG_RU866
     REQUEST HB_LANG_RUWIN
   
Hb_LangSelect( cLangID ) --> cLangIDSets the language of the program, returns the ID of the previous language.
Hb_LangName() --> cLangIDReturns the name of the program language.
Hb_LangErrMsg( nError ) --> cErrorReturns the error number.
Hb_LangMessage( nMessage ) --> cMessageReturns the text of the alarm messages by number.

3.5 Codepage API

This subsystem provides support for national code pages for your data. To link it to your application you must specify the library hbcpage.lib in a link - script and include in a chief prg file the proposal REQUEST with the names of the code pages, which you intend to use, for example:

     REQUEST HB_CODEPAGE_RU866
     REQUEST HB_CODEPAGE_RUKOI8
     REQUEST HB_CODEPAGE_RU1251
   

Further, you may determine the main code page of the application. This code page will be used for the IsUpper(), IsLower(), IsAlpha(), Upper() and Lower(), Transform (), and when comparing strings. In the console applications, one should use the OEM codepage ( "RU866" for Russian ) and in the Windows GUI application - ANSI ( "RU1251" for Russian ).
To set the code page the hb_cdpSelect() function is used

     hb_cdpSelect( sCodepage )
      // for example:
     hb_cdpSelect( "RU866" )
   

Such a possibility can be found by Clipper as well ( by connecting the special obj ), but in the Harbour, in addition to that you can define the code page for any opened file data, all the strings will be automatically translated into the main code page of the application for reading and return - when recording, the strings in dbSeek(), dbLocate(), dbSetFilter() will be translated, too. It is enough to specify the code page when opening a file, and all subsequent work with it will look as if the data is in the main code page:

      USE file_name ... CODEPAGE "RU1251"
      // or
      dbUseArea( .T.,,file_name,,.T.,.F.,"RU1251" ) 
   

By default the file opens with the main code page of the application.
This possibility ( the definition of the code page of the file ) should only be used with family RDD - DBFNDX and DBFCDX.
ADS RDD provides for these purposes other tools ( setting up the ini file or SET CHARTYPE TO OEM/ANSI ).

Codepage API also includes a function for translation of strings from one codepage to another:

      Hb_Translate( sData, sCodepageIN, sCodepageOUT )
      // for example:
      Hb_Translate( "Привет", "RU1251", "RU866" )
   

and a set of functions for utf8:

hb_StrToUtf8( sData, sCodepageIN ) Translation sData of sCodepageIN in utf8
hb_Utf8ToStr( sData, sCodepageOUT ) Translation sData from utf8 to sCodepageOUT
  
hb_Utf8Len( sData ) This and the following functions are analogs of
hb_utf8Chr( n ) the standard string functions Len(),
hb_utf8Asc( sData ) Chr(), Asc (), etc., but for rows in a
hb_utf8Substr( sData, n1, n2 ) utf8 encoding. As it is known, in utf8
hb_utf8Left( sData, n1 ) one character may be encoded with more
hb_utf8Right( sData, n1 ) than one byte. These functions operate with
hb_utf8Stuff( sData, n1, n2, cNew ) the number of symbols rather than bytes.
hb_utf8Peek( sData, n1 )  
hb_utf8Poke( sData, n1, n )  

3.6 Working with hrb - files

I have already mentioned herethat instead of the C file Harbour can create a special type of file from your prg with the .hrb extension, which contains the p-code of your program and can be executed by the utility Hbrun.exe.
But the hrb can be called for execution directly from your program - there are special functions for this:

hb_hrbRun( hrbName, ... (parameters))Executes a hrbfile with the name hrbname, passes to it the list of parameters and returns the result.
handle := hb_hrbLoad( [ nOptions, ] hrbName [, xparams,... ] )Loads into memory p-code from a hrbfile with the name hrbname, returns a pointer to these instructions; xparams - parameters, which are passed to the INIT PROCEDURE of hrb-module;
nOptions - optional parameter (if it is absent, the first will be hrbname), which may have following values (constants are defined in hbhrb.ch):
   HB_HRB_BIND_DEFAULT    0x0  /* do not overwrite any functions, ignore
                               public HRB functions if functions with the same
                               names already exist */
   HB_HRB_BIND_LOCAL      0x1  /* do not overwrite any functions, but keep local
                               references, so if module has public function FOO
                               and this function exists already, then the
                               function in HRB is converted to STATIC one */
   HB_HRB_BIND_OVERLOAD   0x2  /* overload all existing public functions */
   HB_HRB_BIND_FORCELOCAL 0x3  /* convert all public functions to STATIC ones */
   HB_HRB_BIND_LAZY       0x4  /* Doesn't check references, allows load HRB with
                               unresolved references */
         
hb_hrbDo( handle, ... (parameters))Executes p-code from hrb file, pre-loaded with the help of hb_hrbLoad(), which specifies handle, passes a list of parameters and returns the result.
hb_hrbUnload( handle )Unloads p-code, to which the handle addresses, from the memory.
  
hb_hrbGetFunsym( handle, functionName )Gets the pointer to a function with the functionName identified in the hrb file; first it is needed to load this hrb to the memory by using handle := hb_HrbLoad( hrbName ). Execution of this function can be done with the help of the Do( hFun, ... (parameters))where hFun is an index received from hb_hrbGetFunsym().
aFuncs := hb_hrbGetFunList( handle, nType )Returns functions list of hrb-module, nType defines the functions type (constants are defined in hbhrb.ch):
   HB_HRB_FUNC_PUBLIC     0x1   /* locally defined public functions */
   HB_HRB_FUNC_STATIC     0x2   /* locally defined static functions */
   HB_HRB_FUNC_LOCAL      0x3   /* locally defined functions */
   HB_HRB_FUNC_EXTERN     0x4   /* external functions used in HRB module */
         

It is needed to ensure that the application has been built with all of the functions which can be called from .hrb, otherwise the program might fall out while runtime. The best way is to put in your program

     REQUEST 
   
You can #include "hbextern.ch" - it contains the REQUEST's to all Harbour functions. The size of the program will, of course, increase at the end, but you will have a guarantee that all functions are enabled.

Note that the hrbname parameter of the hb_hrbRun() and hb_hrbload() may contain not only the name of the hrb file, but also the p-code, pre-loaded with the help of, for example, Memoread(), or received as a result of compilation with the help of hb_compileBuf() or hb_compileFromBuf() (see the description of these functions here):

      FUNCTION Main()
      Local han, buf, cBuf := ""

         cBuf += "Procedure MM" +Chr(13)+Chr(10)
         cBuf += "? 'Just a test!'" +Chr(13)+Chr(10)

         buf := hb_compileFromBuf( cBuf, "harbour", "/n" )
         hb_hrbRun( buf )
         ?
      Return Nil
   

And one more interesting moment. I have already mentioned that hrb files are very similar judjing by functionality to the p-code dll. And indeed, the function hb_hrbLoad() loads p-code in the space of your application in the same way as a function of the hb_libLoad() loads the shared library. And therefore, the functions of the hrb file can be called with the same way, i.e. directly, without any hb_hrbGetFunsym() or Do(). To do this, as in the case of using the p-code dll, you must first declare these functions in your application as DYNAMIC:

      DYNAMIC HRBFUNC1
      FUNCTION Main()
      Local x, handle := hb_hrbLoad( "my.hrb" )
      
      x := hrbFunc1()   // hrbFunc1 - the function from my.hrb
      
      hb_hrbUnload( handle )
      Return Nil
   

3.7 the Built-in C

In the Harbour there is a possibility to insert fragments of C code in the prg file. This may be conveniently, if you don't want to create a .c file for a pair of C functions and then putting it in the project. This can be done with the help of directives #pragma BEGINDUMP ... #pragma ENDDUMP.

   #pragma BEGINDUMP
      #include < extend.h >
      #include < math.h >
      HB_FUNC( SIN )
      {
         double x = hb_parnd(1);
         hb_retnd( sin( x ) );
      }
   #pragma ENDDUMP
   

3.8 Hash arrays

Hash arrays or Hash tables - see. article in Wikipedia - is one of the implementations of data structures, known under the name of associative arrays, which stores the pair ( key,value ) and allows you to perform at least three operations: add a new pair, search according to the key and delete the key. The support of associative arrays is in many interpreted high-level programming languages, for example, in Perl, PHP, Python, Ruby, and others. Now it is also implemented in the Harbour.

A new hash-array is created by the function hb_hash(), it can be used without a parameter ( in this case initialized with the empty hash array ) and with parameters, defining an arbitrary number of pairs in the array.

   FUNCTION Main
   local harr := hb_Hash( "six", 6, "eight", 8, "eleven", 11 )

      harr[10] := "str1"
      harr[23] := "str2"
      harr["fantasy"] := "fiction"

      ? harr[10], harr[23]                                   // str1  str2
      ? harr["eight"], harr["eleven"], harr["fantasy"]       // 8       11  fiction
      ? len(harr)                                            // 6
      ?

      RETURN nil
   
An alternative method for initializing the hash array:
   local harr := hb_Hash( "six" => 6, "eight" => 8, "eleven" => 11 )
   

For hash array 4 flags are defined:

    - Autoadd - when set in TRUE (the default), the operation assignment ( as harr["fantasy"] := "fiction" in the above example ) leads to the addition of a new pair, while the FALSE leads to an error;
    - Binary - when set in TRUE (the default), sorting the array is made in accordance with the weight of the binary code of the character, without taking into account the national code pages and the relevant rules of the sort. This allows you to significantly speed up the manipulation with the hash of arrays;
    - CaseMatch - defines wether to take into consideration the Case character ( big/small ) for sorting or not.     - KeepOrder - when set in TRUE, the order of the pairs in the array corresponds to the priority of their add.

Below is a table of functions for a hash of arrays.

aHash := hb_hash( [ Key1, Value1 ], ..., [ KeyN, ValueN ] ) Create, initialize the hash array
lExists := hb_hHaskey( aHash, Key ) Returns a Boolean value, indicates whether there is a pair with the key Key in the array aHash
xValue := hb_hGet( aHash, Key ) Returns the value of the pair with the key Key in the array aHash - the same as xValue := aHash[Key]
xValue := hb_hGetDef( aHash, Key, DefaultVal ) Returns the value of the pair with the key Key in the array aHash or DefaultValif the key is not found
hb_hSet( aHash, Key, xValue ) Sets the value of the pair with the key Key in the array aHash - the same as aHash[Key] := xValue
hb_hDel( aHash, Key ) Destroys a couple of key Key from the array aHash
nPosition := hb_hPos( aHash, Key ) Returns the index of the pair with the key Key in the array aHash
Key := hb_hKeyAt( aHash, nPosition ) Returns the key pair in the array aHash with the index nPosition
xValue := hb_hValueAt( aHash, nPosition, [NewValue] ) Returns the value of a pair of the array aHash with the index nPosition and establishes a new NewValue, if it is set
array := hb_hPairAt( aHash, nPosition ) Returns a two-dimensional array of key/value of the pairs in the array aHash with the index nPosition
hb_hDelAt( aHash, nPosition ) Destroys a couple of from the array aHash with the index nPosition
aKeys := hb_hKeys( aHash ) Returns an array of all the keys array aHash
aValues := hb_hValues( aHash ) Returns an array of all values array aHash
hb_hFill( aHash, xValue ) Fills the array aHash values xValue
aHash2 := hb_hClone( aHash ) Returns a copy of array aHash
aHash2 := hb_hCopy( aHash2, aHash, [nStart], [nCount] ) Copies pairs from the array aHash in aHash2. You can specify the nStart - starting position from which to copy and nCount - how many pairs to copy
aHash2 := hb_hMerge( aHash2, aHash, bBlock | nPosition ) adds pairs from the array aHash in aHash2. bBlock - codeblock, the executable for each pair of source, it is given key, value and index. If bBlock returns the truth, the pair is copied. nPosition - the index of the pair, which will be added to the aHash.
aHash := hb_hEval( aHash, bBlock, [nStart], [nCount] ) Performs codeblock for each pair of array aHash, codeblock gets the key, value and index.
nPosition := hb_hScan( aHash, xValue, [nStart], [nCount], [lExact] ) Looks for value xValue in the array aHash
aHash2 := hb_hSort( aHash ) Sorts an array aHash
lPrevFlag := hb_hCaseMatch( aHash, [lFlag] ) Sets a flag "match case" for an array of aHash and returns the previous value.
aHash := hb_hSetCaseMatch( aHash, [lFlag] ) Sets a flag "match case" for an array of aHash
lPrevFlag := hb_hBinary( aHash, [lFlag] ) Sets a flag "binary" for an array of aHash and returns the previous value.
aHash := hb_hSetBinary( aHash, [lFlag] ) Sets a flag "binary" for an array of aHash
nPrevFlag := hb_hAutoAdd( aHash, [lFlag] ) Sets a flag "auto add" to the array aHash and returns the previous value.
aHash := hb_hSetAutoAdd( aHash, [lFlag] ) Sets a flag "auto add" to the array aHash
nPrevFlag := hb_hKeepOrder( aHash, [lFlag] ) Sets a flag "keep order" for an array of aHash and returns the previous value.
aHash := hb_hSetOrder( aHash, [lFlag] ) Sets a flag "keep order" for an array of aHash
hb_hAllocate( aHash, nItems ) Reserves space for an array of aHash in the number of nItems pairs.
xPrevDef := hb_hDefault( aHash, DefaultValue ) Sets the value of the default for the array aHash and returns the previous one.

3.9 Regular expressions

Many people think ( and I thought ), that regular expression are the strings for searching, where the "*" indicates a group of any characters, and "?" - any single character, the same as that are used as masks for files. In fact it is a kind of language, enough complex - the searching strings on it can shock at first. But if mastering and understanding the logic of the language gradually you get used to its view and it ceases to be a gobbledygook.

Regular expressions are a very powerful tool for searching in the text. Many modern programming languages ( Perl, Php, Python, Ruby, Javascript, etc. ) have the built-in support of them. You can get acquainted with the regular expressions for example on pcre.ru.

Further you will find a group of functions, similar in parameters structure. Here cRegEx - a string with a regular expression ( text or already compiled ); cString - a string in which the search is conducted; lCase specifies whether to take into account the register of symbols ( by default - to - .T. ); what about the parameter lNewLine - I have not yet managed to find it out, if someone tells me - I'll be glad; nMaxMatches - the maximum number of matches, which should be returned (by default - without limitation - 0). Functions have to be looking for in line cString fragments of the corresponding regular expression, specified cRegEx. The regular expression can consist of parts that are separated by round brackets. In this case, we call the fragment of the line corresponding to the entire expression the full coincidence, and a substring of this fragment, corresponding to the relevant part of the regular expression (dedicated round brackets) - the sub-coincidence. So, for example:

     s := "Claabrok abbey" // a String, where we will look 
     cRegex : = (a+)(b+)"  // the Symbol '+' in the regular expression means that
                           // the previous symbol occur 1 or more times
     // Have 2 full matches:
     // 1) 'REC', the 'aa' is the first sub-coincidence, 'b' - the second
     // 2) 'abb'here 'a' is the first sub-coincidence, 'bb' - the second
   
aResult := hb_Regex( cRegex, cString, [lCase], [lNewLine] ) Returns a one-dimensional array, including the first full matches and all of its sub-matches (only the substring).
lResult := hb_RegexLike( cRegEx, cString, [lCase], [lNewLine] ) Returns TRUE if the string cString corresponds to the expression of cRegEx.
lResult := hb_RegexHas( cRegEx, cString, [lCase], [lNewLine] ) Returns TRUE if the line cString found at least one match expression cRegEx.
as aresult := hb_RegexSplit( cRegEx, cString, [lCase], [lNewLine], [nMaxMatches] ) The function is looking for a match in cStringexcludes the found string and returns an array of the remaining parts. It splits the string into parts with the cRegEx substrings as a separator .
as aresult := hb_RegexAtx( cRegEx, cString, [lCase], [lNewLine] ) Returns a two-dimensional array, including the first full match and all of its sub-matches, for each array with the corresponding substring, the start and end positions.
as aresult := hb_RegexAll( cRegex, cString, [lCase], [lNewLine], [nMaxMatches], [nGetMatch], [lOnlyMatch] ) Here the first five parameters were described above, nGetMatch determines the character of the returned array, if 0, the function returns both - full coincidences and sub-matches, if-1 - only complete word matches, if 2 - the first sub-match, 3 - the second sub-matching, etc.; lOnlyMatch - if .T.(the default), in the returning array only substrings are included, if - .F., then in addition to them - the initial and final positions of the found substrings. Depending on the combination of these two parameters the returned array can be one-dimensional, two-dimensional or three-dimensional. So, if nGetMatch is not equal to 0 and lOnlyMatch is the truth, then a one-dimensional array returns from substrings ( of full coincidence or sub-matches ). If nGetMatch equals 0 and lOnlyMatch is the TRUE, then a two-dimensional array returns, each element of which is an array of the substring - full coincidence and substrings - sub-coincidences ). If the same nGetMatch equals 0 and lOnlyMatch - is false, then a three-dimensional array returns - instead of each substring of the previous case an array appears, which includs the substring, its start and end position.

And another 3 functions:

pRegEx := hb_RegexComp( cRegEx, [lCase], [lNewLine] ) cRegEx is compiled- a string with a regular expression, and returns the compiled code; lCase specifies wether the register of symbols should be taken inro account ( by default - to - .T. ). It makes sense in order to accelerate the search operations if this expression will be used more than once.
lGood := hb_isRegex( cRegEx ) Returns truth if the cRegEx - compiled regular expression.
cMatch := hb_Atx( cRegEx, cString, [lCase], [@nStart], [@nEnd] ) Searches in the line cString coorespondance to the expression cRegEx; lCase specifies wether the register of symbols should be taken inro account ( by default - to - .T. ); nStart, nEnd - from what position to start the search and where end. Returns the first found substring ( or Nil, if not found ). In the nStartif it is past with the link, the position of the found substring is recorded, in the nEnd - its length.

And, finally, an example of the use of the simplest regular expression, already discussed above:

   FUNCTION Main()
   Local s := "ClaAbrok aBbey", n1, n2

      ? hb_isregex( hb_regexcomp( "a+b+" ) )                // .T.
      n1 := 1
      n2 := Len( s )
      ? hb_atx( "a+b+", s, .f., @n1, @n2 ), n1, n2          // aAb      3     3
      ? hb_regexhas( "a+b+", s, .f. )                       // .T.
      ? hb_regexhas( "a+b+", s )                            // .F.
      ? hb_regexlike( "a+b+", s, .f. )                      // .F.

      PrintArray( hb_regex( "a+b+",s,.f. ) )                // { aAb }
      PrintArray( hb_regex( "(a+)(b+)",s,.f. ) )            // { aAb aA b }

      PrintArray( hb_regexSplit( "a+b+",s,.f. ) )           // { Cl rok  ey }

      PrintArray( hb_regexAtx( "a+b+",s,.f. ) )             // { { aAb 3 5 } }
      PrintArray( hb_regexAtx( "(a+)(b+)",s,.f. ) )         // { { aAb 3 5 } { aA 3 4 } { b 5 5 } }

      PrintArray( hb_regexAll( "a+b+",s,.f. ) )             // { { aAb aBb } }
      PrintArray( hb_regexAll( "(a+)(b+)",s,.f. ) )         // { { aAb aA b } { aBb a Bb } }
      PrintArray( hb_regexAll( "(a+)(b+)",s,.f.,,,1 ) )     // { aAb aBb }
      PrintArray( hb_regexAll( "(a+)(b+)",s,.f.,,,2 ) )     // { aA a }
      PrintArray( hb_regexAll( "(a+)(b+)",s,.f.,,,0,.f. ) ) 
      // { { { aAb 3 5 } { aA 3 4 } { b 5 5 } } { { aBb 10 12 } { a 10 10 } { Bb 11 12 } } }
      PrintArray( hb_regexAll( "(a+)(b+)",s,.f.,,,1,.f. ) ) // { { aAb 3 5 } { aBb 10 12 } }

      ?
   Return Nil

   Function PrintArray( arr, lNoNewLine )
   Local i

      IF lNoNewLine == Nil .OR. !lNoNewLine
         ?
      ENDIF
      ?? " {"
      FOR i := 1 TO Len( arr )
         IF Valtype( arr[i] ) == "A"
            PrintArray( arr[i], .T. )
         ELSE
            ?? " " + Iif( Valtype( arr[i] ) == "N", Ltrim(Str(arr[i])), arr[i] )
         ENDIF
      NEXT
      ?? " }"
   Return Nil
   

3.10 INET subsystem

This is a basic set of functions, designed for the work in networks using the IP protocol. With their help it is possible to create a socket, to connect to another socket ( on the same or on the remote computer ) and arrange the reception/transmission of data. This is the basic set, and as such it implements the TCP/IP and UDP protocols. Using it, you can implement an exchange under the more high-leveled protocol - HTTP, FTP, POP3, etc. One example of such an implementation is the library hbtip, the sources of which are located in the harbour/contrib/hbtip.

lResult := hb_inetInit() Initializes the INET subsystem, returns .T. in the case of success.It should be called before any calls of other INET functions ( in the beginning of the program, for example ).
hb_inetCleanup() Frees the resources busy with the INET subsystem. It should be called at the end of a program that uses INET function.
hSocket := hb_inetCreate( [nTimeOut] ) Creates and returns the handle of the socket ( hSocket ) for connection to resources in the network; nTimeOut - timeout value for the socket in milliseconds. Timeout is set for blocking operations, such as reading and writing data, i.e. those which stop the program, put it in the standby mode of the completion of the current operation. If the time expectations are greater than the specified timeout value, the operation immediately ends and hb_inetErrorCode(hSocket) returns -1. Note that this is just one blocking operation, and not the read/write hb_inet... Some functions, for example, hb_inetRecvAll(), can cause such an operation a few times, therefore, their execution time may exceed the time-out.
The default timeout value is -1, i.e. the restriction has not been installed.
nResult := hb_inetClose( hSocket ) Closes the previously created socket with hSocket and the appropriate connection. Returns 0 by success or -1 in case of error. If you have other streams, which use this socket, the waiting time is over and they are returned an error. This function does not destroy the socket, so that other streams can refer to it to check, whether it is closed ( and, if Yes, complete the corresponding operations ).
fd := hb_inetFD( hSocket, [l] )  
nResult := hb_inetDestroy( hSocket ) Closes and destroys the socket, after calling this function socket can no longer be used. Returns 0 by success or -1 in case of error.
nResult := hb_inetStatus( hSocket ) Returns 1, if the socket exists, or -1 in the opposite case.
cResult := hb_inetCRLF() Returns the sequence CRLF ( return strings + new string )used in many of the protocols.
lResult := hb_inetIsSocket( hSocket ) Returns .T., if the passed parameter is the handle of the socket.
nMillis := hb_inetTimeout( hSocket [,nMillis] ) Sets new, if you set the second parameter nMillis, and returns the old value of the timeout for socket hSocket. Learn more about time-out, see the description of hb_inetCreate().
hb_inetClearTimeout( hSocket ) Clears ( set to -1 ) timeout value for the socket hSocket. Learn more about time-out, see the description of hb_inetCreate().
nMillis := hb_inetTimeLimit( hSocket [,nMillis] ) Sets new, if you set the second parameter nMillis, and returns the old value of the TimeLimit for the socket hSocket. TimeLimit works, if installed xCallBack - see below description of hb_inetPeriodCallback().
hb_inetClearTimeLimit( hSocket ) Clears ( set to -1 ) value TimeLimit for the socket hSocket.
nResult := hb_inetErrorCode( hSocket ) Returns the code of completing of the last operation, 0 in case of success, 1 - connection is closed, the rest - the standard error codes defined in the Winsock or Unixsockets.
cString := hb_inetErrorDesc( hSocket ) Returns a string describing the error that occurred upon execution of the last operation.
hb_inetClearError( hSocket )
nResult := hb_InetCount( hSocket ) Returns the number of characters read or recorded during the last operation.
cString := hb_InetAddress( hSocket ) Returns the address of the remote server ( or local address, if the socket belongs to a server ) in string form of four numbers, separated by dots.
cString := hb_InetPort( hSocket ) Returns the port, to which the socket is bound, or the port number of the remote socket, to which there is a connection.
xPrevCallback := hb_inetPeriodCallback( hSocket [,xCallback] ) Sets xCallBack for the socket hSocket. This is a codeblock, an array, or something else that can be executed by the function hb_execFromArray() (it is described below in section 3.13.14 ). xCallBack marks the blocking operation, when the timeout expires, established for this socket. If xCallBack returns .F., reader/writer function stops trying to start blocking operation and returns the error. If the same xCallBack returns .T., the function repeats the launch of the read/write operations - and so til the TimeLimit (see above hb_inetTimeLimit()), if it is set. If the TimeLimit is not set, the loop continues until the read/write are performed, or until the xCallBack returns .F..
So, for xCallBack to run, timeout for the socket needs to be set ; the TimeLimitif it is set, must be greater than the timeout. In this case, the xCallBack will be launched with the frequency specified by timeout value, until you end the operations read/write or til the TimeLimit, if it is set.
hb_inetClearPeriodCallback( hSocket ) Destroys xCallBackinstalled with the help of hb_inetPeriodCallback().
nResult := hb_inetGetSndBufSize( hSocket ) Returns the size of the write buffer, or -1 in case of error.
nResult := hb_inetGetRecvBufSize( hSocket ) Returns the size of the read buffer, or -1 in case of error.
nResult := hb_inetSetSndBufSize( hSocket, nSize ) Sets the size of the write buffer, or -1 in case of error.
nResult := hb_inetSetRecvBufSize( hSocket, nSize ) Sets the size of the read buffer, or -1 in case of error.

hSocket := hb_inetServer( port [,hSocket [,cBindAddr [,nListenLimit]]] ) Creates a socket and returns a handle hSocket. This socket server, it can accept connections from clients on the port port. Parameter cBindAddr serves to indicate the address of a particular interface on the computer to which must be attached the server. This is necessary in cases when on the computer there are several logical interfaces ( 2 or more network card, PPP, loopback, etc. ) and you need server to answer requests only on one of them. nListenLimit is not usually required indicate; if the socket will come nListenLimit attempts to connect from a client, and the program still not had time to process a single one of them, the next attempt will be rejected by the kernel with the message that the server is busy (busy). Usually the default value ( 10 ) enough even for heavily loaded server.
hSocket := hb_inetAccept( hSocketSrv ) Waits until some client tries to connect to the server socket hSocketSrv, created with the help of hb_inetServer(). Returns a new socket, created specially for communication with the client. In case of error returns Nil and in the hSocketSrv set the error code.
hSocket := hb_inetConnect( cAddress, nPort ) Carries out connecting to the port nPort server ( local or remote ) to the address cAddress, which can be referred to as IP address of 4 numbers separated by dots ("192.168.0.1") or as the DNS host name ("www.kresin.ru"). Returns the handle of the newly created socket hSocket.
hb_inetConnect( cAddress, nPort, hSocket ) Carries out connecting to the port nPort server ( local or remote ) to the address cAddress, which can be referred to as IP address of 4 numbers separated by dots ("192.168.0.1") or as the DNS host name ("www.kresin.ru"). Unlike the previous version of this function, use the pre-prepared socket hSocket.
hSocket := hb_inetConnectIP( cAddress, nPort ) Works similarly to hb_inetConnect(), but as the address takes only IP address and is thread safe, i.e. it can be run simultaneously from multiple threads.
hb_inetConnectIP( cAddress, nPort, hSocket ) Works similarly to hb_inetConnect(), but as the address takes only IP address and is thread safe, i.e. it can be run simultaneously from multiple threads.

nResult := hb_inetRecv( hSocket, @cResult [,nAmount] ) Reads the received data from a socket hSocket in the pre-prepared string cResult in a quantity not exceeding nAmount, if this parameter is passed, or the length of cResult otherwise; returns the number of the read bytes. The function blocks the execution of the program ( or thread ), while some of the data cannot be read from the socket, or until any error occurs ( including the ending of time ). It won't necessarily fill all the string, won't necessarily accept all data transferred in a socket - in this case, you should call it again before the end of the full data reception. To lock the stream til the end of the reception of the data, use the function hb_inetRecvAll().
nResult := hb_inetRecvAll( hSocket, @cResult [,nAmount] ) Read the received data from a socket hSocket in the pre-prepared string cResult in a quantity not exceeding nAmountif this parameter is passed, or the length of cResult otherwise; returns the number of read bytes. Unlike hb_inetRecv() blocks the flow, until the required amount of data is not read.
cResult := hb_inetRecvLine( hSocket [,@nBytesRead [,nMaxLength [,nBufSize]]] ) blocks the stream until it sequence CRLF is read and returns the resulting string. If an error occurred, or the socket is closed before CRLF is read, the function does not return anything and will set the error code. The returned line does not include CRLF. In nBytesRead,if it is transferred, there will be written the number of the received bytes, including CRLF - i.e., in the normal completion this will be the length of the the resulting string plus 2. nMaxLengthif it is passed, specifies the maximum number of bytes that can be accepted, regardless of whether received CRLF or not. nBufSize - size of the receive buffer,in default it is 80 bytes. If the string is longer then the piece of memory on the nBufSize allocates more and etc., ie for a long line the redistribution of memory can be many times required , which is not very good. So with the help of instructions nBufSize you can manage it. You can, for example, set nBufSize equal to nMaxLength - then soon the buffer will be allocated with the size nMaxLength and will not be anymore redistributed.
cResult := hb_inetRecvEndBlock( hSocket [,cBlock [,@nBytesRead [,nMaxLength [,nBufSize]]]] ) This function behaves exactly the same as hb_inetRecvLine(), but the sign of the end of the reception here is the string that is passed to the parameter cBlock. Default cBlock == CRLF, so, if this parameter is not specified, the function is identical to hb_inetRecvLine().
nResult := hb_inetDataReady( hSocket [,nMillis] ) Checks whether the socket data is available for reading and returns 1 if there is, 0 if not, and -1 in the case of the error. If you specify the nMillis the function awaits the appearance of the data in the nMillis milliseconds, and returns the result as soon as the data. If this parameter is not specified, the function returns the result immediately.
nResult := hb_inetSend( hSocket, cBuffer [,nLength] ) Sends data contained in line cBuffer through a socket hSocket, returns the number of the sent bytes, 0 if the socket was closed or -1 in case of error. The Parameter nLengthif it is passed, specifies the number of bytes to be sent. Note that this function does not guarantee that all the data will be stored, so you should check the returned number.
nResult := hb_inetSendAll( hSocket, cBuffer [,nLength] ) Sends data contained in line cBuffer through a socket hSocket, returns the number of the sent bytes , 0 if the socket was closed or -1 in case of error. Parameter nLengthif it is passed, specifies the number of bytes to be sent. Unlike hb_inetSend() this function ensures that all data will be recorded only after that it will complete its work.

aHosts := hb_inetGetHosts( cName ) Returns an array of IP addresses associated with the host called cName.
aHosts := hb_inetGetAlias( cName ) Returns an array of aliases associated with the host called cName.
hb_inetInfo()

hSocket := hb_inetDGram( [lBroadCast] ) Creates and returns a socket to work on UDP Protocol. If lBroadCast set in .T., the socket will be able to send and receive the broadcast messages; in most systems, the program must-have for this special privileges.
hSocket := hb_inetDGramBind( nPort [,cAddress [,lBroadCast]] ) Creates and returns a socket to work on UDP Protocol attached to the port nPort and to a certain logical interface, described cAddress (if it is specified). If lBroadCast set in .T., the socket will be able to send and receive the broadcast messages; in most systems the program must-have for this special privileges.
nBytesSent := hb_inetDGramSend( hSocket, cAddress, nPort, cBuffer [,nSize] ) Sends the data contained in the cBufferthrough socket hSocket on IP address cAddress, port nPort. nSize specifies the amount of data transferred; if this parameter is omitted, then all what is in cBuffer is transmitted. Returns the number of the sent bytes , or -1 in case of error.. Since the function does not guarantee that all data will be transferred, the returned value should be checked.
nBytesRead := hb_inetDGramRecv( hSocket, @cBuffer [,nSize] ) Read the received data from a socket hSocket in the pre-prepared string cResult in a quantity not exceeding nSizeif this parameter is transferred, or the length of cBuffer in the opposite case. Returns the number of the read bytes or -1 in case of error.

I propose you a useful example of using the above (INET) and below (multithreading) functions. So, this is a 2-thread application inspecting every 2 minutes the updates on the forum clipper.borda.ru ( do not forget that it must be compiled with support for multi-threading, otherwise it will not work ):

   #include "box.ch"

   REQUEST HB_CODEPAGE_RU866
   REQUEST HB_CODEPAGE_RU1251

   Static mutex1
   Static aNew := {}

   Function Main()
   Local pThread, lEnd := .F., nkey

      hb_cdpSelect( "RU866" )
      hb_inetInit()

      // Create a mutex to synchronize threads when accessing the array aNew
      mutex1 := hb_mutexCreate()
      // Create a thread to check for updates with clipper.borda.ru
      pThread := hb_threadStart( @GetData(), @lEnd )

      // Just waiting for user input.
      CLEAR SCREEN
      @ 24, 1 SAY "Press Esc to complete the program, F5 - changes"
      read
      DO WHILE ( nKey := Inkey(0) ) != 27
         IF nKey == -4    // F5
            ShowUpd()
         ENDIF
      ENDDO

      // Send via lEnd completion signal to a thread and are looking forward to it.
      lEnd := .T.
      hb_threadJoin( pThread )

      hb_inetCleanup()

   Return .T.

   // This function is called by F5 and shows the changes to the site
   Function ShowUpd()
   Local arr, bufc, i, l

      // Here we need a mutex we read an array aNew, which could in this
      // time modified by a second thread
      hb_mutexLock( mutex1 )
      IF ( l := !Empty( aNew ) )
         arr := {}
         FOR i := 1 TO Len( aNew )
            Aadd( arr, Padr( hb_translate( Trim(aNew[i,2])+": " ;
               +Trim(aNew[i,3])+" "+Trim(aNew[i,4]),"RU1251","RU866" ),64 ) )
         NEXT
      ENDIF
      hb_mutexUnLock( mutex1 )


      bufc := Savescreen( 7,7,13,73 )
      @ 7,7,13,73 BOX B_DOUBLE_SINGLE + Space(1)
      IF l
         AChoice( 8,8,12,72,arr )
      ELSE
         @ 10, 35 SAY "Ничего нового..."
         Inkey(1)
      ENDIF
      Restscreen( 7,7,13,73, bufc )
      hb_dispOutAt( 0, 69, "          ", "GR+/N" )

   Return .T.

   // This function analyzes the main page of the site and looking for there the necessary changes.
   // In order to better understand it, look at the source text of the main page clipper.borda.ru
   Static Function CheckAns( cBuf )
   Local nPos1 := 1, nPos2, aItems, aRes := {}
   Local aStru := { {"ID","C",2,0}, {"NAME","C",16,0}, {"TM","C",10,0}, {"LOGIN","C",16,0}, {"TITLE","C",32,0} }
   Local fname := "cl_borda.dbf", lFirstTime := .F.
   Field ID, NAME, TM, LOGIN, TITLE

      IF !File( fname )
         dbCreate( fname, aStru )
         lFirstTime := .T.
      ENDIF
      USE (fname) NEW EXCLUSIVE
     
      DO WHILE ( nPos1 := hb_At( "st(", cBuf, nPos1 ) ) != 0
         IF ( nPos2 := hb_At( ")", cBuf, nPos1 ) ) != 0

            aItems := hb_aTokens( Substr(cBuf,nPos1+3,nPos2-nPos1-3), ",", .T. )
            aItems[1] := Padr( Substr( aItems[1], 2, Min( aStru[1,3],Len(aItems[1])-2 ) ), aStru[1,3] )
            aItems[2] := Padr( Substr( aItems[2], 2, Min( aStru[2,3],Len(aItems[2])-2 ) ), aStru[2,3] )
            aItems[5] := Padr( Substr( aItems[5], 2, Min( aStru[3,3],Len(aItems[5])-2 ) ), aStru[3,3] )
            aItems[8] := Padr( Substr( aItems[8], 2, Min( aStru[4,3],Len(aItems[8])-2 ) ), aStru[4,3] )
            aItems[9] := Padr( Substr( aItems[9], 2, Min( aStru[5,3],Len(aItems[9])-2 ) ), aStru[5,3] )

            IF lFirstTime
               APPEND BLANK
               REPLACE ID WITH aItems[1], NAME WITH aItems[2], TM WITH aItems[5], ;
                     LOGIN WITH aItems[8], TITLE WITH aItems[9]
            ELSE
               LOCATE FOR ID == aItems[1]
               IF !Found()
                  APPEND BLANK
                  REPLACE ID WITH aItems[1], NAME WITH aItems[2], TM WITH aItems[5], ;
                        LOGIN WITH aItems[8], TITLE WITH aItems[9]
                  Aadd( aRes, {aItems[1],aItems[2],aItems[8],aItems[9]} )
               ELSEIF TM != aItems[5] .OR. LOGIN != aItems[8] .OR. TITLE != aItems[9]
                  REPLACE TM WITH aItems[5], LOGIN WITH aItems[8], TITLE WITH aItems[9]
                  Aadd( aRes, {aItems[1],aItems[2],aItems[8],aItems[9]} )
               ENDIF
            ENDIF

            nPos1 := nPos2 + 1
         ELSE
            EXIT
         ENDIF
      ENDDO
      USE

   Return aRes

   // This is the 2nd flow
   Function GetData( lEnd )
   Local nCount := 0, hSocket, cUrl, cServer, cBuf, aRes

      cServer := "clipper.borda.ru"
      cURL := "GET http://" + cServer + "/ HTTP/1.1" +Chr(13)+Chr(10)
      cURL += "Host: " + cServer + Chr(13)+Chr(10)
      cURL += "User-Agent: test_util"+Chr(13)+Chr(10)
      cUrl += Chr(13)+Chr(10)

      DO WHILE !lEnd
         IF nCount == 0
            hb_dispOutAt( 0, 61, "Читаем.", "GR+/N" )
            hSocket := hb_inetCreate()                   // create a socket
            hb_inetConnect( cServer, 80, hSocket )       // connect to the web site of the forum
            IF hb_inetErrorCode( hSocket ) != 0
               hb_dispOutAt( 0, 61, "Сбой...", "GR+/N" ) 
            ENDIF

            // Send the request, formed above,  and waiting for a reply.
            IF hb_inetSendAll( hSocket, cURL ) > 0 .AND. !Empty( cBuf := hb_inetRecvEndBlock( hSocket, "main2(",,,4096 ) )
               IF !Empty( aRes := CheckAns( cBuf ) )
                  hb_dispOutAt( 0, 69, "!! Есть !!", "GR+/N" )
                  // Use a mutex for the safe aNew modification
                  hb_mutexLock( mutex1 )
                  aNew := aRes
                  hb_mutexUnLock( mutex1 )
               ENDIF
               hb_dispOutAt( 0, 61, "       ", "GR+/N" )
            ELSE
               hb_dispOutAt( 0, 61, "Сбой...", "GR+/N" )
            ENDIF

            // Close the socket
            hb_inetClose( hSocket ) 
         ENDIF
         hb_idleSleep(2)
         IF ++nCount >= 60
            nCount := 0
         ENDIF
      ENDDO

   Return Nil
   

When changing the engine of the forum this program may stop working, because it implies the presence of certain strings in the html code - searches for calls of javascript functions main2() and st().

3.11 Multithreading

Multithreading is a programming model that allows multiple threads to run within the same process, the same application, interacting with each other, sharing the resources of this application. Threads are performed by the operating system in parallel. If the system is a single-processor, it is a quasiparallelism provided by the fact that the OS allocates the time between threads. In a multiprocessor system threads can actually be performed in parallel on different processors, providing a general improvement in the performance of the application. It is often convenient to use separate threads for any action related to the lengthy computation or a lengthy waiting time of any event, blocking the execution of the program ( for example, when working in a network using sockets ); while the main thread continues to respond to user's actions.

The Harbour application can be built for a single-threaded execution and for multi-threading. In the last case it is necessary to use a multi-threaded version of the Harbour virtual machine - the hbvmmt.lib library instead of a single-threaded hbvm.lib and the library of your C compiler, containing functions for the realization of multi-threading (for Borland C it is cw32mt.lib). The most simple solution is to build your application using hbmk2 with the key -mt. Nothing prevents you from putting in the multi-threaded mode normal single-threaded applications, but the way they will work is a little slower and it will take a little more space on the disk and in memory.

When creating a new thread inherits from the parent:
    - the code page installed hb_cdpSelect(),
    - language ( hb_langSelect() ),
    - all SET options,
    - RDD by default,
    - GT driver and console window,
    - options of I18N.

These settings are initialized to its initial value ( a new copy is created ):
    - public variable Getlist := {},
    - an error handler, initialized by the Errorsys() call,
    - the handler of mathematical errors,
    - installation of a macrocomiler ( set hb_setMacro() ),
    - RDDI_* installation in standard RDD ( third-party RDD can use the global settings ),
    - thread static variables.

Individual Public and Private variables can be passed to the thread when it is created.

The following resources are used together:
    - functions and procedures,
    - definition of classes,
    - modules RDD,
    - GT drivers,
    - language modules,
    - modules of code pages,
    - static variables.

Local resources of a thread:
    - Public and Private variables, except those that are used together,
    - working areas (workareas),
    - thread static variables.

Below is a list of functions that are intended for management of threads, their creation and completion of:

pThID := hb_threadStart( @sStart() | bStart | cStart [, params,... ] ) Creates the new thread, and returns a pointer to it pThID, or Nil if the thread's creation failed. The first parameter of hb_threadStart() determines what code a new thread will execute. This can be a pointer to a function, codeblock or the name of the function passed as a text string. Following is a list of parameters passed to the function.
pThID := hb_threadStart( nThreadAttrs, @sStart() | bStart | cStart [, params,... ] ) This way of using the hb_threadStart() is different from the previous, it has the first parameter nThreadAttrs, containing the attributes of the thread. These attributes are described in hbthread.ch:
   #define HB_THREAD_INHERIT_PUBLIC 1
   #define HB_THREAD_INHERIT_PRIVATE 2
   #define HB_THREAD_INHERIT_MEMVARS 3
   #define HB_THREAD_MEMVARS_COPY 4
        
For instance, if we write hb_threadStart( hb_bitor(HB_THREAD_INHERIT_PUBLIC,HB_THREAD_MEMVARS_COPY),@thFunc() ), the thread receives copies of all Public variables of the parent, and if hb_threadStart( HB_THREAD_INHERIT_MEMVARS,@thFunc() ), the thread shares all Public and Private variables with the parent, see the example harbour/tests/mt/mttest08.prg.
pThID := hb_threadSelf() Returns the index of the thread from which this functionis called . It can return Nil, if the stream is created not by means of the Harbour.
nThNo := hb_threadId( [ pThID ] ) Returns the identifier of the the thread in the index.
lOk := hb_threadJoin( pThID [, @xRetCode ] ) Suspends execution of the current thread, until you end the stream, on which the pThID points to.
lOk := hb_threadDetach( pThID ) Disables the thread which pThID indicates , making it separate. Now it is no longer necessary to attach function hb_threadJoin (); it will release all their resources automatically after the completion of its work.
lOk := hb_threadQuitRequest( pThID ) Sends to the operating system a request to complete the thread, which pThID indicates and suspends the current thread, waiting for the implementation of the completion of the thread of pThID. Please note that this thread is "killed" from the outside, and the result of its work is unspecified.
hb_threadTerminateAll() Sends a request to the completion of the all threads and waits till it happens. A function can be called only from the main thread.
hb_threadWaitForAll() Waits for all the threads to be completed.
nRes := hb_threadWait( pThID | apThID [, nTimeOut] [, lAll] ) Waits nTimeOut seconds ( or unlimited period of time, if this parameter is not specified ) for the completion of the thread pThID, or one of the threads in the array of pointers apThID, or all the threads of this array, if the parameter lAll is specified and it is equal to .T.. Returns the number of the comleted thread, or the number of threads comleted in the period of nTimeOut seconds, if lAll is equal to .T..
lOk := hb_mtvm() Returns TRUE, if your program is compiled with the ability to create threads.

Using these functions, we can now write the first simple multi-threaded applications, for example this one, to display a clock on the screen:

   FUNCTION Main()
      LOCAL cVar := Space( 20 )

      CLEAR SCREEN

      IF !hb_mtvm()
         ? "There is no support for multi-threading, clocks will not be seen."
         WAIT
      ELSE
         hb_threadStart( @Show_Time() )
      ENDIF

      @ 10, 10 SAY "Enter something:" GET cVar
      READ
      SetPos( 12, 0 )
      ? "You enter -> [" + cVar + "]"
      WAIT

      RETURN Nil

   FUNCTION Show_Time()
      LOCAL cTime

      DO WHILE .T.
         cTime := Dtoc( Date() ) + " " + Time()
         hb_dispOutAt( 0, MaxCol() - Len( cTime ) + 1, cTime, "GR+/N" )
         hb_idleSleep( 1 )
      ENDDO

      RETURN nil
   

We have created a thread that began to perform the function of Show_Time(), constantly showing the current time in the upper right corner of the screen. Please note, that the function hb_dispOutAt() is used for showing the time on the screen, it does not change the current cursor position and the color of the console.

If another function were used, for example, DispOut(), the cursor would have moved from the area of input in the main thread, this would not help the preservation and restoration of its position in Show_Time(). The fact is that the operating system can interrupt the thread and pass control to the other at any moment of time, in the middle of any operation. You, for example, are restoring the position of the cursor using the function SetPos(), but at any stage of its implementation ( as it is a long serie of native code operations ) the operating system can interrupt it and return control to the main thread, or any other, who also modifies at that moment the position of the cursor and the result will be unpredictable. Therefore,in cases when the use of any shared resources ( most often a variable ), required the synchronization tool of the work threads so that they do not address to the general resources at the same time. These means are semaphores, and one of them - mutex. Look below a description of the relevant functions:

pMtx := hb_mutexCreate() Creates mutex and returns its handle.
lLocked := hb_mutexLock( pMtx [, nTimeOut] ) Blocks mutex pMtx for the current thread. If this mutex is already blocked by the other thread, then the current thread is suspended until the stream unlocks mutex pMtx, or until the nTimeOut milliseconds, if this parameter is passed. Returns .T. if pMtx is locked.
lOk := hb_mutexUnlock( pMtx ) Unlocks the previously blocked mutex pMtx.
hb_mutexNotify( pMtx [, xVal] ) Sends a notification to the next thread, signed to mutex pMtx with the help of the function hb_mutexSubscribe() or hb_mutexSubscribeNow() and, therefore, gives him the opportunity to continue the work. If multiple threads are signed on pMtx then the operating system itself chooses, which of them to send the notification to. The parameter xVal is transmitted to the unlocked thread, through the function hb_mutexSubscribe() of the thread, that writes xVal in the passed by a reference third parameter.
hb_mutexNotifyAll( pMtx [, xVal] ) is similar to hb_mutexNotify(), but, unlike it, sends a notice not to one, but to all threads, "signed" on mutex pMtx.
lSubscribed := hb_mutexSubscribe( pMtx, [nTimeOut] [, @xVal] ) The function suspends the execution of the current thread at a time in nTimeOut milliseconds ( or for an unlimited time, if this parameter is not specified ), until another thread calls hb_mutexNotify() or hb_mutexNotifyAll() for the same mutex pMtx. In the passed by the reference xVal the value received from hb_mutexNotify() of the second parameter ( hb_mutexNotify() ) is recorded . The function returns .T. if it is ended as a result of receipt of the notification, and .F. if - due to timeout.
lSubscribed := hb_mutexSubscribeNow( pMtx, [nTimeOut] [, @xSubscribed] ) Is similar to the previous function hb_mutexSubscribe(), but, unlike it ignores the notification sent by challenges hb_mutexNotify() before the start of work of the function.
xResult := hb_mutexEval( pMtx, bCode | @sFunc() [, params,...] ) Consistently performs hb_mutexLock( pMtx ), the action specified by codeblock bCode or pointer @sFunc() with parameters params,... and hb_mutexUnlock( pMtx ).
hb_mutexQueueInfo( pMtx, @nWaiters, @nEvents ) Provides information about a queue for mutex pMtx. In nWaiters the number of the recorded thread, "signed" on pMtx, in nEvents - the number of notifications, not arrived to the subscribers.

So, to prevent simulteneous addressing of different threads to the same resource, do the following:
    1) create mutex: pMtx := hb_mutexCreate()
    2) in those parts of the program which refer to protected resources, wrap the code lock/unlocked mutex, but, i.e., put before it hb_mutexLock( pMtx ) and after it hb_mutexUnLock( pMtx ) - the same as if we are blocking/unblocking a record of an shared database while recording in it. Don't forget that, unlike rlock(), hb_mutexLock() suspends the further execution of a thread, if mutex is already blocked by something - cases of carelessness can cause the deadlock - the situation, when several threads mutually block the execution of each other.

However, in our example with the clock this is not the case - if we used Setpos() and Dispout(), mutex would not have helped us. In the main thread the cursor control is not directly in our program, but somewhere within the Harbour's RTL, in the implementation of the function Readmodal() - and we can't insert there a lock mutex. Therefore let's expand a little our example by adding there the parameter setting of the date display:

   #include "box.ch"

   Static mutex1
   Static cOpt := "DD.MM.YY"

   FUNCTION Main()
      LOCAL cVar := Space( 20 )

      CLEAR SCREEN

      IF !hb_mtvm()
         ? "There is no support for multi-threading, hours will not be seen."
         WAIT
      ELSE
         mutex1 := hb_mutexCreate()
         hb_threadStart( @Show_Time() )
      ENDIF

      SET KEY -4 TO Options
      @ 10, 10 SAY "Enter something:" GET cVar
      READ
      SET KEY -4 TO
      SetPos( 12, 0 )
      ? "You enter -> [" + cVar + "]"
      WAIT

      RETURN Nil

   FUNCTION Show_Time()
      LOCAL cTime

      DO WHILE .T.
         hb_mutexLock( mutex1 )
         cTime := Iif( !Empty( cOpt ), hb_Dtoc( Date(),cOpt ), Space(10) ) + " " + Time()
         hb_mutexUnLock( mutex1 )
         hb_dispOutAt( 0, MaxCol() - Len( cTime ) + 1, cTime, "GR+/N" )
         hb_idleSleep( 1 )
      ENDDO

      RETURN nil

   Function Options
   Local bufc, n, aOpt := { "DD.MM.YYYY","DD/MM/YYYY","DD.MM.YY","YYYY.MM.DD","       " }

      bufc := Savescreen( 7,31,13,49 )
      @ 7,31,13,49 BOX B_DOUBLE_SINGLE + Space(1)
      IF ( n := AChoice( 8,32,12,48,aOpt ) ) != 0
         hb_mutexLock( mutex1 )
         cOpt := aOpt[n]
         hb_mutexUnLock( mutex1 )
      ENDIF
      Restscreen( 7,31,13,49, bufc )

   Return Nil
   

So we have a variable cOpt, which contains the display format of the dates, the function Options(), which is invoked by pressing the F5 key and changes the string format. Since the thread executing Show_Time(), uses the string, we use mutex to avoid collisions.

Another example of a multithreaded program with mutex see in the previous section.

2 more functions to synchronize actions between threads:

lFirstCall := hb_threadOnce( @onceControl [, bAction] ) Performs bAction one time, onceControl - variable, which stores the status of the implementation, it should be initialized to Nil, usually this is a static variable. When bAction is performed by the thread all other threads which call at this moment the hb_threadOnce(), are suspended, even if they use a different onceControl. If a thread tries to call hb_threadOnce() with the same onceControl recursively from bAction, the function immediately terminates without executing bAction and returns .F.. The function returns a boolean value that indicates wether it was the first call of hb_threadOnce() for this onceControl.
lInit := hb_threadOnceInit( @item, value ) This function is similar to the hb_threadOnce(), it writes value in nItemif nItem is equal to Nil.

Now about the access to the databases in multi-threaded applications. As already noted above, working area (workareas) in the Harbour are local to the thread, i.e. each thread has its own independent workspaces and aliases, and, in the general case, if you need a database in the thread "B" which is already opened in the thread "A", you need to open it in the "B" agein, which is easy to do in shared mode.

But there is another possibility - you can transfer the workspace from one thread to another, using the known by users xBase++ zero space. Two following options are designed for this:

lOk := hb_dbDetach( [nWorkArea|cAlias] [, xCargo] ) Disconnects the working area, specified by number nWorkArea or by the name of the alias cAlias from the current thread, moving it to zero space. It is possible to transmit information through xCargo to the threads, which will use it.
lOk := hb_dbRequest( [cAlias] [, lNewArea] [,@xCargo] [,lWait]) Requests working area of zero space by the name of the alias cAlias; lNewArea specifies whether to use a new workspace ( as in dbUseArea() )or not, lWait - wether it should be waited until the requested working area is available. The function returns .T. if all was successfully.

This mechanism allows to use workspaces by different threads together . To illustrate it we shall take 2 commands:

      #xcommand UNLOCK WORKAREA [] => hb_dbDetach(  )
      #xcommand LOCK WORKAREA    => hb_dbRequest( , .T.,, .T. )
   

After the opening table by the command Use the thread executes UNLOCK WORKAREA and, i.e., moves it in zero space. Now each thread, which needs this table, can get access to it, for example:

      LOCK WORKAREA "DOCUMENTS"
         COUNT TO nInvoices FOR year( DOCUMENTS->DATE ) == year( date() )
      UNLOCK WORKAREA
   

Other examples of multi-threaded programs you can find in the harbour/tests/mt.

3.12 File IO API

Harbour inherited from the Clipper a set of functions FOpen(), FSeek(), FRead(), ..., that are designed to work with the files.

This collection supplemented with new functions, a complete list is provided in subsection 3.13.2 File functions.

New File IO API is a set of functions with hb_vf prefix, similar to the common file functions, with the same set of parameters and the same functionality. An implementation of these functions allows to use replaceable drivers like RDDs. Now, if you put a prefix of any of the existing drivers with a colon at the end in front of the file name ( "mem:", "net:", ... ), a communication with this file will go through appropriate driver. The following function call, for example:

      hb_vfCopyFile( "test.txt", "mem:test.txt" )
will create a copy of a "test.txt" in a memory (hbmemio driver).

IO drivers are the contributed Harbour libraries, such as hbnetio, hbmemio, hbgzio, hbbz2io, hbcomio and others.

Functions list:

lOk := hb_vfExists( cFileName, [ @cDestFileName ] ) Checks, if a file cFileName exists, matches the File() function.
nResult := hb_vfErase( cFileName ) Deletes a file cFileName, matches the FErase() function.
nResult := hb_vfRename( cFileSrc, cFileDst ) Renames a file cFileSrc into cFileDst, matches the FRename() function.
nResult := hb_vfCopyFile( cFileSrc, cFileDst ) Copies a file cFileSrc into cFileDst, matches the FCopy() function.
lExists := hb_vfDirExists( cDirName )Checks, if a directory cDirName exists, matches the DirExists().
nSuccess := hb_vfDirMake( cDirName ) Creates a new directory cDirName, matches the MakeDir().
nSuccess := hb_vfDirRemove( cDirName ) Deletes a directory cDirName, matches the DirRemove().
aDirectory := hb_vfDirectory( [ cDirSpec ], [ cAttr ] ) Returns an array with info about a directory, matches the Directory() function.
nFreeSpace := hb_vfDirSpace( cDirName, [ nInfoType ] )  
lOk := hb_vfAttrGet( cFileName, @nAttr ) Stores in a nAttr variable, passed by a reference, an attributes of a cFileName file; matches the hb_fGetAttr() function.
lOk := hb_vfAttrSet( cFileName, nAttr ) Sets attributes nAttr to a cFileName file; matches the hb_fSetAttr() function.
lOk := hb_vfTimeGet( cFileName, @tsDateTime ) Stores in tsDateTime date and time of a cFileName modification; matches partially the hb_fGetDateTime() function.
lOk := hb_vfTimeSet( cFileName, tsDateTime ) Sets a date and time (tsDateTime) of a cFileName modification; matches partially the hb_fSetDateTime() function.
nSuccess := hb_vfLink( cExistingFileName, cNewFileName ) Creates a hard link cNewFileName to a file cExistingFileName, matches the hb_fLink() function.
nSuccess := hb_vfLinkSym( cTargetFileName, cNewFileName ) Creates a symbolic link cNewFileName to a file cTargetFileName, matches the hb_fLinkSym() function.
cDestFileName := hb_vfLinkRead( cFileName ) Returns a value (a full path) of a symbolic link, matches the hb_fLinkRead() function.
pHandle := hb_vfOpen( [@]cFileName, [ nModeAttr ] ) Opens cFileName file, matches the FOpen() function.
lOk := hb_vfClose( pHandle ) Closes an opened file, matches the FClose() function.
lOk := hb_vfLock( pHandle, nStart, nLen, [ nType ] ) Locks a file fragment from nOffsetFrom to nOffsetTo, matches the hb_FLock() function.
lOk := hb_vfUnlock( pHandle, nStart, nLen ) Unlocks a file fragment from nOffsetFrom to nOffsetTo, matches the hb_FUnLock() function.
nPID := hb_vfLockTest( pHandle, nStart, nLen, [ nType ] ) 
nRead := hb_vfRead( pHandle, @cBuff, [ nToRead ], [ nTimeOut ] ) Reads a block with a nToRead length, beginning from a current offset, matches the FRead() function.
cBuffer := hb_vfReadLen( pHandle, nToRead, [ nTimeOut ] ) Reads nToRead bytes from an opened file, returns a read buffer, matches the hb_FReadLen() function.
nWritten := hb_vfWrite( pHandle, cBuff, [ nToWrite ], [ nTimeOut ] ) Writes to an opened file from a current offset, matches the FWrite() function.
nRead := hb_vfReadAt( pHandle, @cBuff, [ nToRead ], [ nAtOffset ] ) Reads a block with a nToRead length, beginning from an offset nAtOffset.
nWritten := hb_vfWriteAt( pHandle, cBuff, [ nToWrite ], [ nAtOffset ] ) Writes to an opened file, from a given offset nAtOffset.
nOffset := hb_vfSeek( pHandle, nOffset, [ nWhence ] ) Sets a pointer to a given position in an opened file, matches the FSeek() function.
lOk := hb_vfTrunc( pHandle, [ nAtOffset ] )  
nSize := hb_vfSize( pHandle | cFileName [, lUseDirEntry ] ) Returns a size of a cFileName file, matches the hb_FSize() function.
lEof := hb_vfEof( pHandle )  
hb_vfFlush( , [ lDirtyOnly ] )  
hb_vfCommit( pHandle )  
nResult := hb_vfConfig( pHandle, nSet, [ nParam ] )  
nOsHandle := hb_vfHandle( pHandle )  
pHandle := hb_vfTempFile( @cFileName, [ cDir ], [ cPrefix ], [ cExt ], [ nAttr ] ) Creates temporary file, matches the hb_fTempCreateEx() function.
cFileBody := hb_vfLoad( cFileName, [ nMaxSize ] ) Reads the whole file (but no more, than nMaxSize, if it is set), matches the hb_Memoread() function.

3.13 Miscellaneous functions

In this section we will cover a variety of new features that were not included in previous thematic sections.

3.13.1 Built-in compiler

Three functions using the built-in compiler (hbcplr.lib), based on the same code, that harbour.exe itself
nRetCode := hb_compile( "harbour", cFileName, [...] ) Compiles the file cFileName with the parameters of compilation passed to it. The result of its work is the .c file.
cHrb := hb_compileBuf( "harbour", cFileName, [...] ) Unlike hb_compile(), it does not create .c file, and returns the created as a result of compiling p-code in the form of a text string, which can be saved as a hrb file or comply with the help of hb_hrbRun()
cHrb := hb_compileFromBuf( cPrg, "harbour", [...] ) Compiles the code from the clipboard cPrg, returns p-codeas hb_compileBuf()
Below is an example of the use of run-time compilation:
      FUNCTION Main( cFileName )
      Local handle, buf, cBuf := "", i

         buf := hb_compilebuf( "harbour", cFileName, "/n","/w" )  // Compile
         hb_hrbRun( buf )                                         // Execute

         handle := FCreate( Iif((i:=Rat('.',cFileName))=0,cFileName,Substr(cFileName,1,i-1)) + ".hrb" )
         FWrite( handle, buf )
         FClose( handle )

      Return Nil
   

3.13.2 File functions.

lSuccess := hb_fGetAttr( cFileName, @nAttr ) Writes to a passed by the reference variable nAttr the attribute of a file with the name cFileName.
lSuccess := hb_fSetAttr( cFileName, nAttr ) Sets the file attributes for the cFileName specified in nAttr.
lSuccess := hb_fGetDateTime( cFileName, @dDate [, @cTime] ) Records in dDate and cTime, respectively, the date and time of a cFileName modification.
lSuccess := hb_fSetDateTime( cFileName, dDate [, cTime] ) Sets the date and time of a cFileName modification.
hb_fcommit( nHandle )  
hb_fisdevice( nHandle )  
hb_flink() Creates a hard link cNewFileName to a file cExistingFileName.
hb_flinkread() Returns a value (a full path) of a symbolic link.
hb_flinksym() Creates a symbolic link cNewFileName to a file cTargetFileName.
hb_flock( nHandle, nOffsetFrom, nOffsetTo ) Locks a file fragment from nOffsetFrom to nOffsetTo.
hb_funlock( nHandle, nOffsetFrom, nOffsetTo ) Unlocks a file fragment from nOffsetFrom to nOffsetTo.
cReadBuf := hb_freadlen( nHandle, nToRead ) Reads nToRead bytes from an opened file, returns a read buffer.
hb_fsetdevmode( nHandle, nMode )  
hb_fsize( cFileName[, lUseDirEntry] ) Returns a size of a cFileName file.
nHandle := hb_fTempCreate( [cPath], [cPrefix], [nAttr], [@cFileName] ) Creates a temporary file, with the attributes nAttr, with a prefix cPrefix, the path to which is specified in cPath. The function returns handle of the opened file , and writes its name to the cFileName, passed by the reference. The attributes of the file ( such as symbolic names defined in the fileio.ch ) can have the following values:
           FC_NORMAL          0
           FC_READONLY        1
           FC_HIDDEN          2
           FC_SYSTEM          4
        
nHandle := hb_fTempCreateEx( [@cFileName], [cPath], [cPrefix], [cExt], [nAttr] ) Makes the same as the previous function, hb_fTempCreate(), differs from it in that it allows to set the extension of the created file cExt and the order of the arguments is other.
lResult := hb_FileDelete( cFileMask [, cAttr ] ) This function removes files which match given cFileMask (it may contain path) and returns .T. if at least one file was deleted. Optional cAttr parameter can be used to include system ("S") and hidden ("H") files. If cAttr contains "R" letter then before deleting READONLY attribute is removed from files (it's necessary to remove files with READONLY attribute in systems like DOS, MS-Windows or OS2). This function uses Harbour File IO API (hb_vf*() functions).

3.13.3 Set of functions, manipulating with file path, name, extension.

cRes := hb_FNameDir( cFullPath ) Returns a path of a file.
cRes := hb_FNameName( cFullPath ) Returns a name of a file without an extension.
cRes := hb_FNameExt( cFullPath ) Returns an extension of a file.
cRes := hb_FNameNameExt( cFullPath ) Returns a name of a file with extension.
cRes := hb_FNameExtSet( cFullPath, cExt ) Sets a new extension of a file, returns new full path.
cRes := hb_FNameExtSetDef( cFullPath, cExt ) Sets a new extension of a file in case if it is absent, returns new full path.

3.13.4 String functions

cString := hb_strFormat( cFormat, ... ) Returns a string formatted in the C-style, as in printf().
nResult := hb_HexToNum( cHex ) Similar function to the Val(), but the string cHex contains 16 decimal number.
cHex := hb_NumToHex( num[, nLen] ) Converts the number num in a string of nLen in the 16 system.
nTokens := hb_TokenCount( cString, [cDelim], [lSkipStrings], [lDoubleQuoteOnly])This and the following 3 functions divide a string cString to the elements, the separator is a cDelim ( the default is a blank " " ). A space as a separator has the peculiarity that multiple spaces in a row are considered as one. Parameter lSkipStrings, if it is set to .T., indicates that at the decomposition of the elements strings should be skiped, i.e., if delimiter is located between the quotes, double or single, it is not considered as separator. lDoubleQuoteOnly specifies that the string is only a group of characters enclosed in double quotation marks.
hb_TokenCount() returns the number of elements in the row according to the above rules.
cToken := hb_TokenGet( cString, nToken, [cDelim], [lSkipStrings], [lDoubleQuoteOnly])Splits the string and returns the item at the number nToken in accordance with the rules set out in the description of hb_TokenCount().
cToken := hb_TokenPtr( cString, @nSkip, [cDelim], [lSkipStrings], [lDoubleQuoteOnly])Operates as hb_TokenGet(), but allows you to go rummaging through the elements, without splitting each time anew. The second parameter nSkiptransmitted by the link that specifies what number of characters in a line should be omitted, the function writes in it the new value, which can be used by its next call.
aTokens := hb_ATokens( cString, [cDelim], [lSkipStrings], [lDoubleQuoteOnly])Splits the string and returns an array with all the elements in accordance with the rules set out in the description of hb_TokenCount().

3.13.5 Array functions

aArray := hb_ADel( aArray, nPos [,lChangeSize] ) Extension of a standard function ADel(). Deletes specified array item, and, if lChangeSize is set to .T., decreases its size - i.e., may be used instead of a couple ADel(), ASize().
aArray := hb_AIns( aArray, nPos [,xItem] [,lChangeSize] ) Extension of a standard function AIns(). Inserts xItem to a specified array position, and, if lChangeSize is set to .T., increases its size - i.e., may be used instead of a couple AIns(), ASize().
nResult := hb_Ascan( aArray, [block], [nStart], [nCount], [lExact] ) Extension of a standard function Ascan() - it does the same, and, if lExact is set to .T., evaluates exact comparison.
nResult := hb_RAscan( aArray, [block], [nStart], [nCount], [lExact] ) Works like the hb_Ascan(), but starts a search from the end of an array.

3.13.6 Processes

nResult := hb_ProcessRun( cCommand, [cStdIn], [@cStdOut], [@cStdErr], [lDetach] )  
handle := hb_ProcessOpen( cCommand, [cStdIn], [@cStdOut], [@cStdErr], [lDetach] )  
nResult := hb_ProcessValue( handle, [lWait] )  
nResult := hb_ProcessClose( handle, lGentle )  

3.13.7 Bit operations

z := hb_BitAnd( x, y ) Returns the result of the bitwise operations And x & y
z := hb_BitOr( x, y ) Returns the result of the bitwise operations OR x | y
z := hb_BitXor( x, y ) Returns the result of the bitwise operations Exclusive OR x ^^ y
y := hb_BitNot( x ) Returns the result of the bitwise operations NOT x
y := hb_BitTest( x, n ) Checks wether the 1 n-th ( starting with 0 ) are installed in bit number x
y := hb_BitSet( x, n ) Returns the number of the 1 n-m ( starting with 0 ) bit number x
y := hb_BitReset( x, n ) Returns the number thrown in 0 n-m ( starting with 0 ) bit number x
z := hb_BitShift( x, y ) Bit shift x << y, if you have x >> y, then y must be negative
y := hb_BitSwapI( x ) Returns the byte swap in 16-bit number x
y := hb_BitSwapW( x ) Returns the byte swap in 16-bit unsigned including x
y := hb_BitSwapL( x ) Returns the result of the rearrangement of bytes in a 32-bit number x
y := hb_BitSwapU( x ) Returns the result of the rearrangement of bytes in 32-bit unsigned including x

3.13.8 Functions for a manipulation with variables

__mvPublic( cVarName ) Creates a Public variable named cVarName
__mvPrivate( cVarName ) Creates a Private variable named cVarName
nScope := __mvScope( cVarName ) Returns the scope (range, class) variable (constants are defined in hbmemvar.ch):
         HB_MV_NOT_FOUND      = переменная не найдена
         HB_MV_UNKNOWN        = переменная не существует, но есть в symbol table
         HB_MV_ERROR          = информация недоступна ( ошибка памяти или аргумента )
         HB_MV_PUBLIC         = Public переменная
         HB_MV_PRIVATE_GLOBAL = Private переменная, определена за пределами текущей функции/процедуры
         HB_MV_PRIVATE_LOCAL  = Private переменная, определена в текущей функции/процедуре
      
__mvClear() Frees all Public and Private variables, implements the CLEAR MEMORY
__mvDbgInfo( nScope [,nPosition [,@cVarName]] ) Returns information about the variables corresponding to the range of nScope ( HB_MV_PUBLIC,... - look description __mvScope() ) for debugger; nPosition - the position of the requested variable in the range nScope, cVarName is passed by reference, the name of the variable is written it it , if nPositionis set. The value returned by the function depends on the transmitted parameters. If only the first parameter is passed, it returns the number of variables set by nScopeif the second too ( nPosition ), then the value of the corresponding variable. If the requested variable does not exist (nPosition) more than the number of variables, then NIL returns, and in the cVarName "?" is written.
lVarExist := __mvExist( cVarName ) Returns .T. if the variable cVarName ( Private or Public ) exists
xVal := __mvGet( cVarName ) Returns the value of a variable cVarName ( Private or Public )
xVal := __mvPut( cVarName, xVal ) Sets the value of the xVal variable cVarName ( Private or Public )
lIsByRef := hb_IsByRef( @cVarName ) Checks whether cVarName was transferred on the link, it must be passed by reference in the function hb_IsByRef().
__mvSetBase() This is the so called hacking functionit should be used very carefully and with full understanding of what you are doing, because it breaks the standard, the correct behavior of the program - changes the basic drift of the list of Private variables. The call of this function causes the fact that the Private variables created in the current function, are not released on a way out of it, but are inherited by the function, from which the current one was called.

3.13.9 hb_WildMatch...()

lResult := hb_wildMatch( cPattern, cString, [lExact] ) Function compares the string cString with a sample of cPattern and returns the result - true or false. The pattern can contain the characters "?" and "*" ( as it is known, on the place of "?" in cString can be used one of any character, on-site "*" - a few characters of any kind ). If lExact - .T., the entire string must conform to the model, if - .F. (the default), then at least its start.
lResult := hb_wildMatchI( cPattern, cString ) The same as hb_wildMatch()but it is insensitive to the case and hasn't a third parameter - requires a precise comparison.
hb_filematch( cFile, cPattern )  

3.13.10 Strings packing/unpacking, based on zlib

cVersion := hb_zlibVersion() Returns a string with the number of the version of zlib, used in the Harbour.
nbytes := hb_zCompressBound( cData | nDataLen ) Returns the maximum length of a compressed string, the string cData, which must be repackaged, or its length itself is passed to the function.
nbytes := hb_zunCompressLen( cPackedData, [@nResult] ) Returns the length of the unpacked string, the function is passed with the Packed string cPackedData. If you pass by the link nResultthere will be written the result of the operation ( 0 - all OK ). If an error occurs, then the 1 returns instead of the length of the unpacked line.
cPackedData := hb_zCompress( cData [@cBuffer | nBufLen], [@nResult], [nLevel] ) Returns the packed string, the string cData, that should be packed, is passed to the function; the buffer for packaging can also be passed by the reference (its size can be calculated beforehand with the help of hb_zCompressBound(), the level of compression nLevel, which must be between 0 and 9 ( 0 - simple copy without compression, 1 - high speed, 9 - the best compression. If you pass by reference nResult, there will be written the result of the operation ( 0 - all OK ). In case of an error, the function returns Nil.
cUnPackedData := hb_zunCompress( cPackedData, [@cBuffer | nBufLen], [@nResult] ) Returns the unpacked string, the string cPackedData, which you should unpack, is passed to the function; the buffer to unpack can also be passed by the reference (its size can be calculated beforehand with the help of hb_zunCompressLen()). If you pass by reference nResultthere is written as the result of the operation ( 0 - all OK ). In case of an error, the function returns Nil.
cError := hb_zError( nError ) Returns a string with a description of the error that occurred when packing or unpacking, nError code which was recorded in nResult with the performing hb_zCompress()b hb_zunCompress() or hb_zunCompressLen().

3.13.11 Idle state

Idle state this is a state of expectation, when the Harbour program is waiting for the input from the user with the keyboard or the mouse. It arises as the result of Inkey() function call ( the READ command uses Inkey(), so that while waiting for input GET-objects idle state occurs ). In this state, there is an automatic collect of the garbage and any background tasks can be carried out. To add/remove these background tasks the following functions are designed (example of usage see in the harbour/tests/testidle.prg):

nHandle := hb_IdleAdd( bAction ) Codeblock bAction is added to the list of background tasks for execition during idle state. The function returns nHandle, which can be used to remove tasks in the list.
bAction := hb_IdleDel( nHandle ) The task nHandle is removed from the list of background tasks, nHandle - value returned by the function hb_IdleAdd() when adding tasks. The function returns codeblock the corresponding th the task , or Nil if such a task is not in the list.
hb_IdleState() As a result of performing this function the program is included in the idle state, performs garbage collection and performs one of background tasks list. As a result of successive calls of the function the background tasks are performed in the queue. This function has sense, if you have a long process without waiting States, and you have to interrupt it to perform background tasks.
hb_IdleSleep( nSeconds ) As a result of performing of this function the program is included in the idle state for nSeconds seconds. In contrast to the Inkey(nSeconds) the standby condition is not interrupted when you enter by the keyboard.

3.13.12 JSON - there and back

cJSON := hb_jsonEncode( xValue [, lHuman] ) Returns the JSON string, obtained by converting xValue, lHuman ( .F. by default ), makes the output string more "readable", if set to .T..
Codeblocks stored as "null", objects - as arrays ( methods are not recorded ).
nLengthDecoded := hb_jsonDecode( cJSON, @xValue ) Demaps the JSON string cJSON into the xValue variable, passed by reference. Returns the number of processed symbols.

3.13.13 Encryption

Blowfish - implementation of the BlowFish algorithm, designed by Bruce Schneier.In this algorithm startup initialization of encryption tables is intentionally designed to be expensive to strongly reduce the efficiency of brute force attacks so call hb_blowfishKey() once for each new password and then reuse initialized encryption key in hb_blowfishEncrypt() and hb_blowfishDecrypt().
Warning: the size of encrypted data is padded to 64bit (8 bytes) so it's bigger then original one.
cBfKey := hb_blowfishKey( cPasswd )Generates a key cBfKey from the passed password cPasswd for use in the encryption/decryption functions.
cCipher := hb_blowfishEncrypt( cBfKey, cText [, lRaw ] )Encrypts cText, using the key cBfKey, prepared with hb_blowfishKey(). The optional lRaw parameter, if set to TRUE (it is FALSE by default), disables ANSI X.923 padding but encode passed string in 8bytes blocks. If last block in string is smaller then it's padded to 8 bytes using chr(0) and information about original string size is not attached to encrypted data. During decoding only strings which are well padded (N*8 bytes) are accepted and lRaw> := .T. disables restoring original string size encoded in encrypted string using ANSI X.923 standard so the size of decrypted string is the same as original one.
cText := hb_blowfishDecrypt( cBfKey, cCipher [, lRaw ] )Decrypts cCipher, using the key cBfKey, prepared with hb_blowfishKey().
cCipher := hb_blowfishEncrypt_CFB( cBfKey, cText [, cInitSeed ] )Encrypts cText, using the key cBfKey, prepared with hb_blowfishKey(), using CFB (cipher feedback) mode instead of ECB (electronic codebook).
cText := hb_blowfishDecrypt_CFB( cBfKey, cCipher [, cInitSeed ] )Decrypts cCipher, using the key cBfKey, prepared with hb_blowfishKey(), using CFB (cipher feedback) mode instead of ECB (electronic codebook).
MD5 - ecryption and hashing
cCipher := hb_MD5Encrypt( cText, cPasswd )
cText := hb_MD5Decrypt( cCipher, cPasswd )
cDigest := hb_MD5( cText )
cDigest := hb_MD5File( cFilename )
SHA1, SHA2 - hashing
cDigest := hb_SHA1( cText [, lRaw ] )
cDigest := hb_HMAC_SHA1( cText, cKey [, lRaw ] )
cDigest := hb_HMAC_SHA224( cText, cKey [, lRaw ] )
cDigest := hb_HMAC_SHA256( cText, cKey [, lRaw ] )
cDigest := hb_HMAC_SHA384( cText, cKey [, lRaw ] )
cDigest := hb_HMAC_SHA512( cText, cKey [, lRaw ] )

3.13.14 Miscellaneous

xResult := hb_ExecFromArray( cFuncName [,aParams] ) Returns the result of the execution of the function specified by the name of cFuncName with parameters passed in the array aParams, for example:
           ? hb_execFromArray( "Str", {11,6,3} )
        
xResult := hb_ExecFromArray( @funcName [,aParams] ) Returns the result of the execution of the function specified by the index @funcName with parameters passed in the array aParams, for example:
           hfunc := @Str()
           ? hb_execFromArray( hfunc, {11,6,3} )
        
xResult := hb_ExecFromArray( bCodeBlock [,aParams] ) Returns the result of execution of the codeblock bCodeBlock with parameters passed in the array aParams:
           ? hb_execFromArray( {|n1,n2,n3|Str(n1,n2,n3)}, { 11,6,3 } )
        
xResult := hb_ExecFromArray( oObject, cMethodName [,aParams] ) Returns the result of the execution of the method cMethodName object oObject with parameters passed in the array aParams, a method is specified by name.
xResult := hb_ExecFromArray( oObject, @msgName [,aParams] ) Returns the result of the execute method of the object oObject with parameters passed by in the array aParams, the method specified with the help of the pointer @msgName
xResult := hb_ExecFromArray( aExecArray )In this version of use hb_ExecFromArray() the parameters are passed as an array:
           ? hb_execFromArray( { "Str", 11,6,3 } )
           ? hb_execFromArray( { hfunc, 11,6,3 } )
           ? hb_execFromArray( { {|n1,n2,n3|Str(n1,n2,n3)},11,6,3 } )
           и т.д.
        
hb_ForNext( nStart, nEnd | bEnd, bCode [, nStep ] ) Implementation of FOR ... NEXT cycle. The bCode codeblock, which receives a counter as a parameter ({|nIndex|...code...}), is evaluated many times while the counter increments from nStart till nEnd (or till the bEnd codeblock returns .T.) with a nStep step.
cDirBase := hb_DirBase() Returns the base directory, the one with the executable file.
cExePath := hb_Progname() Returns the path and name of the executable file.
cSep := hb_ps() Returns the character that separates directories in the path, passed in the OS ( "/", "\")
cSep := hb_osDriveSeparator()  
cDirName := hb_DirTemp() Returns a path to the system temporary directory.
lResult := hb_DirRemoveAll( cDir ) This function removes recursively whole directories with it's body so hb_DirRemoveAll( hb_ps() ) can remove all files and directories from your disk.
n := hb_Rand32() Returns random integer value in a range from 0 to 0xFFFFFFFF.
n := hb_RandomInt( [n1,] [n2] ) Returns random integer value. Optional parameters n1, n2 - the range, and if n2 is not specified, then the return value is in the range between 0 and n1; if both parameters are not specified, then - in the range of 0,1.
n := hb_Random( [n1,] [n2] ) Returns random real value. Optional parameters n1, n2 - the range, and if n2 is not specified, then the return value is in the range between 0 and n1; if both parameters are not specified, then - in the range between 0 and 1.
hb_RandomSeed( n ) If the parameter n is 0, then first call to HB_RANDOM() or HB_RANDOMINT() activates initialization which generates new seed using current time in milliseconds and HVM stack address (for MT modes). If someone needs repeatable results from HB_RANDOM() and HB_RANDOMINT() then he should initialize the seed for each thread with some fixed value i.e. HB_RANDOMSEED( 123456789 )