Alexander Kresin software |
Rus Eng
|
3. Differences from Clipper - a brief description
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.
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;
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.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:
expr<n>
;Another new operator - WITH OBJECT:
WITH OBJECT expression ... ENDWITHIt 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 ENDSWITCHIt looks like this:
SWITCH a CASE 1 ? "FOUND: 1" CASE "2" ? "FOUND: 2" OTHERWISE ? "other" ENDThis 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 := "/cargo/" o:&( upper( msg ) ) += "/value/" ? 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.
// 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.701905By 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: 0dYYYYMMDDAnd 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
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:
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:
METHODThe method should return a reference to the object ( Self ), if it is used as a constructor.( [ ] ) CLASS ... RETURN Self
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.EXTERN <funcName>([<args,...>])
is applied, if the external function
<funcName>() implements what this method needs. obj:<methodName> := <value>
.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;<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 SelfHere appeared such new features:
METHOD <methodName> CLASS <className>
;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.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 * zNote 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 NilThis 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.
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 ) --> cLangID | Sets the language of the program, returns the ID of the previous language. |
Hb_LangName() --> cLangID | Returns the name of the program language. |
Hb_LangErrMsg( nError ) --> cError | Returns the error number. |
Hb_LangMessage( nMessage ) --> cMessage | Returns the text of the alarm messages by number. |
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 ) | Translates sData from a codepage sCodepageIN to utf8 |
hb_Utf8ToStr( sData, sCodepageOUT ) | Translates sData from utf8 to a sCodepageOUT codepage |
hb_Utf8Len( sData ) | The Len() function for utf8 strings |
hb_utf8Chr( n ) | The Chr() function for utf8 strings |
hb_utf8Asc( sData ) | The Asc() function for utf8 strings |
hb_utf8Substr( sData, n1, n2 ) | The Substr() function for utf8 strings |
hb_utf8Left( sData, n1 ) | The Left() function for utf8 strings |
hb_utf8Right( sData, n1 ) | The Right() function for utf8 strings |
hb_utf8Stuff( sData, n1, n2, cNew ) | The Stuff() function for utf8 strings |
hb_utf8Peek( sData, n1 ) | The Peek() function for utf8 strings |
hb_utf8Poke( sData, n1, n ) | The Poke() function for utf8 strings |
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
REQUESTYou 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
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
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 nilAn 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 ( asharr["fantasy"] := "fiction"
in the above example )
leads to the addition of a new pair, while the FALSE leads to an error;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. |
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
| 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
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.14.15 ). 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().
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 4For 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.
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.14.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 Internationalization (hbi18n)
The application internationalization technology is based on the well-known GNU gettext and provides a relatively simple way to translate applications into different languages.
I will try to describe this process step by step. So,
1. We will mark in a special way all string literals that we want to translate. To do this, we insert in each source file that contains such literals, a line of the type
#xtranslate _I( <x,...> ) => hb_i18n_gettext( <x> )
hb_i18n_gettext()
,
and enclose all the necessary string literals in _I()
, for example:
Alert( _I("Dangerous error!") )
Alert()
will still be issue a "Dangerous error!" message.
2. Create so-called .pot files. To do this, we need to compile our source code with the -j key (well, with other keys that we use when compiling), for example:
harbour myfile1.prg myfile2.prg -j -n -ic:\harbour\include
#: /myfile1.prg:230 #, c-format msgid "Dangerous error!" msgstr ""
3. Combine all .pot files of our project. To do this, a special program is used, which is included in
Harbor kit, hbi18n
. We run it with the key -m (merge):
hbi18n -m -omyproject.pot myfile1.pot myfile2.pot
Alert( _I("Dangerous error!"))
in the source code in different places, there will be
only one occurrence of it in myproject.pot.
4. We proceed, in fact, to the translation. For each msgid with text, we insert its translation into msgstr. Now we will have lines of the type in myproject.pot:
#: /myfile1.prg:230 #, c-format msgid "Dangerous error!" msgstr "Жуткая ошибка!"
5. The next step is to compile myproject.pot, for this we again use hbi18n
, now with the -g key:
hbi18n -g myproject.pot
6. To do this, insert the following lines into the program:
cBuff := hb_MemoRead( "myproject.hbl" ) IF hb_i18n_Check( cBuff ) hb_i18n_Set( hb_i18n_RestoreTable(cBuff) ) ENDIF
Well, we created the hbl files, upload them, everything works. But we are changing the program, adding new literals.
We create pot files in a new way, but they are without translation. What, write a translation again? No, of course not! We will be served by the same
hbi18n
, but with the key -a (add). It adds translations of words from the saved old pot file:
hbi18n -a -omyproject.pot myproject.pot myproject_old.pot
In conclusion, I will attach here as an example my working bat file that creates and modifies pot. This the bat file is taken from hbedit:
@set SRC_DIR=\papps\gitapps\hbedit\source c:\harbour\bin\harbour %SRC_DIR%\fdiff.prg %SRC_DIR%\fedit.prg %SRC_DIR%\fmenu.prg %SRC_DIR%\fview.prg %SRC_DIR%\hbcommander.prg -j -n -q -ic:\harbour\include c:\harbour\bin\hbi18n.exe -m -ohbedit_ru_1.pot fdiff.pot fedit.pot fmenu.pot fview.pot hbcommander.pot c:\harbour\bin\hbi18n.exe -a -ohbedit_ru_1.pot hbedit_ru_1.pot hbedit_ru866.pot mv hbedit_ru_1.pot hbedit_ru866.pot @del *.c @del fdiff.pot @del fedit.pot @del fmenu.pot @del fview.pot @del hbcommander.pot
As you can see, first a temporary hbedit_ru_1.pot is created, then translations from the main one hbedit_ru866.pot are added to it, and after that the temporary hbedit_ru_1.pot is renamed to the main hbedit_ru866.pot
In this section we will cover a variety of new features that were not included in previous thematic sections.
3.14.1 Built-in compiler
Three functions using the built-in compiler (hbcplr.lib), based on the same code, that harbour.exe itselfnRetCode := 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() |
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.14.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.14.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.14.4 String functions
cString := hb_strFormat( cFormat, ... ) | Returns a string formatted in the C-style, as in printf(). |
lResult := hb_strIsUtf8( cString ) | Checks is a cString passed is utf8 string. |
cResult := hb_strReplace( cString, cSource | acSource | hReplace [, cDest | acDest ] ) ) |
This function allows to easy replace different substrings in given string. If 2nd is string then each character in cString which exists in cSource at n position is replaced by corresponding character at n position in cDest or string from acDest[n]. If 2nd parameter is array then each cString substring which exists in acSource at n position is replaced by corresponding character at n position in cDest or string from acDest[n]. If 2nd parameter is a hash array, 3rd parameter must be omitted. Please remember that single hash layer is much lighter data type than array of subarrays because it needs only one GC item when each subarray is new GC item. If n is longer then LEN() of cDest or acDest then given character/substring is removed from result. This function should help in code which uses repeated StrTran() calls. Examples:
// encode XML value
|
cResult := hb_strShrink( cString[, nShrinkBy] ) |
Shrinks the cString from the end by nShrinkBy bytes. nShrinkBy has a default value of 1. Function returns empty string on error, returns full string if nShrinkBy is zero or negative. This function call is meant to be use instead of code like this:
cString := Left( cString, Len( cString ) - 1 ) ->
|
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.14.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.14.6 Processes
nResult := hb_ProcessRun( cCommand, [cStdIn], [@cStdOut], [@cStdErr], [lDetach] ) | |
handle := hb_ProcessOpen( cCommand, [cStdIn], [@cStdOut], [@cStdErr], [lDetach] ) | |
nResult := hb_ProcessValue( handle, [lWait] ) | |
lResult := hb_ProcessClose( handle, lGentle ) |
3.14.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.14.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.14.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.14.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.14.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.14.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.14.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.14.14 HiPer-SEEK (Fast Text Search)
Harbour's HiPer-SEEK library is compatible with Six Drivers's HiPer-SEEK system, which, in turn, uses Index Applications' Fast Text Search technology.
The HiPer-SEEK system is a set of functions, which create and maintain index files, enabling the rapid search of textual data. The HiPer-SEEK functions can be used to index and search any character based information the application can access.
HiPer-SEEK indexes contain fixed length keys (index records) which track the contents of text records. A text record may consist of any arbitrary block of text. In the case of a .dbf format file, a text record would usually consist of the concatenation of selected fields from a data record. A text record might also be a line or paragraph from a text file or even an entire file. In all cases, there will be one index record for each and every text record. This one to one relationship is critical to the proper operation of a HiPer-SEEK system.
An index file is created by hs_Create(). hs_Create() builds the index file header and establishes the index's attributes. It does not add records to the file. This is done by hs_Add().
An .hsx index is not an inversion or compression of the original textual data. Its keys track the occurrence of text signatures. It identifies matches as those index records containing all the signatures or attributes of the search string(s). It is possible that differing strings will resolve to the same signature. We refer to these as aliases. That's why you need to verify the result, returned by hs_Next(). It is important to note that while an .hsx index search may mistakenly identify a record as containing a match, it will never fail to find a record that does match.
As mentioned above, the HiPer-SEEK system has been built to provide fast text searches in a wide variety of applications. There are limits however. It will be less useful in two extreme situations. The first is where text records are very small. The smallest .hsx index key is 16 bytes. When individual text records are also that small the index file overhead can be 100% or more. The other extreme is where individual text records are very large. The largest choice for an .HSX index key is 64 bytes. It is easy to imagine that indexing 64 kilobytes of text with a single 64 byte key is not practical.
n := hs_Add( handle, Expr, [lDel] ) |
Adds a text string entry to a HiPer-SEEK index file. handle - an integer handle of a HiPer-SEEK index file.Expr - a code block that will be evaluated for the current record to extract the key data to be added to the index, or a ready string value. lDel - allow to set DELETE flag to a new record. Returns an integer representing the appropriate record number. Errors are indicated by integers less than zero. Return values start with one and increase. This integer is an internally incremented counter and indicated the record position within the index file. The first time hs_Add() is called it will return a 1, the second time, a 2, and so on. These return values represent the position within the index file held by the key for the string passed. These numbers will always increase by one for each add, even if records are deleted in the interim. Example: USE test EXCL h := hs_Open( "SALES.HSX", 10, 1 ) bExpr := { || Trim( test->FIRST ) + " " + Trim( test->LAST ) } DO WHILE !eof() nRec := hs_Add( h, bExpr ) IF nRec < 1 .OR. nRec != RecNo() ? "Error adding record " + LTrim( Str( RecNo() )) + "!" ENDIF SKIP ENDDO |
hs_Close( handle ) |
Closes a previously opened HiPer-SEEK index file. handle - an integer handle of a HiPer-SEEK index file.All open HiPer-SEEK index files must be closed by hs_Close() prior to program termination. |
hs_Create( cFileName, nBufSize, nKeySize, lCase, nFiltSet, xExpr ) |
Creates a new, empty HiPer-SEEK index file. If another file of the same name exists, it will be overwritten. cFileName - a string representing a file path name. It may contain drive and subdirectory information.nBufSize - an integer that will be multiplied by 1024 to determine how much buffer space is to be used by the HiPer-SEEK routines for this index. For example, a value of 10 will allocate 10k or 10240 bytes of buffer space plus a small amount of overhead. nKeySize - an integer from 1 to 3. This value has two effects: 1) The larger the value, the greater the number of bytes written for each record in the index. 2) The larger the value the lower the number of aliases (or false positives) returned by a search. For most cases, a value of 2 is the best choice, offering a low alias rate and small index size. For a value of two, each new index record increases the size of the index by 32 bytes. For values of 1 and 3 it increases by 16 and 64 bytes respectively. lCase - can be set to either .T. or .F., depending if case is to be significant during matches. If .T., then case is not significant and lower case will match both lower and upper case. If the value is .F., then upper case characters will not match lower case. nFiltSet - a number which indicates the filter set to be used for indexing and searching. A filter set is a translation of characters from the source to the index. HiPer-SEEK indexes have two sets: 1) Filter set 1, the recommended choice, will mask off the highorder bit and treat all non-printing characters as spaces. 2) Filter set 2 does not eliminate any characters. For most text purposes, filter set 1 is recommended since it eliminates carriage returns, line-feeds, etc., from becoming part of the index. Filter set 2 is useful for those character sets which use the high order bit. xExpr - a key expression, or a codeblock, which is remembered and later can be used by HS_ADD, HS_REPLACE, HS_FILTER and HS_VERIFY when this functions are called without key expression. When the key expression is set as string then it is also stored in HSX header and later is automatically retrieve by HS_OPEN. Returns: if successful, a non-negative integer, representing the HiPer-SEEK index file handle. Otherwise, returns a negative integer indicating an error. lCase is set during the index creation process and cannot be changed later. Appreciable time savings in execution speed are realized when case is not significant (i.e. lCase is .T.). Unlike lCase, nBufSize can be different each time the file is opened. nBufSize only specifies the amount of memory buffer used, and is not otherwise an indexing factor. This is why nBufSize is an argument for both hs_Create() and hs_Open() while lCase is not. Generally, the larger this buffer is the faster an index file will be created. The buffer size is less important to performance in systems using fast fixed disks and disk caching. The index file can be opened later with a smaller buffer. The memory used for these buffers is released when the index file is closed. The value of nKeySize relates to the speed of index creation and retrieval. For a value of 2, each record occupies 32 bytes in memory. If the total number of records indexed * 32 is less than the value of nBufSize * 1024, then the entire index can be buffered in memory, greatly increasing the speed of adds and finds. Otherwise disk accesses will be needed for each find and add. hs_Create() creates an index file with no records in it. You must add records using hs_Add(). hs_Create() leaves this index file open so don't try to open it again without closing it first. Example: h := hs_Create( "myindex.hsx", 8, 3, .T., 2 ) IF h < 0 ? "Error creating HiPer-SEEK index!" ENDIF |
hs_Delete( handle, nVal ) |
Deletes specifed index record from HiPer-SEEK index file. handle - an integer handle of a HiPer-SEEK index file.nVal - an integer representing the record number to be deleted. Returns 1 if successful or a negative integer indicating an error. hs_Delete() is used to set a delete flag for an individual index record. The actual index record is not physically deleted from the index, rather it is only marked as deleted. Marking an index record as deleted prevents it from being returned by hs_Next(). Example: USE sales EXCL h := hs_Open( "sales.hsx", 10, 1 ) DO WHILE !eof() IF deleted() nVal := hs_Delete( h, RecNo() ) IF nVal < 1 ? "HiPer-SEEK delete error: " + LTrim( Str( RecNo() )) ENDIF ENDIF SKIP ENDDO |
hs_Filter( cIndex, cVal, [cExpr], [nBufSize], [nOpenMode] ) |
Sets a filter using a HiPer-SEEK index. It can be used only in conjunction with a .dbf file. This function needs RDD with record map (RM) functionality cIndex - the Hiper-SEEK index file name to use for the filter or a handle to already open HSX index. If index extension is not specified, defaults to .hsxcVal - a space-delimited string of one or more tokens to look for. cExpr - an optional expression to validate "hits" with. If no parameter (or an empty string) is passed, "fuzzy" matches are accepted. nBufSize - an optional buffer size in kbytes. Default is 8. nOpenMode - an optional Open Mode. Default is 2. Valid values are: 0 = READ-WRITE + SHARED 1 = READ-WRITE + EXCLUSIVE 2 = READ-ONLY + SHARED 3 = READ-ONLY + EXCLUSIVE Returns the number of records matching the filter condition. Example: #include "inkey.ch" LOCAL cExpr := "test->FIRST + test->LAST + test->STREET + test->CITY" LOCAL nCount := 0, lStrict := .F. LOCAL cSearch := "Steve John Robert" LOCAL GetList := {} CLS USE test EXCL IF !file("TEST.HSX") @ 0,0 "Building HiPer-SEEK Index..." hs_Index( "TEST.HSX", cExpr, 2 ) ?? "Done!" Inkey(1) CLS ENDIF WHILE .T. cSearch := PadR( cSearch, 59 ) @ 0,0 SAY "Search Values.....:" GET cSearch @ 1,0 SAY "Strict Match (Y/N):" GET lStrict PICTURE "Y" READ IF LastKey() == K_ESC CLS EXIT ENDIF cSearch := AllTrim( cSearch ) @ 3,0 SAY "Setting HiPer-SEEK Filter ..." nCount := hs_Filter( "TEST.HSX", cSearch, iif( lStrict, cExpr, "" )) ?? "Done!" @ 4,0 SAY LTrim( Str( nCount )) + " records meeting filter condition." @ 6,0 SAY "Press any key to browse the matching records..." Inkey(0) GO TOP Browse() CLS ENDDO |
hs_IfDel( handle, nVal ) |
Determines if a HiPer-SEEK record has been marked as deleted. handle - an integer handle of a HiPer-SEEK index file.nVal - an integer representing the record number to be tested. Returns 1 ( TRUE ) if the specified record is marked as deleted. If not, returns 0 ( FALSE ). A negative return value indicates an error. |
hs_Index( cFileName, cExpr, [nKeySize], [nOpenMode], [nBufSize], [lCase], [nFiltSet] ) |
Creates and populates a new HiPer-SEEK index. It can be used only in conjunction with a .dbf file. cFileName - a string representing a file path name. It may contain drive and subdirectory information.nKeySize - an integer from 1 to 3. This value has two effects: 1) The larger the value, the greater the number of bytes written for each record in the index. 2) The larger the value the lower the number of aliases (or false positives) returned by a search. For most cases, a value of 2 is the best choice, offering a low alias rate and small index size. For a value of two, each new index record increases the size of the index by 32 bytes. For values of 1 and 3 it increases by 16 and 64 bytes respectively. nOpenMode - the optional mode in which the HiPer-SEEK index file is opened. Note that this pertains to the mode the index will be left in after creation. During creation, the index is always built in READ-WRITE + EXCLUSIVE mode. The default value is 1. Valid values are: 0 = READ-WRITE + SHARED 1 = READ-WRITE + EXCLUSIVE 2 = READ-ONLY + SHARED 3 = READ-ONLY + EXCLUSIVE nBufSize - an integer that will be multiplied by 1024 to determine how much buffer space is to be used by the HiPer-SEEK routines for this index. For example, a value of 10 will allocate 10k or 10240 bytes of buffer space plus a small amount of overhead. lCase - an optional logical parameter, can be set to either .T. or .F., depending if case is to be significant during matches. If .T., then case is not significant and lower case will match both lower and upper case. If the value is .F., then upper case characters will not match lower case. The default value is .T. (not case sensitive). nFiltSet - a number which indicates the filter set to be used for indexing and searching. A filter set is a translation of characters from the source to the index. HiPer-SEEK indexes have two sets: 1) Filter set 1, the recommended choice, will mask off the highorder bit and treat all non-printing characters as spaces. 2) Filter set 2 does not eliminate any characters. For most text purposes, filter set 1 is recommended since it eliminates carriage returns, line-feeds, etc., from becoming part of the index. Filter set 2 is useful for those character sets which use the high order bit. Returns 1 if successful or a negative integer indicating an error. Example: LOCAL cExpr := "test->FIRST + test->LAST + test->STREET + test->CITY" LOCAL h := 0 USE test EXCL // Build HiPer-SEEK index using 64 byte index record, READ-WRITE + SHARED // mode, using a 16k buffer. h := hs_Index( "TEST.HSX", cExpr, 3, 0, 16 ) hs_Close( h ) |
hs_KeyCount( handle ) |
Returns the number of entries in a HiPer-SEEK index. handle - an integer handle of a HiPer-SEEK index file.Returns an integer greater or equal to zero, representing the number of records currently in the index. Deleted records are included in this total. A negative integer represents an error. |
hs_Next( handle ) |
Assuming that a hs_Set() call has occurred immediately preceding the first call to hs_Next() for a given search, this function performs the actual 'find' operation using the index. The first call returns the record number of the first hit, the second of the second hit, etc. When all possible hits have been returned, the function returns zero. Example: // Lists all record numbers that contain the word "John" anywhere in // either the FIRST, LAST, STREET, or CITY fields. Uses hs_Verify() // to prevent "fuzzy" matches. LOCAL cExpr := "test->FIRST + test->LAST + test->STREET + test->CITY" LOCAL bExpr := &( "{||" + cExpr + "}" ) LOCAL cVal := "John", h := 0, nRec := 0 CLS USE test EXCL IF !File("TEST.HSX") ? "Building HiPer-SEEK Index..." h := hs_Index( "TEST.HSX", cExpr, 2 ) ELSE h := hs_Open( "TEST.HSX", 8, 1 ) ENDIF hs_Set( h, cVal ) nRec := hs_Next( h ) DO WHILE nRec > 0 dbGoto( nRec ) IF hs_Verify( bExpr, cVal ) ? nRec ENDIF nRec := hs_Next( h ) ENDDO hs_Close( h ) |
hs_Open( cFileName, nBufSize, nOpenMode ) |
This function is used to open an existing HiPer-SEEK index file. cFileName - a string representing a file path name.nBufSize - an integer that will be multiplied by 1024 to determine how much buffer space is to be used by the HiPer-SEEK routines for this index. For example, a value of 10 will allocate 10k or 10240 bytes of buffer space plus a small amount of overhead. Performance will improve greatly if the entire index file will fit into this buffer. If not, some experimentation will be helpful in determining the best size. nOpenMode - an optional Open Mode. A HiPer-SEEK index can be opened read-write or read-only and EXCLUSIVE or SHARED. The EXCLUSIVE and SHARED modes follow Clipper's use of the terms. When an index is opened read-write, the application can read existing index records and write new ones to the file. When the file is opened read-only, the application can only read records from the index. The values to use are: 0 READ-WRITE + SHARED 1 READ-WRITE + EXCLUSIVE 2 READ-ONLY + SHARED 3 READ-ONLY + EXCLUSIVE Returns an integer HiPer-SEEK index hendle (0 through 63) if successful, else returns a negative integer indicating an error. Example: USE test EXCL h := hs_Open( "LOOKUP.HSX", 16, 1 ) IF h < 0 ? "Error opening HiPer-SEEK index!" ENDIF |
hs_Replace( handle, Expr, nVal, lDel ) |
Replaces the current index entry for nVal with the new value of Expr. There must be at least nVal records already indexed. handle - an integer handle of a HiPer-SEEK index file.Expr - a code block that will be evaluated for the current record to extract the key data to be added to the index, or a ready string value. nVal - an integer representing the record number to be replaced. lDel - allow to set DELETE flag to a record. Returns 1 if successful or a negative integer indicating an error. Example: USE test EXCL h := hs_Open( "NAMES.HSX", 8, 1 ) GOTO 10 cStr := Trim( test->FIRST ) + " " + Trim( test->LAST ) IF hs_Replace( h, cStr, RecNo() ) < 1 ? "HiPer-SEEK replace error on record #" + LTrim( Str( RecNo() )) ENDIF |
hs_Set( handle, cExpr ) |
Sets up parameters for a subsequent hs_Next() call. handle - an integer handle of a HiPer-SEEK index file.cExpr - a string containing the search criteria. Each substring to be searched for should be separated by blanks (it is not necessary to pad the front and back of the string with blanks). For example, the string "ed cat" would match all records in the index containing both "ed" and "cat" within them. Since it is a sub-string match, both "ed has a cat" and "The catalog was spray painted" would be valid matches. The order of the substrings in the search criteria is not important. For a successful match, all substrings in the search criteria must be present in the original indexed string. Returns 1 if successful or a negative integer indicating an error. Example: h := hs_Open( "LOOKUP.HSX", 4, 1 ) hs_Set( h, "Thompson" ) nRec := hs_Next( h ) ? "The first record conaining 'Thompson' is #" + LTrim( Str( nRec )) hs_Close( h ) |
hs_UnDelete( handle, nVal ) |
Unmarks the specified HiPer-SEEK record as being deleted. handle - an integer handle of a HiPer-SEEK index file.nVal - an integer representing the record number to be undeleted. Returns 1 if successful or a negative integer indicating an error. |
hs_Verify( bSource, cValue ) |
Verify a target string against the .DBF source string, passed as a code block. It can be used only in conjunction with a .dbf file. bSource - a code block containing the expression to evaluate.cValue - the value to compare against the evaluated bSource. Returns logical .T. if cValue is contained anywhere within Eval(bSource), else .F. Example: // Lists all record numbers that contain the word "John" anywhere in // either the FIRST, LAST, STREET, or CITY fields. Uses hs_Verify() // to prevent "fuzzy" matches. LOCAL cExpr := "test->FIRST + test->LAST + test->STREET + test->CITY" LOCAL bExpr := &( "{||" + cExpr + "}" ) LOCAL cVal := "John", h := 0, nRec := 0 CLS USE test EXCL IF !file("TEST.HSX") ? "Building HiPer-SEEK Index..." h := hs_Index( "TEST.HSX", cExpr, 2 ) ELSE h := hs_Open( "TEST.HSX", 8, 1 ) ENDIF hs_Set( h, cVal ) nRec := hs_Next( h ) DO WHILE nRec > 0 dbGoto( nRec ) IF hs_Verify( bExpr, cVal ) ? nRec ENDIF nRec := hs_Next( h ) ENDDO hs_Close( h ) |
hs_Version() |
Returns a version string. |
3.14.15 Miscellaneous
xResult := hb_ExecFromArray() | There are several different versions of this function.
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 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.
|
cSep := hb_ps() | Returns the character that separates directories in the path, passed in the OS ( "/", "\") |
cSep := hb_osDriveSeparator() | |
cExePath := hb_Progname() | Returns the path and name of the executable file. |
aDir := hb_Directory() | The same as Directory(), but returns DateTime value instead of Date in a third subarray item. |
cDirBase := hb_DirBase() | Returns the base directory, the one with the executable file. |
cDirName := hb_DirTemp() | Returns a path to the system temporary directory. |
lResult := hb_DirExists( cDir ) | Returns a logical value, .T. - if a given directory exisis, .F. - if no. |
nResult := hb_DirCreate( cDir ) | Creates a given directory cDir. If succeeds, returns 0. |
lResult := hb_DirBuild( cDir ) | Creates a given directory cDir and all parent directories, if they does not exist. If succeeds, returns .T., if no - .F.. |
nResult := hb_DirDelete( cDir ) | Removes a given directory cDir. If succeeds, returns 0. |
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 ) |