Alexander Kresin
software |
Rus Eng
|
var
( 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 ) exprexpr
;@()
, or dynamically during execution with the help of the macro:&("@()")
. Valtype() returns S for such pointers. ]
[ METHOD ( [] ) BLOCK ]
[ METHOD ( [] ) EXTERN ([]) ]
[ METHOD ( [] ) SETGET ]
[ METHOD ( [] ) VIRTUAL ]
[ METHOD ( [] ) OPERATOR ]
[ ERROR HANDLER ( [] ) ]
[ ON ERROR ( [] ) ]
...
[PROTECTED:]
...
[VISIBLE:]
[EXPORTED:]
...
[FRIEND CLASS ]
[FRIEND FUNCTION ]
[SYNC METHOD ]
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 clauseFROM or INHERIT. This means that it receives from the parent class all its variables and methods (except those marked asHIDDEN). Any of the inherited methods can be overridden. The parent method can be invoked asSuper:()
or :::()
(if it is notHIDDEN).
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 asobj:
, the other - obj::
.
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 hisfriends, 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 clausesFRIEND CLASS and FRIEND FUNCTION - they determine friendly for this class classes and functions.
The words FUNCTION
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 variableAS
(Character, Numeric, Date, Logical, Codeblock, Nil), can be set its initial default valueINIT
and defined Scope (Hidden,Protected,Exported,Readonly).Scope can be set for each variable separately ( in the DATA clause ), and for the group of variables and methods:
- HIDDEN - variable or method are only available within the class, where they are identified and are not inherited;
- PROTECTED - are available only to the class, where they are determined for the heirs;
- EXPORTED ( or VISIBLE ) - are available on every side;
- READONLY ( or RO ) - if the variable is defined as EXPORTED, then modification of its value can only be done from his class and his heirs, if asPROTECTED, then only from his class.
Variables can belong to the object and to the whole class - the last are declared asCLASSDATA, CLASSVAR or CLASS VAR. Such a variable is available from any object of a class as aobj:
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 ofCLASSDATA on the one hand, and CLASSVAR or CLASS VAR on the other. These variables can be declared asSHARED. 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 sameSHARED 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 theCLASSVAR and CLASS VAR it works, as it was described above, andCLASSDATA 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 notINLINE, not BLOCK and not VIRTUAL, after the definition of the class (afterENDCLASS) you must put the implementation of the method:
METHOD ( [] ) CLASS
...
RETURN Self
The method should return a reference to the object ( Self ), if it is used as a constructor.
The following are different ways of declaring a method:
- INLINE allows you to put the implementation of the method in the same line, where it is declared. This method can be used when the code is fairly small, it is making an announcement of the method more simple and demonstrative.
- BLOCK similar to the INLINEbut he expects the record representes method in the form of codeblock and allows you to send arbitrary number of parameters.
- EXTERN ([])
is applied, if the external function () implements what this method needs.
- SETGET is used for the data to be calculated, it allows you to use the name of the method as the name of the variable to be read / write data. For example, if your class is a variable, value of which is not desirable to install directly, since it should have a certain format or should be only in a certain range. You define this variable asHIDDEN to exclude its direct change, and define SETGET the method that will be used to set the value of a variable, it checks the passed value, formats it as it should be and so on. Now, to set the value of a variable, you write:obj: :=
.
- VIRTUAL this method does nothing. It can be used for basic, parental classes - in this case, the concrete realization of the method will be made in the heir classes.
- OPERATOR
is used to overload the operator ( "+", "-", ... ) - i.e. when the action on the object in the program is taking place, this action is implemented by the method, which is indicated with the wordOPERATOR. As a parameter to the method the second object participating in the operation is passed.
Another way of declaring a method is to set it with the help of ERROR HANDLER orON 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 asSYNCthen 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 theCLASS ... ENDCLASS and do not describe the method New(n) separately. Here we should pay attention to the following moments:
- you must include the hbclass.ch in the prg, containing the class declaration;
- object variable inside a method of this object is preceded by a double colon::x;
- the object inside the method is called Self;
- the method used to create a new object must return a reference to this object:Self;
- how to create a new object: ():()
( obj := myFirstClass():New(3)
).
Now one more complicated example:
#include "hbclass.ch"
FUNCTION Main
Local obj1, obj2
obj1 := mySecondClass():New( 10,"Alex" )
? "Всего объектов mySecondClass:", mySecondClass():nKolObj
obj2 := mySecondClass():New( 11,"Mike" )
? "Всего объектов mySecondClass:", mySecondClass():nKolObj
? obj1:x, obj2:x
?
RETURN Nil
CLASS myFirstClass
VAR x
METHOD New( n ) INLINE ( ::x := n, Self )
ENDCLASS
CLASS mySecondClass FROM myFirstClass
HIDDEN:
VAR cStringS
EXPORTED:
CLASS VAR nKolObj INIT 0
VAR cString1 INIT "Sample"
METHOD New( n, s )
ENDCLASS
METHOD New( n, s ) CLASS mySecondClass
Super:New( n )
::nKolObj ++
::cStringS := s
RETURN Self
Here appeared such new features:
- we have two classes, myFirstClass and mySecondClass, and themySecondClass is the heir of myFirstClass;
- two variables, nKolObj and cString1, in the declaration of a class the initial values are assigned, they will stay for any new object of the class;
- the method New of the class mySecondClass is described separately with the help ofMETHOD CLASS
;
- in this method Super:New(n)
is used to call an identically named method of the parent class to initialize the object's variables inherited from a parent.
- mySecondClass variable cStringS has the attribute (scope)HIDDEN (i.e., it is only available from methods of the class), and the rest of the variables and the method ofNew marked as EXPORTED (i.e., accessible from everywhere);
- in the mySecondClass the CLASS VAR nKolObj
appeared - variable, which belongs not to the object, but to the whole class, here it is used as a counter of objects of this class;
Another example showing the method declarations as BLOCK and EXTERN:
#include "hbclass.ch"
function Main()
Local o1 := HSamp1():New()
Local o2 := HSamp2():New()
? o1:x, o2:x // 10 20
? o1:Calc( 2,3 ), o2:Mul( 2,3 ) // 60 120
? o1:Sum( 5 ) // 15
?
return nil
CLASS HSamp1
DATA x INIT 10
METHOD New() INLINE Self
METHOD Sum( y ) BLOCK {|Self,y|::x += y}
METHOD Calc( y,z ) EXTERN fr1( y,z )
ENDCLASS
CLASS HSamp2
DATA x INIT 20
METHOD New() INLINE Self
METHOD Mul( y,z ) EXTERN fr1( y,z )
ENDCLASS
Function fr1( y, z )
Local o := QSelf()
Return o:x * y * z
Note the function call QSelf(), it returns a reference to the object, in the context of which it is located, i.e., the object which is calledSelf in the methods of the class.
And here is an example of use of ERROR HANDLER:
#include "hbclass.ch"
FUNCTION Main
LOCAL oTable
oTable := Table():Create( "sample.dbf", { {"F1","C",10,0}, {"F2","N",8,0} } )
oTable:AppendRec()
oTable:F1 := "FirstRec"
oTable:F2 := 1
oTable:AppendRec()
? oTable:nRec // 2
oTable:nRec := 1
? oTable:nRec // 1
? oTable:F1, oTable:F2 // "FirstRec" 1
?
RETURN nil
CLASS Table
METHOD Create( cName, aStru )
METHOD AppendRec() INLINE dbAppend()
METHOD nRec( n ) SETGET
ERROR HANDLER OnError( xParam )
ENDCLASS
METHOD Create( cName, aStru ) CLASS Table
dbCreate( cName, aStru )
USE (cName) NEW EXCLUSIVE
RETURN Self
METHOD nRec( n ) CLASS Table
IF n != Nil
dbGoTo( n )
ENDIF
RETURN Recno()
METHOD OnError( xParam ) CLASS Table
Local cMsg := __GetMessage(), cFieldName, nPos
Local xValue
IF Left( cMsg, 1 ) == '_'
cFieldName := Substr( cMsg,2 )
ELSE
cFieldName := cMsg
ENDIF
IF ( nPos := FieldPos( cFieldName ) ) == 0
Alert( cFieldName + " wrong field name!" )
ELSEIF cFieldName == cMsg
RETURN FieldGet( nPos )
ELSE
FieldPut( nPos, xParam )
ENDIF
Return Nil
This shows extremely simplified class Table, every object of oTable should correspond to the newly created or opened ( methodOpen is omitted here ) table (dbf file). We want to refer to the fields of the table as to the properties (variables) of an objectoTable. 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. HereERROR 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 asERROR HANDLER ( or ON ERROR ). From this method you can get the same non-existent name of the called variable with the help of the function__GetMessage(). Moreover, if there was an attempt to write something in this variable, then the name is preceded by the symbol "_" and the method is passed by the recorded value as the first parameter. All the rest I think is clear from the example.
In the code above we saw also an example of the use of SETGET method. Note that a call toSETGET method nRec occurs as a variable of the object. If we try to set it ( oTable:nRec := 1
), the method gets the set value as a parameter and it produces the movement in the database, but if we read ( ? oTable:nRec
), it simply returns the result Recno().
A list of functions for the manipulation of classes and objects follows:
lExist := __objHasData( oObject, cName )
Returns a Boolean value, specifies whether the object oObject has a variable namecName
lExist := __objHasMethod( oObject, cName )
Returns a Boolean value, specifies whether the object oObject has the method with the namecName
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 inhboo.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 section3.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 inoObject; с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 inoObject; с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 inoObject; с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 inoObject
oObject := __objDelInline( oObject, сMethodName )
Delete method, or inline method with the name of сMethodName the class to which the object belongs inoObject
oObject := __objDelData( oObject, сDataName )
Removes a variable with the name of сDataName the class to which the object belongs inoObject
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 inoObject
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 variablecName 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.
3.5 Codepage API
This subsystem provides support for national code pages for your data. To link it to your application you must specify the library hbcpage.lib in a link - script and include in a chief prg file the proposal REQUEST with the names of the code pages, which you intend to use, for example:
REQUEST HB_CODEPAGE_RU866
REQUEST HB_CODEPAGE_RUKOI8
REQUEST HB_CODEPAGE_RU1251
Further, you may determine the main code page of the application. This code page will be used for the IsUpper(), IsLower(), IsAlpha(), Upper() and Lower(), Transform (), and when comparing strings. In the console applications, one should use the OEM codepage ( "RU866" for Russian ) and in the Windows GUI application - ANSI ( "RU1251" for Russian ).
To set the code page the hb_cdpSelect() function is used
hb_cdpSelect( sCodepage )
// for example:
hb_cdpSelect( "RU866" )
Such a possibility can be found by Clipper as well ( by connecting the special obj ), but in the Harbour, in addition to that you can define the code page for any opened file data, all the strings will be automatically translated into the main code page of the application for reading and return - when recording, the strings in dbSeek(), dbLocate(), dbSetFilter() will be translated, too. It is enough to specify the code page when opening a file, and all subsequent work with it will look as if the data is in the main code page:
USE file_name ... CODEPAGE "RU1251"
// or
dbUseArea( .T.,,file_name,,.T.,.F.,"RU1251" )
By default the file opens with the main code page of the application.
This possibility ( the definition of the code page of the file ) should only be used with family RDD - DBFNDX and DBFCDX.
ADS RDD provides for these purposes other tools ( setting up the ini file or SET CHARTYPE TO OEM/ANSI ).
Codepage API also includes a function for translation of strings from one codepage to another:
Hb_Translate( sData, sCodepageIN, sCodepageOUT )
// for example:
Hb_Translate( "Привет", "RU1251", "RU866" )
and a set of functions for utf8:
hb_StrToUtf8( sData, sCodepageIN )
Translation sData of sCodepageIN in utf8
hb_Utf8ToStr( sData, sCodepageOUT )
Translation sData from utf8 to sCodepageOUT
hb_Utf8Len( sData )
This and the following functions are analogs of
hb_utf8Chr( n )
the standard string functions Len(),
hb_utf8Asc( sData )
Chr(), Asc (), etc., but for rows in a
hb_utf8Substr( sData, n1, n2 )
utf8 encoding. As it is known, in utf8
hb_utf8Left( sData, n1 )
one character may be encoded with more
hb_utf8Right( sData, n1 )
than one byte. These functions operate with
hb_utf8Stuff( sData, n1, n2, cNew )
the number of symbols rather than bytes.
hb_utf8Peek( sData, n1 )
hb_utf8Poke( sData, n1, n )
3.6 Working with hrb - files
I have already mentioned herethat instead of the C file Harbour can create a special type of file from your prg with the .hrb extension, which contains the p-code of your program and can be executed by the utilityHbrun.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 specifieshandle, 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 usinghandle := hb_HrbLoad( hrbName )
. Execution of this function can be done with the help of theDo( hFun, ... (parameters))
where hFun is an index received from hb_hrbGetFunsym().
aFuncs := hb_hrbGetFunList( handle, nType )
Returns functions list of hrb-module, nType defines the functions type (constants are defined in hbhrb.ch): HB_HRB_FUNC_PUBLIC 0x1 /* locally defined public functions */
HB_HRB_FUNC_STATIC 0x2 /* locally defined static functions */
HB_HRB_FUNC_LOCAL 0x3 /* locally defined functions */
HB_HRB_FUNC_EXTERN 0x4 /* external functions used in HRB module */
It is needed to ensure that the application has been built with all of the functions which can be called from .hrb, otherwise the program might fall out while runtime. The best way is to put in your program
REQUEST
You can #include "hbextern.ch" - it contains the REQUEST's to all Harbour functions. The size of the program will, of course, increase at the end, but you will have a guarantee that all functions are enabled.
Note that the hrbname parameter of the hb_hrbRun() and hb_hrbload() may contain not only the name of thehrb 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 functionshere):
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 thep-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 thehrb 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 thep-code dll, you must first declare these functions in your application asDYNAMIC:
DYNAMIC HRBFUNC1
FUNCTION Main()
Local x, handle := hb_hrbLoad( "my.hrb" )
x := hrbFunc1() // hrbFunc1 - the function from my.hrb
hb_hrbUnload( handle )
Return Nil
3.7 the Built-in C
In the Harbour there is a possibility to insert fragments of C code in the prg file. This may be conveniently, if you don't want to create a .c file for a pair ofC functions and then putting it in the project. This can be done with the help of directives#pragma BEGINDUMP ... #pragma ENDDUMP
.
#pragma BEGINDUMP
#include < extend.h >
#include < math.h >
HB_FUNC( SIN )
{
double x = hb_parnd(1);
hb_retnd( sin( x ) );
}
#pragma ENDDUMP
3.8 Hash arrays
Hash arrays or Hash tables - see. article in Wikipedia - is one of the implementations of data structures, known under the name ofassociative arrays, which stores the pair ( key,value ) and allows you to perform at least three operations: add a new pair, search according to the key and delete the key. The support of associative arrays is in many interpreted high-level programming languages, for example, in Perl, PHP, Python, Ruby, and others. Now it is also implemented in the Harbour.
A new hash-array is created by the function hb_hash(), it can be used without a parameter ( in this case initialized with the empty hash array ) and with parameters, defining an arbitrary number of pairs in the array.
FUNCTION Main
local harr := hb_Hash( "six", 6, "eight", 8, "eleven", 11 )
harr[10] := "str1"
harr[23] := "str2"
harr["fantasy"] := "fiction"
? harr[10], harr[23] // str1 str2
? harr["eight"], harr["eleven"], harr["fantasy"] // 8 11 fiction
? len(harr) // 6
?
RETURN nil
An alternative method for initializing the hash array:
local harr := hb_Hash( "six" => 6, "eight" => 8, "eleven" => 11 )
For hash array 4 flags are defined:
- Autoadd - when set in TRUE (the default), the operation assignment ( asharr["fantasy"] := "fiction"
in the above example ) leads to the addition of a new pair, while the FALSE leads to an error;
- Binary - when set in TRUE (the default), sorting the array is made in accordance with the weight of the binary code of the character, without taking into account the national code pages and the relevant rules of the sort. This allows you to significantly speed up the manipulation with the hash of arrays;
- CaseMatch - defines wether to take into consideration the Case character ( big/small ) for sorting or not. -KeepOrder - when set in TRUE, the order of the pairs in the array corresponds to the priority of their add.
Below is a table of functions for a hash of arrays.
aHash := hb_hash( [ Key1, Value1 ], ..., [ KeyN, ValueN ] )
Create, initialize the hash array
lExists := hb_hHaskey( aHash, Key )
Returns a Boolean value, indicates whether there is a pair with the key Key in the arrayaHash
xValue := hb_hGet( aHash, Key )
Returns the value of the pair with the key Key in the array aHash - the same asxValue := aHash[Key]
xValue := hb_hGetDef( aHash, Key, DefaultVal )
Returns the value of the pair with the key Key in the array aHash orDefaultValif 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 asaHash[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 newNewValue, 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 indexnPosition
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 thenStart - 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. IfbBlock returns the truth, the pair is copied. nPosition - the index of the pair, which will be added to theaHash.
aHash := hb_hEval( aHash, bBlock, [nStart], [nCount] )
Performs codeblock for each pair of array aHash, codeblock gets the key, value and index.
nPosition := hb_hScan( aHash, xValue, [nStart], [nCount], [lExact] )
Looks for value xValue in the array aHash
aHash2 := hb_hSort( aHash )
Sorts an array aHash
lPrevFlag := hb_hCaseMatch( aHash, [lFlag] )
Sets a flag "match case" for an array of aHash and returns the previous value.
aHash := hb_hSetCaseMatch( aHash, [lFlag] )
Sets a flag "match case" for an array of aHash
lPrevFlag := hb_hBinary( aHash, [lFlag] )
Sets a flag "binary" for an array of aHash and returns the previous value.
aHash := hb_hSetBinary( aHash, [lFlag] )
Sets a flag "binary" for an array of aHash
nPrevFlag := hb_hAutoAdd( aHash, [lFlag] )
Sets a flag "auto add" to the array aHash and returns the previous value.
aHash := hb_hSetAutoAdd( aHash, [lFlag] )
Sets a flag "auto add" to the array aHash
nPrevFlag := hb_hKeepOrder( aHash, [lFlag] )
Sets a flag "keep order" for an array of aHash and returns the previous value.
aHash := hb_hSetOrder( aHash, [lFlag] )
Sets a flag "keep order" for an array of aHash
hb_hAllocate( aHash, nItems )
Reserves space for an array of aHash in the number of nItems pairs.
xPrevDef := hb_hDefault( aHash, DefaultValue )
Sets the value of the default for the array aHash and returns the previous one.
3.9 Regular expressions
Many people think ( and I thought ), that regular expression are the strings for searching, where the "*" indicates a group of any characters, and "?" - any single character, the same as that are used as masks for files. In fact it is a kind of language, enough complex - the searching strings on it can shock at first. But if mastering and understanding the logic of the language gradually you get used to its view and it ceases to be a gobbledygook.
Regular expressions are a very powerful tool for searching in the text. Many modern programming languages ( Perl, Php, Python, Ruby, Javascript, etc. ) have the built-in support of them. You can get acquainted with the regular expressions for exampleon pcre.ru.
Further you will find a group of functions, similar in parameters structure. HerecRegEx - 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 parameterlNewLine - 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 linecString fragments of the corresponding regular expression, specified cRegEx. The regular expression can consist of parts that are separated by round brackets. In this case, we call the fragment of the line corresponding to the entire expression the full coincidence, and a substring of this fragment, corresponding to the relevant part of the regular expression (dedicated round brackets) - the sub-coincidence. So, for example:
s := "Claabrok abbey" // a String, where we will look
cRegex : = (a+)(b+)" // the Symbol '+' in the regular expression means that
// the previous symbol occur 1 or more times
// Have 2 full matches:
// 1) 'REC', the 'aa' is the first sub-coincidence, 'b' - the second
// 2) 'abb'here 'a' is the first sub-coincidence, 'bb' - the second
aResult := hb_Regex( cRegex, cString, [lCase], [lNewLine] )
Returns a one-dimensional array, including the first full matches and all of its sub-matches (only the substring).
lResult := hb_RegexLike( cRegEx, cString, [lCase], [lNewLine] )
Returns TRUE if the string cString corresponds to the expression of cRegEx.
lResult := hb_RegexHas( cRegEx, cString, [lCase], [lNewLine] )
Returns TRUE if the line cString found at least one match expressioncRegEx.
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 thecRegEx 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, ifnGetMatch is not equal to 0 and lOnlyMatch is the truth, then a one-dimensional array returns from substrings ( of full coincidence or sub-matches ). IfnGetMatch 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 samenGetMatch 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 thenStartif it is past with the link, the position of the found substring is recorded, in thenEnd - its length.
And, finally, an example of the use of the simplest regular expression, already discussed above:
FUNCTION Main()
Local s := "ClaAbrok aBbey", n1, n2
? hb_isregex( hb_regexcomp( "a+b+" ) ) // .T.
n1 := 1
n2 := Len( s )
? hb_atx( "a+b+", s, .f., @n1, @n2 ), n1, n2 // aAb 3 3
? hb_regexhas( "a+b+", s, .f. ) // .T.
? hb_regexhas( "a+b+", s ) // .F.
? hb_regexlike( "a+b+", s, .f. ) // .F.
PrintArray( hb_regex( "a+b+",s,.f. ) ) // { aAb }
PrintArray( hb_regex( "(a+)(b+)",s,.f. ) ) // { aAb aA b }
PrintArray( hb_regexSplit( "a+b+",s,.f. ) ) // { Cl rok ey }
PrintArray( hb_regexAtx( "a+b+",s,.f. ) ) // { { aAb 3 5 } }
PrintArray( hb_regexAtx( "(a+)(b+)",s,.f. ) ) // { { aAb 3 5 } { aA 3 4 } { b 5 5 } }
PrintArray( hb_regexAll( "a+b+",s,.f. ) ) // { { aAb aBb } }
PrintArray( hb_regexAll( "(a+)(b+)",s,.f. ) ) // { { aAb aA b } { aBb a Bb } }
PrintArray( hb_regexAll( "(a+)(b+)",s,.f.,,,1 ) ) // { aAb aBb }
PrintArray( hb_regexAll( "(a+)(b+)",s,.f.,,,2 ) ) // { aA a }
PrintArray( hb_regexAll( "(a+)(b+)",s,.f.,,,0,.f. ) )
// { { { aAb 3 5 } { aA 3 4 } { b 5 5 } } { { aBb 10 12 } { a 10 10 } { Bb 11 12 } } }
PrintArray( hb_regexAll( "(a+)(b+)",s,.f.,,,1,.f. ) ) // { { aAb 3 5 } { aBb 10 12 } }
?
Return Nil
Function PrintArray( arr, lNoNewLine )
Local i
IF lNoNewLine == Nil .OR. !lNoNewLine
?
ENDIF
?? " {"
FOR i := 1 TO Len( arr )
IF Valtype( arr[i] ) == "A"
PrintArray( arr[i], .T. )
ELSE
?? " " + Iif( Valtype( arr[i] ) == "N", Ltrim(Str(arr[i])), arr[i] )
ENDIF
NEXT
?? " }"
Return Nil
3.10 INET subsystem
This is a basic set of functions, designed for the work in networks using the IP protocol. With their help it is possible to create a socket, to connect to another socket ( on the same or on the remote computer ) and arrange the reception/transmission of data. This is the basic set, and as such it implements the TCP/IP and UDP protocols. Using it, you can implement an exchange under the more high-leveled protocol - HTTP, FTP, POP3, etc. One example of such an implementation is the libraryhbtip, 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 otherINET 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 usesINET 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 sockethSocket. 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 theTimeLimit for the socket hSocket. TimeLimit works, if installedxCallBack - 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 theWinsock 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 insection 3.13.14 ). xCallBack marks the blocking operation, when the timeout expires, established for this socket. IfxCallBack returns .F., reader/writer function stops trying to start blocking operation and returns the error. If the samexCallBack returns .T., the function repeats the launch of the read/write operations - and so til theTimeLimit (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 thexCallBack 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 theTimeLimit, 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 portport. 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 thehSocketSrv set the error code.
hSocket := hb_inetConnect( cAddress, nPort )
Carries out connecting to the port nPort server ( local or remote ) to the addresscAddress, 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 sockethSocket.
hb_inetConnect( cAddress, nPort, hSocket )
Carries out connecting to the port nPort server ( local or remote ) to the addresscAddress, 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 sockethSocket.
hSocket := hb_inetConnectIP( cAddress, nPort )
Works similarly to hb_inetConnect(), but as the address takes only IP address and isthread 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 isthread 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 stringcResult in a quantity not exceeding nAmount, if this parameter is passed, or the length ofcResult 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 stringcResult in a quantity not exceeding nAmountif this parameter is passed, or the length ofcResult 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 beforeCRLF is read, the function does not return anything and will set the error code. The returned line does not includeCRLF. In nBytesRead,if it is transferred, there will be written the number of the received bytes, includingCRLF - 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 receivedCRLF 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 thenBufSize 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 instructionsnBufSize you can manage it. You can, for example, set nBufSize equal tonMaxLength - 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 parametercBlock. 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 thenMillis 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 ParameternLengthif 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. ParameternLengthif 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 portnPort and to a certain logical interface, described cAddress (if it is specified). IflBroadCast 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 onIP address cAddress, port nPort. nSize specifies the amount of data transferred; if this parameter is omitted, then all what is incBuffer 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 stringcResult in a quantity not exceeding nSizeif this parameter is transferred, or the length ofcBuffer 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 ofjavascript functions main2() and st().
3.11 Multithreading
Multithreading is a programming model that allows multiple threads to run within the same process, the same application, interacting with each other, sharing the resources of this application. Threads are performed by the operating system in parallel. If the system is a single-processor, it is a quasiparallelism provided by the fact that the OS allocates the time between threads. In a multiprocessor system threads can actually be performed in parallel on different processors, providing a general improvement in the performance of the application. It is often convenient to use separate threads for any action related to the lengthy computation or a lengthy waiting time of any event, blocking the execution of the program ( for example, when working in a network using sockets ); while the main thread continues to respond to user's actions.
The Harbour application can be built for a single-threaded execution and for multi-threading. In the last case it is necessary to use a multi-threaded version of the Harbour virtual machine - the hbvmmt.lib library instead of a single-threaded hbvm.lib and the library of your C compiler, containing functions for the realization of multi-threading (for Borland C it is cw32mt.lib). The most simple solution is to build your application usinghbmk2 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 parameternThreadAttrs, containing the attributes of the thread. These attributes are described in hbthread.ch: #define HB_THREAD_INHERIT_PUBLIC 1
#define HB_THREAD_INHERIT_PRIVATE 2
#define HB_THREAD_INHERIT_MEMVARS 3
#define HB_THREAD_MEMVARS_COPY 4
For instance, if we write hb_threadStart( hb_bitor(HB_THREAD_INHERIT_PUBLIC,HB_THREAD_MEMVARS_COPY),@thFunc() )
, the thread receives copies of all Public variables of the parent, and ifhb_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 thepThID 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 ofpThID. 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 threadpThID, or one of the threads in the array of pointers apThID, or all the threads of this array, if the parameterlAll is specified and it is equal to .T.. Returns the number of the comleted thread, or the number of threads comleted in the period ofnTimeOut 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 unlocksmutex pMtx, or until the nTimeOut milliseconds, if this parameter is passed. Returns .T. ifpMtx 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 onpMtx then the operating system itself chooses, which of them to send the notification to. The parameterxVal is transmitted to the unlocked thread, through the function hb_mutexSubscribe() of the thread, that writesxVal 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" onmutex 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 samemutex 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 codeblockbCode 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" onpMtx, 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/unlockedmutex, but, i.e., put before it hb_mutexLock( pMtx )
and after ithb_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, ifmutex is already blocked by something - cases of carelessness can cause thedeadlock - 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 lockmutex. 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 usemutex 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. WhenbAction is performed by the thread all other threads which call at this moment the hb_threadOnce(), are suspended, even if they use a differentonceControl. If a thread tries to call hb_threadOnce() with the same onceControl recursively from bAction, the function immediately terminates without executingbAction and returns .F.. The function returns a boolean value that indicates wether it was the first call of hb_threadOnce() for thisonceControl.
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 inshared mode.
But there is another possibility - you can transfer the workspace from one thread to another, using the known by usersxBase++ 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 aliascAlias from the current thread, moving it to zero space. It is possible to transmit information throughxCargo to the threads, which will use it.
lOk := hb_dbRequest( [cAlias] [, lNewArea] [,@xCargo] [,lWait])
Requests working area of zero space by the name of the alias cAlias;lNewArea specifies whether to use a new workspace ( as in dbUseArea() )or not,lWait - wether it should be waited until the requested working area is available. The function returns .T. if all was successfully.
This mechanism allows to use workspaces by different threads together . To illustrate it we shall take 2 commands:
#xcommand UNLOCK WORKAREA [] => hb_dbDetach( )
#xcommand LOCK WORKAREA => hb_dbRequest( , .T.,, .T. )
After the opening table by the command Use the thread executes UNLOCK WORKAREA
and, i.e., moves it in zero space. Now each thread, which needs this table, can get access to it, for example:
LOCK WORKAREA "DOCUMENTS"
COUNT TO nInvoices FOR year( DOCUMENTS->DATE ) == year( date() )
UNLOCK WORKAREA
Other examples of multi-threaded programs you can find in the harbour/tests/mt.
3.12 File IO API
Harbour inherited from the Clipper a set of functions FOpen(), FSeek(), FRead(), ...
, that are designed to work with the files.
This collection supplemented with new functions, a complete list is provided in subsection3.13.2 File functions.
New File IO API is a set of functions with hb_vf prefix, similar to the common file functions, with the same set of parameters and the same functionality. An implementation of these functions allows to use replaceable drivers like RDDs. Now, if you put a prefix of any of the existing drivers with a colon at the end in front of the file name ( "mem:", "net:", ... ), a communication with this file will go through appropriate driver. The following function call, for example:
hb_vfCopyFile( "test.txt", "mem:test.txt" )
will create a copy of a "test.txt" in a memory (hbmemio driver).
IO drivers are the contributed Harbour libraries, such as hbnetio, hbmemio, hbgzio, hbbz2io, hbcomio and others.
Functions list:
lOk := hb_vfExists( cFileName, [ @cDestFileName ] )
Checks, if a file cFileName exists, matches the File()
function.
nResult := hb_vfErase( cFileName )
Deletes a file cFileName, matches the FErase()
function.
nResult := hb_vfRename( cFileSrc, cFileDst )
Renames a file cFileSrc into cFileDst, matches the FRename()
function.
nResult := hb_vfCopyFile( cFileSrc, cFileDst )
Copies a file cFileSrc into cFileDst, matches the FCopy()
function.
lExists := hb_vfDirExists( cDirName )
Checks, if a directory cDirName exists, matches the DirExists()
.
nSuccess := hb_vfDirMake( cDirName )
Creates a new directory cDirName, matches the MakeDir()
.
nSuccess := hb_vfDirRemove( cDirName )
Deletes a directory cDirName, matches the DirRemove()
.
aDirectory := hb_vfDirectory( [ cDirSpec ], [ cAttr ] )
Returns an array with info about a directory, matches the Directory()
function.
nFreeSpace := hb_vfDirSpace( cDirName, [ nInfoType ] )
lOk := hb_vfAttrGet( cFileName, @nAttr )
Stores in a nAttr variable, passed by a reference, an attributes of acFileName 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 thehb_fGetDateTime()
function.
lOk := hb_vfTimeSet( cFileName, tsDateTime )
Sets a date and time (tsDateTime) of a cFileName modification; matches partially thehb_fSetDateTime()
function.
nSuccess := hb_vfLink( cExistingFileName, cNewFileName )
Creates a hard link cNewFileName to a file cExistingFileName, matches thehb_fLink()
function.
nSuccess := hb_vfLinkSym( cTargetFileName, cNewFileName )
Creates a symbolic link cNewFileName to a file cTargetFileName, matches thehb_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 thehb_FLock()
function.
lOk := hb_vfUnlock( pHandle, nStart, nLen )
Unlocks a file fragment from nOffsetFrom to nOffsetTo, matches thehb_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 theFRead()
function.
cBuffer := hb_vfReadLen( pHandle, nToRead, [ nTimeOut ] )
Reads nToRead bytes from an opened file, returns a read buffer, matches thehb_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 thehb_Memoread()
function.
3.13 Miscellaneous functions
In this section we will cover a variety of new features that were not included in previous thematic sections.
3.13.1 Built-in compiler
Three functions using the built-in compiler (hbcplr.lib), based on the same code, that harbour.exe itself
nRetCode := hb_compile( "harbour", cFileName, [...] )
Compiles the file cFileName with the parameters of compilation passed to it. The result of its work is the.c file.
cHrb := hb_compileBuf( "harbour", cFileName, [...] )
Unlike hb_compile(), it does not create .c file, and returns the created as a result of compilingp-code in the form of a text string, which can be saved as a hrb file or comply with the help of hb_hrbRun()
cHrb := hb_compileFromBuf( cPrg, "harbour", [...] )
Compiles the code from the clipboard cPrg, returns p-codeas hb_compileBuf()
Below is an example of the use of run-time compilation:
FUNCTION Main( cFileName )
Local handle, buf, cBuf := "", i
buf := hb_compilebuf( "harbour", cFileName, "/n","/w" ) // Compile
hb_hrbRun( buf ) // Execute
handle := FCreate( Iif((i:=Rat('.',cFileName))=0,cFileName,Substr(cFileName,1,i-1)) + ".hrb" )
FWrite( handle, buf )
FClose( handle )
Return Nil
3.13.2 File functions.
lSuccess := hb_fGetAttr( cFileName, @nAttr )
Writes to a passed by the reference variable nAttr the attribute of a file with the namecFileName.
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 returnshandle 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 thefileio.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 filecExt 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. OptionalcAttr parameter can be used to include system ("S") and hidden ("H") files. IfcAttr contains "R" letter then before deleting READONLY attribute is removed from files (it's necessary to remove files with READONLY attribute in systems like DOS, MS-Windows or OS2). This function uses Harbour File IO API (hb_vf*() functions).
3.13.3 Set of functions, manipulating with file path, name, extension.
cRes := hb_FNameDir( cFullPath )
Returns a path of a file.
cRes := hb_FNameName( cFullPath )
Returns a name of a file without an extension.
cRes := hb_FNameExt( cFullPath )
Returns an extension of a file.
cRes := hb_FNameNameExt( cFullPath )
Returns a name of a file with extension.
cRes := hb_FNameExtSet( cFullPath, cExt )
Sets a new extension of a file, returns new full path.
cRes := hb_FNameExtSetDef( cFullPath, cExt )
Sets a new extension of a file in case if it is absent, returns new full path.
3.13.4 String functions
cString := hb_strFormat( cFormat, ... )
Returns a string formatted in the C-style, as in printf().
nResult := hb_HexToNum( cHex )
Similar function to the Val(), but the string cHex contains 16 decimal number.
cHex := hb_NumToHex( num[, nLen] )
Converts the number num in a string of nLen in the 16 system.
nTokens := hb_TokenCount( cString, [cDelim], [lSkipStrings], [lDoubleQuoteOnly])
This and the following 3 functions divide a string cString to the elements, the separator is acDelim ( the default is a blank " " ). A space as a separator has the peculiarity that multiple spaces in a row are considered as one. ParameterlSkipStrings, 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 parameternSkiptransmitted by the link that specifies what number of characters in a line should be omitted, the function writes in it the new value, which can be used by its next call.
aTokens := hb_ATokens( cString, [cDelim], [lSkipStrings], [lDoubleQuoteOnly])
Splits the string and returns an array with all the elements in accordance with the rules set out in the description of hb_TokenCount().
3.13.5 Array functions
aArray := hb_ADel( aArray, nPos [,lChangeSize] )
Extension of a standard function ADel(). Deletes specified array item, and, iflChangeSize 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, iflChangeSize is set to .T., increases its size - i.e., may be used instead of a couple AIns(), ASize().
nResult := hb_Ascan( aArray, [block], [nStart], [nCount], [lExact] )
Extension of a standard function Ascan() - it does the same, and, if lExact is set to .T., evaluates exact comparison.
nResult := hb_RAscan( aArray, [block], [nStart], [nCount], [lExact] )
Works like the hb_Ascan(), but starts a search from the end of an array.
3.13.6 Processes
nResult := hb_ProcessRun( cCommand, [cStdIn], [@cStdOut], [@cStdErr], [lDetach] )
handle := hb_ProcessOpen( cCommand, [cStdIn], [@cStdOut], [@cStdErr], [lDetach] )
nResult := hb_ProcessValue( handle, [lWait] )
nResult := hb_ProcessClose( handle, lGentle )
3.13.7 Bit operations
z := hb_BitAnd( x, y )
Returns the result of the bitwise operations And x & y
z := hb_BitOr( x, y )
Returns the result of the bitwise operations OR x | y
z := hb_BitXor( x, y )
Returns the result of the bitwise operations Exclusive OR x ^^ y
y := hb_BitNot( x )
Returns the result of the bitwise operations NOT x
y := hb_BitTest( x, n )
Checks wether the 1 n-th ( starting with 0 ) are installed in bit number x
y := hb_BitSet( x, n )
Returns the number of the 1 n-m ( starting with 0 ) bit number x
y := hb_BitReset( x, n )
Returns the number thrown in 0 n-m ( starting with 0 ) bit number x
z := hb_BitShift( x, y )
Bit shift x << y, if you have x >> y, then y must be negative
y := hb_BitSwapI( x )
Returns the byte swap in 16-bit number x
y := hb_BitSwapW( x )
Returns the byte swap in 16-bit unsigned including x
y := hb_BitSwapL( x )
Returns the result of the rearrangement of bytes in a 32-bit number x
y := hb_BitSwapU( x )
Returns the result of the rearrangement of bytes in 32-bit unsigned including x
3.13.8 Functions for a manipulation with variables
__mvPublic( cVarName )
Creates a Public variable named cVarName
__mvPrivate( cVarName )
Creates a Private variable named cVarName
nScope := __mvScope( cVarName )
Returns the scope (range, class) variable (constants are defined in hbmemvar.ch): HB_MV_NOT_FOUND = переменная не найдена
HB_MV_UNKNOWN = переменная не существует, но есть в symbol table
HB_MV_ERROR = информация недоступна ( ошибка памяти или аргумента )
HB_MV_PUBLIC = Public переменная
HB_MV_PRIVATE_GLOBAL = Private переменная, определена за пределами текущей функции/процедуры
HB_MV_PRIVATE_LOCAL = Private переменная, определена в текущей функции/процедуре
__mvClear()
Frees all Public and Private variables, implements the CLEAR MEMORY
__mvDbgInfo( nScope [,nPosition [,@cVarName]] )
Returns information about the variables corresponding to the range of nScope ( HB_MV_PUBLIC,... - look description __mvScope() ) for debugger;nPosition - the position of the requested variable in the range nScope,cVarName is passed by reference, the name of the variable is written it it , ifnPositionis 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 bynScopeif 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 thecVarName "?" is written.
lVarExist := __mvExist( cVarName )
Returns .T. if the variable cVarName ( Private or Public ) exists
xVal := __mvGet( cVarName )
Returns the value of a variable cVarName ( Private or Public )
xVal := __mvPut( cVarName, xVal )
Sets the value of the xVal variable cVarName ( Private or Public )
lIsByRef := hb_IsByRef( @cVarName )
Checks whether cVarName was transferred on the link, it must be passed by reference in the function hb_IsByRef().
__mvSetBase()
This is the so called hacking functionit should be used very carefully and with full understanding of what you are doing, because it breaks the standard, the correct behavior of the program - changes the basic drift of the list of Private variables. The call of this function causes the fact that the Private variables created in the current function, are not released on a way out of it, but are inherited by the function, from which the current one was called.
3.13.9 hb_WildMatch...()
lResult := hb_wildMatch( cPattern, cString, [lExact] )
Function compares the string cString with a sample of cPattern and returns the result - true or false. The pattern can contain the characters "?" and "*" ( as it is known, on the place of "?" incString can be used one of any character, on-site "*" - a few characters of any kind ). IflExact - .T., the entire string must conform to the model, if - .F. (the default), then at least its start.
lResult := hb_wildMatchI( cPattern, cString )
The same as hb_wildMatch()but it is insensitive to the case and hasn't a third parameter - requires a precise comparison.
hb_filematch( cFile, cPattern )
3.13.10 Strings packing/unpacking, based on zlib
cVersion := hb_zlibVersion()
Returns a string with the number of the version of zlib, used in the Harbour.
nbytes := hb_zCompressBound( cData | nDataLen )
Returns the maximum length of a compressed string, the string cData, which must be repackaged, or its length itself is passed to the function.
nbytes := hb_zunCompressLen( cPackedData, [@nResult] )
Returns the length of the unpacked string, the function is passed with the Packed stringcPackedData. 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 ofhb_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 referencenResult, 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 ofhb_zunCompressLen()). If you pass by reference nResultthere is written as the result of the operation ( 0 - all OK ). In case of an error, the function returns Nil.
cError := hb_zError( nError )
Returns a string with a description of the error that occurred when packing or unpacking,nError code which was recorded in nResult with the performing hb_zCompress()b hb_zunCompress() or hb_zunCompressLen().
3.13.11 Idle state
Idle state this is a state of expectation, when the Harbour program is waiting for the input from the user with the keyboard or the mouse. It arises as the result of Inkey() function call ( the READ command uses Inkey(), so that while waiting for input GET-objects idle state occurs ). In this state, there is an automatic collect of the garbage and any background tasks can be carried out. To add/remove these background tasks the following functions are designed (example of usage see in the harbour/tests/testidle.prg):
nHandle := hb_IdleAdd( bAction )
Codeblock bAction is added to the list of background tasks for execition duringidle state. The function returns nHandle, which can be used to remove tasks in the list.
bAction := hb_IdleDel( nHandle )
The task nHandle is removed from the list of background tasks, nHandle - value returned by the function hb_IdleAdd() when adding tasks. The function returns codeblock the corresponding th the task , or Nil if such a task is not in the list.
hb_IdleState()
As a result of performing this function the program is included in the idle state, performs garbage collection and performs one of background tasks list. As a result of successive calls of the function the background tasks are performed in the queue. This function has sense, if you have a long process without waiting States, and you have to interrupt it to perform background tasks.
hb_IdleSleep( nSeconds )
As a result of performing of this function the program is included in the idle state for nSeconds seconds. In contrast to the Inkey(nSeconds) the standby condition is not interrupted when you enter by the keyboard.
3.13.12 JSON - there and back
cJSON := hb_jsonEncode( xValue [, lHuman] )
Returns the JSON string, obtained by converting xValue, lHuman ( .F. by default ), makes the output string more "readable", if set to .T..
Codeblocks stored as "null", objects - as arrays ( methods are not recorded ).
nLengthDecoded := hb_jsonDecode( cJSON, @xValue )
Demaps the JSON string cJSON into the xValue variable, passed by reference. Returns the number of processed symbols.
3.13.13 Encryption
Blowfish - implementation of the BlowFish algorithm, designed by Bruce Schneier.
In this algorithm startup initialization of encryption tables is intentionally designed to be expensive to strongly reduce the efficiency of brute force attacks so call hb_blowfishKey() once for each new password and then reuse initialized encryption key in hb_blowfishEncrypt() and hb_blowfishDecrypt().
Warning: the size of encrypted data is padded to 64bit (8 bytes) so it's bigger then original one.
cBfKey := hb_blowfishKey( cPasswd )
Generates a key cBfKey from the passed password cPasswd for use in the encryption/decryption functions.
cCipher := hb_blowfishEncrypt( cBfKey, cText [, lRaw ] )
Encrypts cText, using the key cBfKey, prepared with hb_blowfishKey(). The optionallRaw 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 andlRaw> := .T.
disables restoring original string size encoded in encrypted string using ANSI X.923 standard so the size of decrypted string is the same as original one.
cText := hb_blowfishDecrypt( cBfKey, cCipher [, lRaw ] )
Decrypts cCipher, using the key cBfKey, prepared with hb_blowfishKey().
cCipher := hb_blowfishEncrypt_CFB( cBfKey, cText [, cInitSeed ] )
Encrypts cText, using the key cBfKey, prepared with hb_blowfishKey(), using CFB (cipher feedback) mode instead of ECB (electronic codebook).
cText := hb_blowfishDecrypt_CFB( cBfKey, cCipher [, cInitSeed ] )
Decrypts cCipher, using the key cBfKey, prepared with hb_blowfishKey(), using CFB (cipher feedback) mode instead of ECB (electronic codebook).
MD5 - ecryption and hashing
cCipher := hb_MD5Encrypt( cText, cPasswd )
cText := hb_MD5Decrypt( cCipher, cPasswd )
cDigest := hb_MD5( cText )
cDigest := hb_MD5File( cFilename )
SHA1, SHA2 - hashing
cDigest := hb_SHA1( cText [, lRaw ] )
cDigest := hb_HMAC_SHA1( cText, cKey [, lRaw ] )
cDigest := hb_HMAC_SHA224( cText, cKey [, lRaw ] )
cDigest := hb_HMAC_SHA256( cText, cKey [, lRaw ] )
cDigest := hb_HMAC_SHA384( cText, cKey [, lRaw ] )
cDigest := hb_HMAC_SHA512( cText, cKey [, lRaw ] )
3.13.14 Miscellaneous
xResult := hb_ExecFromArray( cFuncName [,aParams] )
Returns the result of the execution of the function specified by the name of cFuncName with parameters passed in the array aParams, for example: ? hb_execFromArray( "Str", {11,6,3} )
xResult := hb_ExecFromArray( @funcName [,aParams] )
Returns the result of the execution of the function specified by the index @funcName with parameters passed in the array aParams, for example: hfunc := @Str()
? hb_execFromArray( hfunc, {11,6,3} )
xResult := hb_ExecFromArray( bCodeBlock [,aParams] )
Returns the result of execution of the codeblock bCodeBlock with parameters passed in the arrayaParams: ? 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 objectoObject 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 arrayaParams, the method specified with the help of the pointer @msgName
xResult := hb_ExecFromArray( aExecArray )
In this version of use hb_ExecFromArray() the parameters are passed as an array: ? hb_execFromArray( { "Str", 11,6,3 } )
? hb_execFromArray( { hfunc, 11,6,3 } )
? hb_execFromArray( { {|n1,n2,n3|Str(n1,n2,n3)},11,6,3 } )
и т.д.
hb_ForNext( nStart, nEnd | bEnd, bCode [, nStep ] )
Implementation of FOR ... NEXT
cycle. The bCode codeblock, which receives a counter as a parameter ({|nIndex|...code...}
), is evaluated many times while the counter increments fromnStart till nEnd (or till the bEnd codeblock returns .T.) with anStep step.
cDirBase := hb_DirBase()
Returns the base directory, the one with the executable file.
cExePath := hb_Progname()
Returns the path and name of the executable file.
cSep := hb_ps()
Returns the character that separates directories in the path, passed in the OS ( "/", "\")
cSep := hb_osDriveSeparator()
cDirName := hb_DirTemp()
Returns a path to the system temporary directory.
lResult := hb_DirRemoveAll( cDir )
This function removes recursively whole directories with it's body so hb_DirRemoveAll( hb_ps() ) can remove all files and directories from your disk.
n := hb_Rand32()
Returns random integer value in a range from 0 to 0xFFFFFFFF.
n := hb_RandomInt( [n1,] [n2] )
Returns random integer value. Optional parameters n1, n2 - the range, and if n2 is not specified, then the return value is in the range between 0 and n1; if both parameters are not specified, then - in the range of 0,1.
n := hb_Random( [n1,] [n2] )
Returns random real value. Optional parameters n1, n2 - the range, and if n2 is not specified, then the return value is in the range between 0 and n1; if both parameters are not specified, then - in the range between 0 and 1.
hb_RandomSeed( n )
If the parameter n is 0, then first call to HB_RANDOM() or HB_RANDOMINT() activates initialization which generates new seed using current time in milliseconds and HVM stack address (for MT modes). If someone needs repeatable results from HB_RANDOM() and HB_RANDOMINT() then he should initialize the seed for each thread with some fixed value i.e. HB_RANDOMSEED( 123456789 )
Site map
© Copyright 2016. All Rights Reserved.