Friday, February 27, 2009

What's fixed in CA-Clipper 5.2a

The following information on the CA-Clipper 5.2a patch was extrated from 52doc.txt which came with the patch.


o C3049 - String Space exhausted. - Change to CLIPPER.EXE The compiler has been changed to increase the available string space. Developers whose applications compiled with this error in previous versions of Clipper should no longer receive this error message.

o Debugger Break Point Handling. - Change to CLD.EXE & CLD.LIB The debugger has been changed to improve Break Point handling. Developers who noticed difficulty in setting break points or noticed that break points were lost upon application restart will no longer have problems.

o Memoedit() double spacing problem. - Change to EXTEND.LIB The Memoedit() function has been changed to alleviate a double spacing effect which sometimes occurred. The only developers who noticed this problem had memo field data which had a trailing space at the wrap point. While this problem was uncommon it could be very disconcerting in applications where the format of memo field data is important.

o Inkey() setting Lastkey() to 0. - Change to CLIPPER.LIB The Inkey() function has been changed. The Inkey() function in CA-Clipper 5.2a will only set the Lastkey() value when a key is drawn from the keyboard buffer. The Inkey() function in version 5.20 set the Lastkey() value to 0 when there was no key in the keyboard buffer to retrieve and the time value expired. The 5.20 behavior had an adverse effect on several systems which use Inkey(); for example Memoedit() with a custom UDF() where the UDF() had been coded to expect the last navigational key pressed to be available via Lastkey() would receive a value of 0.

o OrdSetFocus() tag in lowercase. - Change to DBFNTX.LIB The OrdSetFocus() function will now accept parameters in any case. Developers who changed their applications to use order names rather than order numbers noticed that unless the name was in upper case the order established would be natural (no) order. This problem was unique to the DBFNTX driver.

o NATION support. - Change to NAT_OBJ.EXE The change here is only in the internal structure of the nation drivers. The drivers will now make a function call into CLIPPER.LIB to retreive clipper version information. In the future should a specific driver require updating this change simplifies that process.

SUPPORT NOTES CA-Clipper 5.01 vs 5.2x

o _GET_() - Changed in 5.2 and may effect 3rd Party products. If you have noticed that sometimes GETs don't appear then your application is probably making a call to _GET_(). The call to this internal function may be invoked from a 3rd party product.

The CA-Clipper internal function _GET_() has changed from version 5.01 to 5.2x. In 5.2x _GET_() no longer displays the GET. The GET is now only displayed after a call to the get_object:Display() method. Developers or 3rd parties who depended on the old behavior will either have to re-write the offending code, recompile, or ask the 3rd party vendor to supply a 5.2x compatible version of the library.

o Avoid calls to Internal Functions. As a reminder. Developers should not rely on the behavior of internal functions, (those that start with one or more "_"). Calling internal functions is strongly discouraged by Computer Associates as there is no guarantee the behavior of these internals will remain consistant between versions.

What's fixed in CA-Clipper 5.2b

The following information on the CA-Clipper 5.2b patch was extrated from 52abi.txt which came with the patch. This is an important patch as it fixes 28 known bugs !!!

The problems resolved by CA-Clipper 5.2b include:

1. Fixed a Virtual Memory Integrity Failure in FOPEN() when the file name is numeric instead of a character string.

2. Fixed a Virtual Memory Integrity Failure when calling AADD() to increase the size of an array.

3. Fixed a Virtual Memory Integerity Failure that occured when an ACHOICE() user function deleted elements from the menu selection array. ACHOICE() now allows the user function to add, delete, or modify existing elements without causing any problems in ACHOICE().

4. Fixed a Virtual Memory Integrity Failure in the Internal Runtime Event System. This was causing the DBFCDX driver to hang as well as various "unexplainable" errors.

5. Fixed a Virtual Memory Integrity Failure in the debugger when viewing multiple nested arrays. Tbrowse was producing a parameter error when viewing multiple nested arrays. In some instances this produced a Virtual Memory Integrity Failure.

6. Fixed a Virtual Memory Integrity Failure in the debugger's memory allocator. (STAR Issue# 737689)

7. Fixed INDEXKEY() memory corruption problem. INDEXKEY() would occasionally return a garbage string when called repeatedly. This sometimes caused a Virtual Memory Integrity Failure.

8. Fixed MEMOEDIT() buffer memory corruption problem. This occurred when MEMOEDIT() was called with a user defined function. This would result in various memory related errors including a Virtual Memory Integrity Failure.

9. Fixed slow disk I/O on replaces on large DBF's with non-unique indexes. The performance has been improved to a speed which is comparable to CA-Clipper 5.01a.

10. Fixed the releasing of all relations (in all workareas) when any child dbf was closed. Now closing a child database releases only the relations that it is involved with.

11. Fixed the DBFNDX Replaceable Database Driver so that it now properly seeks on a date value with SET DELETED ON.

12. Fixed DBCREATE() to properly return a NIL value as documented.

13. Fixed INDEXORD() so that it now returns a zero when no database is open rather than generating a runtime error.

14. Fixed some occurances of internal error 1210 (database and index are out of sync).

15. Fixed many occurances of internal error 415 (can not open external overlay file).

16. Fixed the Runtime Memory Manager so that it now returns an EG_MEM (5300 "Memory Low Warning") before generating a memory exhausted error.

17. Fixed Runtime failures that occured when CA-Clipper mistakenly tried to use non-existent EMS memory.

18. Fixed FREAD() so that it does not modify variables that it shouldn't have access to.

19. Fixed BROWSE() so that it no longer causes the repositioning of a file to BOF() when editing takes place in a new record.

20. Fixed the debugger so that it is no longer necessary to specify the default file extension (.PRG or .PPO) when opening a file.

21. Fixed the debugger so that it correctly searches the path (indicated by the PATH environment variable) when searching for a file to open.

22. Fixed the debugger so that it does not produce "Argument error +" when the F6 key is pressed to view databases.

23. Fixed DBU so it now correctly parses a file name that contained a drive letter and colon (:) but no backslash (\) (such as C:TEMP).

24. Fixed numerous bugs in the R.L. utility.

25. Fixed the compiler screen to include the missing /t and /z options in order to match the documented options.

26. Fixed the spelling of OrdDestory to OrdDestroy in STD.CH for the DELETE TAG command.

27. Fixed the "Guide to CA-Clipper" .NG file so that the Norton Guide engine may now be unloaded from memory.

28. Enhanced the //INFO command line parameter to show what message and collation driver is linked in.

Technical Note: The Virtual Memory Integrity Failure Error Message

The Virtual Memory Integrity Failure error message (or VMIF for short) refers to a problem that is neither well documented nor well understood. In this technical note, we will explore what the VMIF message indicates, some of the common reasons it occurs, and what measures to take if you encounter one.

CA-Clipper uses a virtual memory manager to allow applications to access more strings, arrays, and objects than conventional memory would otherwise allow. It accomplishes this by swapping information to and from expanded memory or disk as needed. Each virtual data item (called a segment) has an entry in a descriptor table that maintains its current location and length. A segment's length can be up to 65,518 bytes, while its location may be conventional memory (resident), disk or EMS (non-resident). When CA-Clipper receives a request for non-resident data, it always checks the segment's descriptor entry to ensure that it's length is non-zero. Any descriptor whose length is zero is, by definition, invalid because VM does not allow zero-byte segments. Thus, a zero length segment is interpreted as a corruption, and the VMIF error message is displayed.

Along with the VMIF message is a hexadecimal address that indicates the address of the corrupted descriptor table element. This information may be useful to the CA-Clipper Technical Support as well as the development staff. C and Assembler programmers may also find this information useful in determining if and how their code caused the corruption. A special case is the address 0000:0000. It indicates that a NULL and (possibly uninitialized) pointer was used to access virtual data.

While it is true that the VMIF message occurs for exactly one condition, that condition can be created in many different ways. Research indicates that the conditions that lead to a VMIF can be broken down into three distinct categories.

First, several VMIFs can be attributed to Clipper programming bugs. While a VMIF should never occur solely on the basis of executing Clipper code, a combination of inadequate error detection on the part of Clipper runtime and a violation of proper programming practices may cause the error to occur. Examples of this have been: calling FOPEN() with numeric data for the file name, deleting array elements from within an ACHOICE() UDF, and assigning NIL to any TBROWSE instance variable that requires a character value. (Please note that the TBROWSE VMIFs were fixed in the 5.01a release and the ACHOICE() and FOPEN() VMIFs are fixed in the 5.2b release by producing an error when improper values are used.)

The second category is within the CA-Clipper runtime support libraries. CA-Clipper's runtime support libraries constantly issue calls to the VM system throughout the normal execution of a Clipper application. On occasion, specific conditions pertaining to runtime activities are not properly handled internally, creating conditions that eventually result in a VMIF. These instances are always considered problems by the development staff.

The last category is external code typically written in C or Assembler by CA-Clipper programmers and third party library developers. These are usually caused by improper use of one of CA-Clipper's APIs or by changing the functionality of something within CA-Clipper's runtime that is assumed to remain constant.

Although CA-Clipper's VM system provides detection and reporting, it is almost never the cause of a VMIF.

VMIFs are commonly detected long after the actual corruption occurred. A module may store a zero-length segment in the segment descriptor table either through use of VM or by writing to a wild pointer. This corruption will not be detected until a VM segment is accessed that requires the corrupted segment to be discarded, or swapped to disk or EMS. The Clipper VM subsystem is demand-based. This means that it only performs swapping when swapping is absolutely required. If a VMIF consistently occurs at a specific point in the execution of an application, it is often not actually caused at this point. It tells us that a corruption has occurred, but has no means for determining the cause of the corruption or when the corruption actually occurred.

Because VMIFs are detected when the VM system attempts to swap in memory, a change in the amount of conventional memory available when an application is executed changes the possibility of the VMIF occurring. When sufficient real memory is available, VM has no need to perform any swapping and any possible VMIFs will not be detected. When less real memory is available to an application, the entire profile of swapping is changed, and may prevent the VMIF from occurring in a constant location.

This raises the question of how to ensure that the VM system is being called during the testing phase of application development. A simple and effective method is to use CA-Clipper's //X: parameter to decrease the available memory as much as is feasible. This will ensure that the VM system will be called with the most frequency. (The //X: parameter is detailed in the Runtime Environment chapter of the CA-Clipper 5.2 Programming and Utilities Guide.)

If a VMIF occurs, try to determine how to reproduce the problem at will and if possible, isolate it to a small example and immediately contact your CA-Clipper Technical Support Representative. The CA-Clipper Quality Assurance team is also constantly searching for occurrences of VMIFs. Either way, the CA-Clipper Development team is anxious to correct all internal VMIFs as quickly as possible.

What's fixed in CA-Clipper 5.2d ?

The following information on the CA-Clipper 5.2d patch was extrated from 52dix.txt which came with the patch. This is an important patch as it fixes 86 known bugs !!!

What is the 5.2d patch ?

The CA-Clipper 5.2d/ExoSpace 1.0f upgrade described in this file contains latest fixes for CA-Clipper and CA-Clipper/ExoSpace. This includes an updated version of the DBFMDX driver revised to be compatible with the CA-Clipper/Compiler Kit. The DBFMDX database collation drivers are now CA-Clipper/ExoSpace compatible also. The DBFCDX driver has not been changed. We are currently working on a separate patch for it. It will be published as soon as it is ready.

Problems resolved by CA-Clipper 5.2d:

1. Fixed a VMIF in the Object Memory Manager. This occurred in low memory situations when the OMM took space from the VM segment that it was attempting to expand.

2. Fixed a GPF/VMIF in the Object Memory Manager's garbage collector that occurred when there were a large number of static variables.

3. Fixed the FIELDPOS() function so that it no longer returns misleading results when passed an invalid argument.

4. Fixed bug in oStrDup() where an incorrect number of bytes were being copied to an incorrect location for dedicated VM segments.

5. Fixed a VMIF in the Object Memory Manager that occurred when performing an incremental garbage collection on an array that had just satisfied the requirement for occupying its own dedicated VM segment.

6. Fixed a GPF that occurred when growing the class/dictionary table. This happens when instantiating a class or creating a dictionary when the dictionary/class table is full.

7. Fixed the RDD manager so it does not become corrupted when the VMM swaps its data to disk. This occurred only in a very low memory situation.

8. Panning right in a TBROWSE containing a single frozen column no longer causes TBROWSE to go into an infinite loop when trying to redisplay.

9. Fixed a GPF in TbrowseNew() and TbrowseDB() in certain memory configurations.

10. Fixed a GPF in the function ALIAS([]) when nWorkArea > 255.

11. Fixed the GPF caused by function ORDKEY() with an invalid .

12. Fixed the SET DELETED ON command so that it does not affect record visibility on a network.

13. Fixed the SORT command so that it deallocates the memory it allocated before returning control to its caller.

14. Fixed internal errors 650, 775 and 999 that occurred when the compiler did not produce correct code for statements that contained an extra set of parentheses. EXAMPLE: (( Test() ))

15. Fixed C3039 PHASE ERROR in the compiler that occurred when a .PRG source file contained several static variables that were inline initialized to a codeblock. EXAMPLE: static Var1 := { | x | func1( x ) } static Var2 := { | x | func2( x ) } static Var3 := { | x | func3( x ) }

16. Fixed a lockup in the compiler that occurred when a .PRG had a large number of STATIC variables.

17. Fixed the compiler so that /Z switch has effect on expressions that contain constants.

18. Fixed the compiler so it will not produce the "C3050 Something terrible has happened" error when it encounters a user defined function whose argument is being negated. Example: MyUDF( -X )

19. Fixed a problem in the compiler that allowed it to accept illegal comment blocks. EXAMPLE: /**/*/

20. Fixed the compiler so it now generates an error when it encounters one of the following incorrect control blocks: IF...ELSE...ELSE...ENDIF IF...ELSE...ELSEIF...ENDIF

21. Fixed the compiler to generate an error when it encounters the following statement: Alias->Alias->

22. Fixed the compiler so it does not generate an error when it encounters an expression containing multiple NOTs. EXAMPLE: iff( ! ! .t., .t., .f. )

23. Fixed the compiler so it does not generate an error when it encounters a statement in which an aliased expression contains extra parenthesis: (MyFunc())->DBFfield

or: ? (MyFunc())[x]

24. Fixed the compiler so it will not produce the "C2005 Statement not recognized match failed at: '&'" error when it encounters the following: @ nCol, nRow SAY "string" + &(macro_expression)

25. Fixed evaluating a macro that contains an alias caused a syntax error in the next macro evaluation. EXAMPLE: ? &( "aTest[1]" ) // ? &( "test->( RECNO() )" ) ? &( "aTest[1]" ) // Syntax error - &

26. Fixed a lockup that occurred when a memvar was created within a macro. The scope of that memvar was incorrect, it expanded to the upper lever procedure. EXAMPLE: &("MEMVAR->dummy") := 666

27. Fixed a lockup that occurred when using a macro to expand an expression, that contains an alias. EXAMPLE: &(alias(1))->( Recno() )

28. Fixed SELECT() so it now returns 0 when passed an invalid parameter.

29. Fixed DBCREATE() so it generates a run-time error when it is passed an unknown RDD name instead of hanging the system.

30. Fixed The USE command so it does not upper-case convert the contents of the variable which is used in VIA clause.

31. Fixed DBCREATE() so it generates a run-time error when there is not enough space on a drive.

32. Fixed the database/index synchronization as in the following example: SET ORDER TO REPLACE SKIP() SET ORDER TO SKIP(-1) ? RECNO()

33. Fixed the 'INDEX ON TO WHILE ' command so it does not create only one record in the scoped index (regardless how many meet the WHILE condition) when the current index order was used, and the most recent DBF movement was not an index movement (SKIP, SEEK).

34. Fixed the updating of the conditional .NTX file when a field, that was not in the key expression, was updated.

35. Fixed index file update failure on complex key expression when the multiple fields were being concatenated as a key and the key field(s) value(s) were subsequently changed.

36. Fixed the 'REPLACE WITH' command so it will not cause a program to lock up if it attempts to write memo information to disk with insufficient space.

37. Fixed the following commands: COPY TO...SDF, COPY TO...DELIMITED APPEND FROM...SDF, APPEND FROM...DELIMITED so they do not hang the system when the DEFAULT option is chosen from the 'cannot open/create file' error message.

38. Fixed the COPY TO [ SDF | DELIMITED ] command so it does not close the current workarea when the DEFAULT option is chosen from the 'cannot open/create file' error message.

39. Fixed the COPY TO command when the current database is the child of a relation and there is a pending seek.

40. Fixed CREATE FROM & COPY STRUCTURE so they work when the target is a disk drive designator and file name without a backslash. EXAMPLE: C:FileName

41. Fixed the APPEND FROM SDF command so it would ignore null-characters when it reads SDF-file.

42. Fixed TBrowse so that 'hitBottom' would report .T. if an attempt was made to go past the end of the available data when there are not enough records to completely fill the TBrowse display and refreshAll() is invoked prior to stabilization.

43. The decimal portion of the field definition for date, logical and memo fields is now initialized.

44. Fixed the function ORDSETFOCUS() to ignore an invalid as per documentation.

45. Fixed the function ORDSETFOCUS() to return an empty string when a database has no controlling order and the DBFNDX driver is used.

46. Fixed the function INDEXKEY() to return an empty string when there was no database opened in a workarea.

47. Fixed the functions ORDBAGNAME(), ORDNAME(), ORDNUMBER(), ORDKEY(), ORDFOR() to provide an argument type checking. The following run-time errors are generated when one or more of the arguments are of the wrong type: Error DBCMD/1021 Argument error: ORDBAGNAME Error DBCMD/1022 Argument error: ORDFOR Error DBCMD/1023 Argument error: ORDKEY Error DBCMD/1024 Argument error: ORDNAME Error DBCMD/1025 Argument error: ORDNUMBER

48. Fixed the inconsistency between OrdSetFocus("") and OrdSetFocus(0). Now OrdSetFocus("") also restores the natural order.

49. Fixed OrdNumber("") so that it returns zero rather than the number of orders for the workarea when DBFNDX driver is used.

50. Fixed the GO command when DBFNDX driver is used and the data type of was DOUBLE, not LONG.

51. Fixed the OrdName(0) so it would return the name of the current controlling order when DBFNDX driver is used.

52. Fixed the OrdNumber() when DBFNDX driver is used and there is no in the Order List.

53. Major revisions have been made to the DBFMDX driver to rectify the problems in the previous versions.

54. Fixed SET ALTERNATE so it generates a run-time error when there is no space available on the disk.

55. Fixed the STRTRAN() function so it no longer goes into an infinite loop when the cSearch parameter is a zero length string.

56. Fixed ALLTRIM() so it generates an error when given an argument of the wrong type.

57. Fixed the function DIRECTORY (, "V") so it does not pad the volume name with garbage, if its length is exactly 8 characters long.

58. All commas contained in a GET's PICTURE clause are now copied into the GET's edit buffer.

59. When a GET's PICTURE clause contains an '@R', the number of editable positions in its edit buffer is now calculated correctly.

60. When a GET's PICTURE clause contains an '@R', non-picture characters beyond the last editable position are now displayed.

61. When a GET's PICTURE clause contains a paren, a leading paren in its edit buffer now causes the value to be parsed as a negative number.

62. A bug was fixed in which GETs that had a PICTURE clause containing a leading '$' would sometimes display negative values with multiple '$' characters.

63. The size of a GET's edit buffer no longer increases if its PICTURE clause contains a paren, and a negative value is entered into its edit buffer.

64. In the #command for GET, the smart stringify result marker was replaced by a normal stringify result marker. The previous use of the smart stringify marker had caused parsing problems whenever an aliased variable was used in a GET command.

65. The ALERT() function now updates the LASTKEY status.

66. Fixed ALERT() so that it no longer exhibits occasional problems when fewer than four option strings are used.

67. Fixed FSEEK() so that it rejects requests that would cause the resulting absolute file position to become negative. In such cases the current file position is left unchanged and an error flag is set; the value of this flag can be retrieved by calling the FERROR() function.

68. Fixed the 'SET FUNCTION TO' command so that it will now accept strings that contain only white space.

69. The SET DATE FORMAT code was modified so that the format string must contain non-numeric delimiters between the month, day, and year portions.

70. Fixed the DEVPOS() function so that it now immediately responds to an error condition.

71. Fixed STUFF() so it will generate a run-time error when there is insufficient virtual memory to create it's data buffer.

72. Fixed VAL("-.nnnn") so it does not return "******" now.

73. Fixed DBU so it does not go to an endless loop if there is a filter set on a field and if GOTO is used and a record number is given that is not one of the records matching the filter.

74. Fixed DBU to respond to the key when BROWSEing a database in which none of the records match the filter condition.

75. Fixed DBU so it does not go to an endless loop if, when VIEWing an empty database, the user presses any character key.

76. Fixed DBU to update and reopen indexes after PACKing or ZAPing a database.

77. Fixed DBU so that BROWSE reflects the filter condition after PACKing or ZAPing a database.

78. Fixed the REPORT FORM command's sub-grouping feature when the name of the last subgroup in a group was the same as the name of the next group.

79. Fixed the REPORT FORM command without the TO PRINT clause so it produces the printer output if SET PRINT ON command was previously issued.

80. The multi-lines per record using ";" - method is now implemented.

81. Fixed the number of lines on the first REPORT page to be the same as the number of lines on the following pages.

82. Fixed the REPORT FORM command not to do page eject after subgroup if page eject after group flag was set to TRUE.

83. Fixed the REPORT FORM heading not to be truncated when page width is set to 61 or more characters.

84. Fixed the CA-Clipper Debugger so it does not display garbage in the last line of the Monitor Window when was pressed.

85. Fixed RLOCK() and DBRLOCK() so that it would not fail if the database was the child of a relation and there was a pending seek.

86. Fixed the failure of the RLOCK() function when invoked after a successful FLOCK() on an open shared database.

What's fixed in CA-Clipper 5.2c ?

The following information on the CA-Clipper 5.2c patch was extracted from 52ci.txt which came with the patch. This is an important patch as it fixes 22 known bugs !!!

Problems resolved by CA-Clipper 5.2c:

1. Fixed most instances of the DBFNTX/1210 error.

2. Fixed INDEXKEY() so that when it is called with an invalid order argument it will no longer corrupt the data at memory location 0000:0000 in dos and will no longer generate a General Protection Error in Exospace.

3. Fixed CMEM.OBJ (in Clipper.lib) so that the malloc(), _fmalloc(), free() and _ffree() memory allocation functions will return the correct information.

4. Fixed VM Integrity error when evaluating detached code blocks that are nested three or more deep.

5. Fixed VAL() so it no longer left justifys the result.

6. Fixed ACHOICE() so that it will not redraw the menu window after returning from the user specified function unless: A. the user specified function changes the number of elements in the menu array. B. the user specified function returned the new return code of AC_REDRAW (which can be found in ACHOICE.CH and has the numeric value of 4).

7. Fixed the Expanded Memory Manager so it will not attempt to use more than eight megabytes of EMM. Currently, Clipper can not take advantage of more than eight megabytes of EMM. Please note that the printed documentation is incorrect. This was the cause of many corruptions on systems that had more than eight megabytes of expanded memory.

8. Fixed an incorrect calculation in _xvalloc() that caused the Virtual Memory Manager to allocate 1K to much if the requested size + 16 (in bytes) was an even multiple of 1024.

9. Fixed an internal calculation that did not account for overflow when converting a segment:offset address to an absolute address. This could only be encountered by calling the VM API function:_xvalloc().

10. Fixed several problems in the DBFCDX replaceable database driver.

11. Fixed a compatability problem between CA-Clipper Tools-II and CA-Clipper 5.2x. Developers using CA-Clipper Tools-II noticed unresolved symbols at link time. The CT2PATCH.OBJ file will resolve these problems and should be included as an object on the link line. Please note that CT2PATCH.OBJ is only intended for use with CA-Clipper Tools-II, those not using CA-Clipper Tools-II should not include this file in their applications.

12. Fixed DBU so that closing a file with an associated filter will no longer cause a DBCMD/2001 error.

13. Fixed DBU so that performing a replace will no longer cause a "Lock Required" error.

14. Fixed REPORT FORM so header will print when creating a report whose width is greater than 254 characters.

15. Fixed REPORT FORM so that it now ejects properly for groups.

16. Fixed REPORT FORM so that double spaced forms will now print properly.

17. Determined that the EG_SYNTAX error in REPORT FORM was not caused by an empty database. It was caused by the absence of a field name for the report. This is the correct behavior.

18. Fixed REPORT FORM so that it will respect the "SUMMARY ONLY" option when requested.

19. Fixed REPORT FORM so that it no longer prints an extra form feed after the report is completed.

20. Fixed RL so that it will save entered data from all entry screens instead of only the current display screen.

21. Fixed RL so that it will no longer save the rightmost column when it is empty.

22. Fixed RL so that it accepts "T" or "F" in it's question fields. It previously was limmited to "Y" or "N".

Technical Note: RLOCK()/DBRLOCK() problem

o Problem: RLOCK() and DBRLOCK() (without any parameter) will not reliably lock a record under certain circumstances, even though their return values (.T.) indicate success. This occurs when locking a record in the child database of a relation, after the record pointer has been repositioned by a SEEK or GOTO, but before the data has been accessed.

o Solution: A workaround for this problem is to use DBRLOCK( RECNO() ) instead of RLOCK() or DBRLOCK(). A simple way to implement this is:

1. Add the following statements to a copy of the header file: #translate RLOCK() => DBRLOCK( RECNO() ) #translate DBRLOCK() => DBRLOCK( RECNO() )

2. Re-compile your program with the /u option, where is the modified STD.CH file.

o EXAMPLE: use child shared new set index to childntx use parent shared new set relation to FieldOne into child goto 2 ---------------> if ( rlock() ) // change this rlock() to dbrlock(recno()) replace child->FieldTwo with parent->FieldTwo endif close data return

o Note: This is a temporary workaround while a solution for the problem is being worked upon.

Thursday, February 26, 2009

Migrating from CA-Clipper 5.2e to xHarbour (Part III)

Having managed to recompile an actual DOS-based CA-Clipper EXE to a XHarbour W32 EXE, the next question is does it work

1) Launch FAS.EXE... so far so good

2) Input User Name & press enter, it bombs with the message

Error BASE/1003 variable does not exist GETLIST
Error at ...: MAIN_PASS(1051) in Module: FAS_MAIN.PRG
Called from : PASS_SCR(234) in Module: FAS_MAIN.PRG
Called from : MAIN(125) in Module: FAS_MAIN.PRG

Solution by Michael Hagl via comp.lang.xharbour

Add LOCAL getlist:={} to MAIN_PASS.PRG

Status : FAS.EXE can now run but now getting a weird ALERT() positioning as reported to

Solution :
// Ron Pinkas 27/2/09
SetMode( 25, 80 )

Updated 2nd March 2009

Conclusion :

We started with FAS4DOS (CA-Clipper 5.2e)

What I got was a true Win32 executable but it looks exactly the same as FAS4DOS (minus the effect of Funckys' background function CLS() ) :-(

So how did I get the following screen-shot ? ;-)

Notice that the Window is centred and displays the EXE name instead of the C:\PATH\EXENAME and looks bigger...

So, how did I do it ?

Wednesday, February 25, 2009

Migrating from CA-Clipper 5.2e to xHarbour (Part II)

From Migrating from CA-Clipper 5.2e to xHarbour (Part I), we have clicked Build and OMG, there are lots of Unresolved External Symbols that needed to resolve.

As usual, I posted to comp.lang.xHarbour and the registered user's xHarbour newsgroup.
Within a short while, I had a most useful response from Michael Hagl as follows :-

Michael Hagl :

Function Mouse(x)

Return SetMouse(x)


Replace with Phil Barnett's Random()

Michael Hagl:

very dirty.

Static saSetAttrib

Function Aprint(nRow,nCol,str,attr,nLength,nStart)
Local nHighStart := At("^",str)
Local nHighEnd
Local cAttrib, nAttrib

If nHighStart > 0
cAttrib := Substr(str,nHighStart+1,1)
nAttrib := GetAttrib(cAttrib)
If Valtype(nAttrib) == "C" .and. nAttrib == "^"
nHighStart := 0
str := Left(str,nHighStart-1)+Substr(str,nHighStart+1)
str := Left(str,nHighStart-1)+Substr(str,nHighStart+2)
nHighEnd := At("^N",Upper(str))
If nHighEnd > 0
str := Left(str,nHighEnd-1)+Substr(str,nHighEnd+2)

If !(nStart == Nil)
str := Substr(str,nStart)
If !(nLength == Nil)
str := Untrim(str,nLength)
If nHighStart > 0
If Valtype(nAttrib) == "N"
Elseif Valtype(nAttrib) == "C"
// ???
Return Nil

Static Function saSetAttribInit()
If saSetAttrib == Nil
saSetAttrib := {{"B","B"},;
Return Nil

Function SetAttrib(chr,nAttr)
Local nPos
chr := Upper(chr)
nPos := Ascan(saSetAttrib,{|x|x[1]==chr})
If nPos > 0
saSetAttrib[nPos,2] := nAttr
Return .t.

Function GetAttrib(chr)
Local nPos
chr := Upper(chr)
nPos := Ascan(saSetAttrib,{|x|x[1]==chr})
If nPos > 0
Return saSetAttrib[nPos,2]
Return -1

Added SuperLib s_foml.prg

Ron Pinkas
FUNCTION SwpRunCmd( cCommand, nuMem, cRunPath, cTempPath )

LOCAL nAt, cArgs, cPresetDisk, cPresetDir, nRet

#ifdef __PLATFORM__Windows
#define DIR_SEPARATOR '\'
#define DIR_SEPARATOR '/'

IF Empty( cRunPath )
nAt := At( ' ', cCommand )

IF nAt > 0
cArgs := SubStr( cCommand, nAt )
cCommand := Left( cCommand, nAt - 1 )

nAt := RAt( DIR_SEPARATOR, cCommand )

IF nAt > 0
cRunPath := Left( cCommand, nAt )
cCommand := SubStr( cCommand, nAt + 1 ) + cArgs

IF cRunPath[2] == ':'
cPresetDisk := DiskName()

IF ! DiskChange( cRunPath[1] )
RETURN Throw( ErrorNew( "Blinker", 0, 1001, ProcName(), "Could not
switch to drive: " + cRunPath[1], HB_aParams() ) )

cRunPath := SubStr( cRunPath, 3 )

IF ! Empty( cRunPath )
cPresetDir := DIR_SEPARATOR + CurDir()

IF DirChange( cRunPath ) != 0
RETURN Throw( ErrorNew( "Blinker", 0, 1002, ProcName(), "Could not
switch to folder: " + cRunPath, HB_aParams() ) )

__Run( cCommand, @nRet )
SwpErrLev( nRet )

IF ! Empty( cPresetDisk )
IF ! DiskChange( cPresetDisk )
RETURN Throw( ErrorNew( "Blinker", 0, 1003, ProcName(), "Could not
switch back to drive: " + cPresetDisk, HB_aParams() ) )

IF ! Empty( cPresetDir )
IF DirChange( cPresetDisk ) != 0
RETURN Throw( ErrorNew( "Blinker", 0, 1004, ProcName(), "Could not
switch back to folder: " + cPresetDir, HB_aParams() ) )

RETURN nRet == 0

FUNCTION SwpErrLev( nLev )

STATIC s_nLastLev := 0

IF ValType( nLev ) == 'N'
s_nLastLev := nLev

RETURN s_nLastLev

Michael Hagl :

V32ShellExecute(<>,[ cVerb ],[ cParam ],[ cDir ],[ cState ],[
lModal ] )

<> is a character string containing the path and file name to be
executed with its associated program. If this parameter is
no action is taken.
[ cVerb ] is the flag what type of action the associated program has to
Default is 'open'. Windows defines default verbs as 'open',

[ cParam ] a character string representing parameters to be passed to
associated program. Default is empty string.

[ cDir ] optional directory path where the associated program searches for
the required components.

[ cState ] can be on of the these: Hide, Normal, Minimized, Maximized, Show.
Default is 'Normal'. This determines the state of associated
program window.

[ lModal ] logical to determine whether the invoked program becomes the
child process of the current application or the program flow
return to application immediately after invoking the associated
program. Default is FALSE means program flow returns immediately.

Function V32ShellExecute(cFile,cAction,cParam,cDir,cState,lModal)
Local nShowCmd
Default cAction To "open"
Default cParam To ""
Default cDir To ""
Default cState To "NORMAL"
If Upper(cState) == "MINIMIZED"
nShowCmd := 2
Elseif Upper(cState) == "MAXIMIZED"
nShowCmd := 3
Elseif Upper(cState) == "NOACTIVATE"
nShowCmd := 4
Elseif Upper(cState) == "SHOW"
nShowCmd := 5
Elseif Upper(cState) == "SHOWMINNOACTIVE"
nShowCmd := 7
Elseif Upper(cState) == "SHOWNA"
nShowCmd := 8
Elseif Upper(cState) == "SHOWDEFAULT"
nShowCmd := 10
nShowCmd := 1
Return .f.

Michael Hagl :

Function Attrtoa(nAttr)
Local r := NtoColor(nAttr,.t.)
Return r

Solved by removing obsolete DOS functions

Solved by removing obsolete DOS functions

Solved by removing obsolete DOS functions

Solved by removing obsolete DOS functions

Solved by removing obsolete DOS functions

Solved by Michael Hagl :

Function xEncrypt(cStr,cKey)
Return cStr ^^ cKey
//Function is from Klas Engwall ?
// but there is a Problem, when cKey is only @
Function Encrypt(cString,cKey)
local i,cNewString := ""
i := int((len(cString)/len(cKey))+1)
cKey := replicate(cKey,i)
for i := 1 to len(cString)
cNewString += chr(numxor(asc(substr(cString,i,1)),;
Return cNewString

Made this redundant using Phil Barnet Random()

Replaced with INKEY(0)

Phew, having got rid of the unresolved external references, FAS.EXE is now a text-based Win32 Apps ! Now to test... Results in the next article.


CCH: Remove redundant PRG containing CATERR function call

CCH: Changed MyQuery() to Superlib Smalls()

CCH: Changed MyQuery() to Superlib Smalls()


CCH/David Smith:
LOCAL cPort:="LPT"+str(nPort,1)+":"
RETURN ISPRINTER(PrinterPortToName(cPort))

More to come...

Migrating from CA-Clipper 5.2e to xHarbour (Part I)

I am going to attempt a migration of a Clipper 5.2e DOS application for Financial Accounting to a Win32 version using xHarbour. BTW, I had already moved this product to Win32 via Delphi 5 in 1997 :-)

Step 1

Test compile using Clipper 5.2e via blinkfas.rmk
Test link using Blinker5 via link script blinkfas.lnk

Status : FAS.EXE created

Nb. This is to verify that the 5.2e project can indeed be built

Step 2

Launch xHarbour Project Builder

Step 3

a) Update Project Root Folder

b) Update Target Name

Step 4

a) Select Tab 2

b) Update Main Source File

Step 5

a) Select Tab 3

b) Add Project *.prg

c) Replace *.prg from xHarbour-compatible SuperLib

Step 6

a) Click Build

Result :

Fas_main.prg, Line 5, can't open include file

Solution : Block temp with //

b) Click Build

Result :

Unresolved external symbols

More to come...

Using Messagebox() Effectively

Q. Is there any way to show an icon such as for Information, warning etc in the following sample call :-
MessageBox(, "Message", "Title")

A. By Marcos Gambeta (marcosgambeta at



From MSDN:

To display an icon in the message box, specify one of the following values.
  • MB_ICONEXCLAMATION : An exclamation-point icon appears in the message box.
  • MB_ICONWARNING : An exclamation-point icon appears in the message box.
  • MB_ICONINFORMATION : An icon consisting of a lowercase letter i in a circle appears in the message box.
  • MB_ICONASTERISK : An icon consisting of a lowercase letter i in a circle appears in the message box.
  • MB_ICONQUESTION : A question-mark icon appears in the message box.
  • MB_ICONSTOP : A stop-sign icon appears in the message box.
  • MB_ICONERROR : A stop-sign icon appears in the message box.
  • MB_ICONHAND : A stop-sign icon appears in the message box.

What is Visual xHarbour ?

CCH: I am currently using Visual xHarbour to add GUI components to my xHarbour apps.

Welcome to Visual xHarbour!
We at are incredibly excited to announce the availability of the first BETA version of Visual xHarbour, xHarbour Builder's powerful yet easy to use Rapid Application Development (RAD) tool for Windows.

With Visual xHarbour you can start developing true xHarbour Windows applications, with the same ease VB programmers have enjoyed for years. Use visual controls to drag and drop onto your forms, add events handling, change properties, etc., just like in Visual Studio, only now you will be using your favorite development language. Nothing can be easier to build your xHarbour application visually.

This first BETA of Visual xHarbour also includes full support for ActiveX controls. Properties and events of the ActiveX are automatically supported just as easily as native controls. Amazingly easy!

There's so much more to tell but you'd probably prefer seeing it first hand. So, please download the new Visual xHarbour BETA release and try it yourself, we are confident you'll agree it's the tool you've been waiting for, to marry your xHarbour programming skills with the necessities of modern GUI application construction.

Download now!
Curious about VXH? Download your evaluation today!
The demo version has no limitation except for a demo pop-up message when starting the VXH build application.

How to use DBFCDX in a xHarbour Application ?

All my CA-Clipper 5.2e applications use the Comix DBFCDX driver...

After a fair bit of trial & error, I have discovered that the best way to use xHarbour's native DBFCDX is to link in a RDD.sys modified to use


How to add code created by Visual xHarbour to a xHarbour Application [Updated 25th Feb 2009]

As you are probably aware, CA-Clipper codes can readily be recompiled in Xharbour into a W32 executable. In my scenario, I have a complex processing engine originally written in CA-Clipper 5.2e which can then be called within my Delphi applications

I had earlier used CA-Clipper 5.2e +Fivewin to create a W16 executable which was then called via winexec() from my Delphi Application. Being 16-bits meant that NTVDM was called automatically by Windows. What I wanted was a true W32 executable which will not require the usage of NTVDM.

There are 2 scenarios involved
1) Automatic processing without user input
2) Need for user input before automatic processing takes place

For Scenario 1, all I needed was to recompiled in Xharbour to transform my 5.2e CA-Clipper Codes into a 32-bit true windows application. It is that easy :-)

For Scenario 2, I needed user to input a parameter. Problem is that XHarbour does not have a simple Window Dialog function such as Delphi's InputQuery. So what's the solution ?

The Solution

1) Use Visual xHarbour (VXH) to create a windows form with
a) label control named MessageLabel
b) edit control named InputValueEdit
c) button control named OKBtn
d) button control named CancelBtn

2) Saved the Windows Form as


GLOBAL cMessage
GLOBAL cCaption
GLOBAL cInputValue

#include ""
#include "MsgGetForm.xfm"
//---------------------------------------- End of system code ----------------------------------------//

METHOD CancelBtn_OnClick( Sender ) CLASS MsgGetForm
METHOD MsggetForm_Form_OnActivate( Sender ) CLASS MsgGetForm

METHOD OKBtn_OnClick( Sender ) CLASS MsgGetForm
cInputValue:= ::InputValueEdit:Caption

MsgGet_XFM.prg (automatically generated by VXH)

#include ""
//---------------------------------------- End of system code ----------------------------------------//


CLASS __MsgGet INHERIT Application
// Components declaration
// Controls declaration

// Event declaration

METHOD Init( oParent ) CLASS __MsgGet
::Super:Init( oParent )

// Populate Components
// Properties declaration


// Populate Children

//MsgGetForm.xfm (text definition based on the window form created with VHB)
// Components declaration
// Controls declaration

// Event declaration
METHOD MsggetForm_Form_OnActivate()
METHOD OKBtn_OnClick()
METHOD CancelBtn_OnClick()


METHOD Init( oParent ) CLASS MsgGetForm
::Super:Init( oParent )

::EventHandler[ "OnActivate" ] := "MsggetForm_Form_OnActivate"

// Populate Components
// Properties declaration
::Left := 10
::Top := 10
::Width := 534
::Height := 139
::Name := "MsgGetForm"
::Caption := "MsgGetFormCaption"
::ClipChildren := .T.
::ClipSiblings := .T.


// Populate Children
WITH OBJECT ( ::MessageLabel := LABEL( Self ) )
:Left := 41
:Top := 8
:Width := 160
:Height := 16
:Name := "MessageLabel"
:Caption := "MessageLabel"
:Border := .F.
:ClipChildren := .T.
:ClipSiblings := .T.
:TabStop := .F.
END //MessageLabel

WITH OBJECT ( ::InputValueEdit := EDIT( Self ) )
:Left := 41
:Top := 32
:Width := 445
:Height := 22
:Name := "InputValueEdit"
:ClientEdge := .T.
:Border := .F.
:ClipSiblings := .T.
END //InputValueEdit

WITH OBJECT ( ::OKBtn := BUTTON( Self ) )
:Left := 178
:Top := 61
:Width := 77
:Height := 30
:Name := "OKBtn"
:Caption := "&OK"
:Border := .F.
:ClipChildren := .T.
:ClipSiblings := .T.
:EventHandler[ "OnClick" ] := "OKBtn_OnClick"

WITH OBJECT ( ::CancelBtn := BUTTON( Self ) )
:Left := 268
:Top := 62
:Width := 79
:Height := 31
:Name := "CancelBtn"
:Caption := "&Cancel"
:Border := .F.
:ClipChildren := .T.
:ClipSiblings := .T.
:EventHandler[ "OnClick" ] := "CancelBtn_OnClick"
END //CancelBtn



2) Next, I added a wrapper function MsgGet() function in my Main.prg

// function in msggetform.prg
FUNCTION MsgGet(cFormCaption,cFormMessage,cFormInputValue)
// GLOBALs in MsgGetForm.prg
__MsgGet( NIL ):Run( MsgGetForm( NIL ), HB_aParams() )

RETURN cInputValue

3) In the calling prg

cRetVal:=MsgGet('Create Data','Input No. of Working Days', @mNo_of_days)

That's it !

What is my wish list for the xHarbour people ? Provide a standard windows function as part of xHarbour :-)

Comments from David A. Smith , comp.lang.xHarbour
Some options are:
1) make a DLL of your "scenario" code, and call it as an unloadable library. (Not sure if there are limitations on collecting user input in a DLL.)
2) call your "scenario" code via OLE / xBScript
3) do it the way you have been doing it, just translate / recompile.

Tuesday, February 24, 2009

Speedtst.prg by Przemyslaw Czerpak

Here is the speedtst.prg code ( if one is unable to locate somehow ) as pointed to by Przemek as above;

// SpeedTst.prg
* $Id; speedtst.prg 10153 2009-02-03 02;05;45Z druzus $

* Harbour Project source code;
* HVM speed test program
* Copyright 2008 Przemyslaw Czerpak
* www -

#define N_TESTS 54
#define N_LOOPS 1000000
#define ARR_LEN 16

#ifdef __XHARBOUR__
/* By default build xHarbour binaries without MT support
* xHarbour needs separated version for MT and ST mode
* because standard MT functions are not available in
* ST libraries.
#ifndef __ST__
#ifndef __MT__
#ifndef MT
#define __ST__

#command ? => outstd(EOL)
#command ? => outstd(EOL);outstd()

#include ""

#ifdef __HARBOUR__
#define EOL hb_OSNewLine()
#define HB_SYMBOL_UNUSED( symbol ) ( ( symbol ) )
#ifndef __CLIP__
#xtranslate secondsCPU() => seconds()
#ifndef EOL
#define EOL chr(10)

#xcommand _( [] ) => []

#xcommand TEST ;
[ WITH ] ;
[ INIT ] ;
[ EXIT ] ;
[ INFO ] ;
CODE [<*testExp*>] => ;
func ; ;
local time, i, x ;= nil ; ;
[ local ; ] ;
[ ; ] ;
time ;= secondscpu() ; ;
for i;=1 to N_LOOPS ; ;
[;] ;
next ; ;
time ;= secondscpu() - time ; ;
[ ; ] ;
return { procname() + "; " + iif( <.info.>, <(info)>, # ), time }

proc main( ... )
local aParams, nMT, cExclude, lScale, cParam, cMemTests, lSyntax, i

aParams ;= hb_aparams()
lSyntax ;= lScale ;= .f.
cMemTests ;= "029 030 023 025 027 040 041 043 052 053 019 022 031 032 054 "
cExclude ;= ""
nMT ;= 0
for each cParam in aParams
cParam ;= lower( cParam )
if cParam = "--thread"
if substr( cParam, 9, 1 ) == "="
if isdigit( substr( cParam, 10, 1 ) )
nMT ;= val( substr( cParam, 10 ) )
elseif substr( cParam, 10 ) == "all"
nMT ;= -1
lSyntax = .t.
elseif empty( substr( cParam, 9 ) )
nMT ;= -1
lSyntax = .t.
elseif cParam = "--exclude="
if substr( cParam, 11 ) == "mem"
cExclude += cMemTests
cExclude += strtran( strtran( strtran( substr( cParam, 11 ), ;
".", " " ), ".", " " ), "/", " " ) + " "
elseif cParam = "--only="
cExclude ;= ""
if substr( cParam, 8 ) == "mem"
cParam ;= cMemTests
for i ;= 1 to N_TESTS
if !strzero( i, 3 ) $ cParam
cExclude += strzero( i, 3 ) + " "
elseif cParam = "--scale"
lScale ;= .t.
lSyntax = .t.
if lSyntax
? "Unknown option;", cParam
? "syntax;", hb_argv( 0 ), "[--thread[=]] [--only=] [--exclude=]"
test( nMT, cExclude, lScale )

#ifdef __XHARBOUR__

#xtranslate hb_mtvm() => hb_multiThread()
#xtranslate hb_threadWaitForAll() => WaitForThreads()
#xtranslate hb_mutexNotify() => Notify()

#ifndef __ST__

/* do not expect that this code will work with xHarbour.
* xHarbour has many race conditions which are exploited quite fast
* on real multi CPU machines so it crashes in different places ;-(
* probably this code should be forwared to xHarbour developers as
* some type of MT test

/* this define is only for test if emulation function works
* without running real test which causes that xHarbour crashes
//#define _DUMY_XHB_TEST_

function hb_mutexSubscribe( mtx, nTimeOut, xSubscribed )
local lSubscribed
if valtype( nTimeOut ) == "N"
nTimeOut ;= round( nTimeOut * 1000, 0 )
xSubscribed ;= Subscribe( mtx, nTimeOut, @lSubscribed )
xSubscribed ;= Subscribe( mtx )
lSubscribed ;= .t.
return lSubscribed

/* in xHarbour there is race condition in JoinThread() which
* fails if thread end before we call it so we cannot use it ;-(
* this code tries to simulate it and also add support for thread
* return value

function hb_threadStart( ... )
local thId
thId ;= StartThread( @threadFirstFunc(), hb_aParams() )
/* Just like in JoinThread() the same race condition exists in
* GetThreadId() so we will use HVM thread numbers internally
#ifdef _DUMY_XHB_TEST_
return val( substr( hb_aParams()[1], 2 ) )
return GetThreadId( thId )

function hb_threadJoin( thId, xResult )
xResult ;= results( thId )
return .t.

static function threadFirstFunc( aParams )
local xResult
#ifdef _DUMY_XHB_TEST_
xResult ;= { "skipped test " + aParams[1], val( substr( aParams[1], 2 ) ) + 0.99 }
results( val( substr( aParams[1], 2 ) ), xResult )
xResult ;= hb_execFromArray( aParams )
results( GetThreadId(), xResult )
return nil

static function results( nThread, xResult )
static s_aResults
static s_mutex
if s_aResults == nil
s_aResults ;= HSetAutoAdd( hash(), .t. )
s_mutex ;= hb_mutexCreate()
if pcount() < s_mutex ="=" xoncecontrol ="=" xoncecontrol ="=">= s

TEST t036 WITH a ;= array( ARR_LEN ), s ;= dtos( date() ) ;
INIT aeval( a, { |x,i| a[i] ;= left( s + s, i ), x } ) ;
CODE x ;= a[ i % ARR_LEN + 1 ] <= s TEST t037 WITH a ;= array( ARR_LEN ), s ;= dtos( date() ) ; INIT aeval( a, { |x,i| a[i] ;= left( s + s, i ), x } ) ; CODE x ;= a[ i % ARR_LEN + 1 ] <> s

TEST t039 WITH a ;= array( ARR_LEN ) ;
INIT aeval( a, { |x,i| a[i] ;= i, x } ) ;
CODE ascan( a, i % ARR_LEN )

TEST t040 WITH a ;= array( ARR_LEN ) ;
INIT aeval( a, { |x,i| a[i] ;= i, x } ) ;
CODE ascan( a, { |x| x == i % ARR_LEN } )

TEST t041 WITH a ;= {}, a2 ;= { 1, 2, 3 }, bc ;= { |x| f1(x) }, ;
s ;= dtos( date() ), s2 ;= "static text" ;
CODE if i%1000==0;a;={};end; aadd(a,{i,1,.t.,s,s2,a2,bc})

TEST t042 WITH a ;= {} CODE x ;= a

TEST t043 CODE x ;= {}

TEST t044 CODE f0()

TEST t045 CODE f1( i )

TEST t046 WITH c ;= dtos( date() ) ;
INFO f2( c[1...8] ) ;
CODE f2( c )

TEST t047 WITH c ;= repl( dtos( date() ), 5000 ) ;
INFO f2( c[1...40000] ) ;
CODE f2( c )

TEST t048 WITH c ;= repl( dtos( date() ), 5000 ) ;
INFO f2( @c[1...40000] ) ;
CODE f2( c )

TEST t049 WITH c ;= repl( dtos( date() ),5000 ), c2 ;
INFO "f2( @c[1...40000] ), c2 ;= c" ;
CODE f2( @c ); c2 ;= c

TEST t050 WITH a ;= {}, a2 ;= { 1, 2, 3 }, bc ;= { |x| f1(x) }, ;
s ;= dtos( date() ), s2 ;= "static text", n ;= 1.23 ;
CODE f3( a, a2, s, i, s2, bc, i, n, x )

TEST t051 WITH a ;= { 1, 2, 3 } CODE f2( a )

TEST t052 CODE x ;= f4()

TEST t053 CODE x ;= f5()

TEST t054 WITH c ;= dtos( date() ) CODE f_prv( c )

function thTest( mtxJobs, aResults )
local xJob
while .T.
hb_mutexSubscribe( mtxJobs,, @xJob )
if xJob == NIL
aResults[ xJob ] ;= &( "t" + strzero( xJob, 3 ) )()
return nil

function thTestScale( mtxJobs, mtxResults )
local xJob
while .T.
hb_mutexSubscribe( mtxJobs,, @xJob )
if xJob == NIL
hb_mutexNotify( mtxResults, &( "t" + strzero( xJob, 3 ) )() )
return nil

proc test( nMT, cExclude, lScale )
local nLoopOverHead, nTimes, nSeconds, cNum, aThreads, aResults, ;
mtxJobs, mtxResults, nTimeST, nTimeMT, nTimeTotST, nTimeTotMT, ;
cTest, x, i, j


#ifdef __HARBOUR__
#include ""
? "Warning !!! Memory statistic enabled."

//? "Startup loop to increase CPU clock..."
//x ;= seconds() + 5; while x > seconds(); enddo

#ifdef __HARBOUR__
if !hb_mtvm()
if lScale
? "scale test available only in MULTI THREAD mode"
if nMT != 0
? "SINGLE THREAD mode, number of threads set to 0"
nMT ;= 0
? date(), time(), os()
? version() + iif( hb_mtvm(), " (MT)" + iif( nMT != 0, "+", "" ), "" ), ;
? date(), time(), os()
? version()
if lScale .and. nMT <>" + ltrim( str( N_TESTS ) ), ltrim( str( nMT ) ) )
? "N_LOOPS;", ltrim( str( N_LOOPS ) )
if !empty( cExclude )
? "excluded tests;", cExclude

x ;=t000()
nLoopOverHead ;= x[2]

if lScale
? space(56) + "1 th." + str(nMT,3) + " th. factor"
? replicate("=",76)
? dsp_result( x, 0 )
? replicate("=",68)

nSeconds ;= seconds()
nTimes ;= secondsCPU()

#ifdef __HARBOUR__
if lScale
mtxJobs ;= hb_mutexCreate()
mtxResults ;= hb_mutexCreate()
nTimeTotST ;= nTimeTotMT ;= 0
for i;=1 to nMT
hb_threadStart( "thTestScale", mtxJobs, mtxResults )
for i;=1 to N_TESTS
cTest ;= strzero( i, 3 )
if !cTest $ cExclude

/* linear execution */
nTimeST ;= seconds()
for j;=1 to nMT
hb_mutexNotify( mtxJobs, i )
hb_mutexSubscribe( mtxResults,, @x )
cTest ;= x[1]
nTimeST ;= seconds() - nTimeST
nTimeTotST += nTimeST

/* simultaneous execution */
nTimeMT ;= seconds()
for j;=1 to nMT
hb_mutexNotify( mtxJobs, i )
for j;=1 to nMT
hb_mutexSubscribe( mtxResults,, @x )
cTest ;= x[1]
nTimeMT ;= seconds() - nTimeMT
nTimeTotMT += nTimeMT

? dsp_scaleResult( cTest, nTimeST, nTimeMT, nMT, nLoopOverHead )

for i;=1 to nMT
hb_mutexNotify( mtxJobs, NIL )
elseif nMT <> 0
aResults ;= array( N_TESTS )
mtxJobs ;= hb_mutexCreate()
for i;=1 to nMT
hb_threadStart( "thTest", mtxJobs, aResults )
for i;=1 to N_TESTS
if !strzero( i, 3 ) $ cExclude
hb_mutexNotify( mtxJobs, i )
for i;=1 to nMT
hb_mutexNotify( mtxJobs, NIL )
for i;=1 to N_TESTS
if aResults[ i ] != NIL
? dsp_result( aResults[ i ], nLoopOverHead )
mtxJobs ;= NIL
for i;=1 to N_TESTS
cNum ;= strzero( i, 3 )
if !cNum $ cExclude
? dsp_result( &( "t" + cNum )(), nLoopOverHead )
for i;=1 to N_TESTS
cNum ;= strzero( i, 3 )
if !cNum $ cExclude
? dsp_result( &( "t" + cNum )(), nLoopOverHead )

nTimes ;= secondsCPU() - nTimes
nSeconds ;= seconds() - nSeconds

if lScale
? replicate("=",76)
? dsp_scaleResult( " TOTAL ", nTimeTotST, nTimeTotMT, nMT, 0 )
? replicate("=",76)
? replicate("=",68)
? dsp_result( { "total application time;", nTimes }, 0)
? dsp_result( { "total real time;", nSeconds }, 0 )


function f0()
return nil

function f1(x)
return x

function f2(x)
return nil

function f3(a,b,c,d,e,f,g,h,i)
return nil

function f4()
return space(4000)

function f5()
return space(5)

function f_prv(x)
memvar PRV_C
private PRV_C ;= x
return nil

function f_pub(x)
memvar PUB_C
public PUB_C ;= x
return nil

function f_stat(x)
static STAT_C
STAT_C ;= x
return nil

static func dsp_result( aResult, nLoopOverHead )
return padr( "[ " + left( aResult[1], 56 ) + " ]", 60, "." ) + ;
strtran( str( max( aResult[2] - nLoopOverHead, 0 ), 8, 2 ), " ", "." )

static func dsp_scaleResult( cTest, nTimeST, nTimeMT, nMT, nLoopOverHead )
if .f.
nTimeST ;= max( 0, nTimeST - nMT * nLoopOverHead )
nTimeMT ;= max( 0, nTimeMT - nMT * nLoopOverHead )
return padr( "[ " + left( cTest, 50 ) + " ]", 54, "_" ) + ;
str( nTimeST, 6, 2 ) + " " + str( nTimeMT, 6, 2 ) + " ->" + ;
str( nTimeST / nTimeMT, 6, 2 )

#define TMP_FILE "_tst_tmp.dbf"
static proc create_db()
dbcreate( TMP_FILE, { {"F_C", "C", 10, 0},;
{"F_N", "N", 10, 2},;
{"F_D", "D", 8, 0} } )
use TMP_FILE exclusive
replace F_C with dtos(date())
replace F_N with 112345.67
replace F_D with date()

static proc remove_db()
ferase( TMP_FILE )

static proc close_db()

static proc use_dbsh()
use TMP_FILE shared

Multi-threading Issue: Response by Harbour's Przemek’s to Alaska's Steffen

Pritpal Bedi :
I think that whole MT issue can be discussed from two angles;

1) From TECHNICAL point of view.
2) From DEVELOPER (End-Users) point of view.

As I am not thoroughly equipped with technical part, I present hereunder Przemek’s ( one who implemented MT in Harbour ) response which he posted on Harbour’s Dev-List.


Xbase++ Steffen :
Sorry to use your blog to comment on the comments of others, as I think this is not the idea of a blog. Anyway please allow me to clarify my statement. I said "clean and easy to use way of multithreading", i didn’t say the Harbours don’t support Multithreading.

My statement is still true, even so Harbour and xHarbour have implemented the ability to execute code in multiple threads and have implemented some of the interfaces Xbase++ provides in one way or another. They are still far away from the idea and concepts of Xbase++ in that area.

In addition Harbour and xHarbour implemented different semantics of isolation. Which makes porting of complex MT applications for sure a mess. Let me clarify that.

It’s not true.

1-st Harbour and xHarbour use different MT implementation and they should not be confused.

In Harbour it’s possible to use threads like in xbase++. It support thread and signal classes/objects, the concept of zero zone for open workareas (dbRequest()/dbRelease()), SYNC object methods, isolated SET and RDD settings, coping or sharing memvars with parent threads. Also xbase++ thread related functions like ThreadID(), ThreadObject(), ThreadWait(), ThreadWaitAll() are supported.

It’s highly possible that I haven’t replicated xbase++ behavior exactly (f.e. the implementation of THREAD class should be extended yet to add support for thread restarting when thread interval is set but it’s rather simple .prg code and I hope that xbase++ users which are interested in exact emulation will make it. I’m not xbase++ user so I cannot easy test the details of implementation.

Harbour does not support thread priority but it’s not multiplatform and portable feature so it cannot be well implemented. Anyhow in few lines it can be added for those platforms which are supported by xbase++.

But in Harbour you can also use other things which does not exists in xbase++. The very important is also scalability which is far much better then in xbase++ or in xHarbour so if you have multi CPU machine you can expect that the speed of MT application will be noticeable improved.

The mutexes in Harbour give very flexible synchronization mechanism. They can be used as message queues, conditional variables or simple mutexes. PRIVATE and PUBLIC sharing or coping is optional and controlled by user. It’s possible to allocate many console windows in single thread or in many threads. Console windows can be shared between threads or can be dedicated to single thread only.

Harbour supports THREAD STATIC variables and they are used in core code. It means that Clipper’s code which need static variables like getlist implementation is MT safe in Harbour. It also gives very powerful mechanism for MT programmers. There are also many other things related to MT programming which seems to be unique to Harbour and does not exist in xbase++.

In summary Harbour offers xbase++ compatible MT API but rather as optional feature for programmers because it provides own more powerful and flexible one and the final applications are much better scalable.

Xbase++ Steffen
Multithreading as the ability to execute code in different code paths is a feature of modern OS sinces decades.

The problem with MT is that it adds another dimension of complexity to the developers task. While with single threaded apps. the developer needs only to think in a more or less sequential way with MT each execution path adds a new dimentions to the equation of programm complexity.

Development languages supporting MT such as Delphi, .NET (C#,VB) or Harbour and xHarbour support MT thats correct, but they do not remove the burden of correctness from the programmer. It is in the sole responsibility of the programmer to ensure programm correctness in two different areas; data-consistency and algorithm isolation.

I agree,

Xbase++ Steffen
The problem of data consistency occurs as soon as more than one thread is accessing the same data - such as a simple string or an array.

Besides nuances in terms of single or multiple readers/writers the consistency of the data must be ensured, so developers are forced to use mutex-semaphores or other higher level concepts such monitors, guards... to ensure data-consistency.

Yes, usually they are though different languages gives some additional protection mechanisms here so not always is necessary to use user level synchronization.

Xbase++ Steffen
Algorithm isolation is somewhat related to data-consistency, it becomes obvious that a linked-list accessed from multiple threads must be protected otherwise dangling pointer occurs. But what about a table/relation of a database.

The problem here is that concurrency inside the process can be resolved - but this type of "isolation" does break the semantics of the isolation principles which are already provided by the underlying dbms (sql-isolation-levels, record or file locks, transactions). Therefore algorithm isolation/correctness is a complete different beast as it is located at a very high semantic level of the task.

yes, it is.

Xbase++ Steffen
Alaska Software has put an enormous amount of research efforts into that area and we have more than a decade of practical experience with that area based on real world customers and real world applications.

From that point of view I would like to reiterate my initial statement "As of today there is still no tool available in the market which provides that clean and easy to use way of multithreading".

I was not making such "enormous amount of research efforts" ;-) Just simply looked at good balance between performance, basic protection and flexibility for programmers.

Xbase++ Steffen
Lets start with xHarbour, its MT implementation is not well thought, as it provides MT features to the programmer without any model, just the features. xHarbour even allows the usage of a workarea from different threads which is a violation of fundamental dbms isolation principles.

In fact xHarbour is just a system language in the sense of MT and makes life not really easier compared with other system languages. Therefore there is no value in besides being able to do MT. Also keep in mind due to the historical burden of the VM and RT core the MT feature is implemented in a way making it impossible to scale in future multi-core scenarios (see later-note).

I agree. Giving the unprotected access to workareas is asking for a troubles. It can create very serious problems (f.e. data corruption in tables) and gives nothing for programmers because they have to use own protection mechanisms to access the tables so final application have to be reduced to the same level as using dbRequest()/dbRelease() to lock/unlock the table. The difference is only that in such model programmer has to implement everything itself.

Xbase++ Steffen
Harbour is better here because if follows more the principles of Xbase++, while I am not sure if the Harbour people have decided to adapt the Xbase++ model for compatibility reasons or not I am glad to see that they followed our models point of view.

The issues with Harbour however is that it suffers from the shortcoming of its runtime in general, the VM design and of course the way how datatypes - the blood of a language - are handled. It is still in a 1980 architectual style centered around the original concept how Clipper did it.

This is also true for xHarbour, so both suffer from the fact that MT was added I think in 2007, while the VM and RT core is from 1999 - without having MT in mind.

Here I can agree only partially.

1-st Harbour does not follow xbase++ model. With the exception to xbase++ emulation level (xbase++ sync and thread classes, thread functions and sync methods) the whole code is the result of my own ideas.

The only one idea I partially borrowed is dbRequest()/dbRelase() semantic. Personally I wanted to introduce many workarea holders (not only single zero area zone) and dbDetach()/dbAttach() functions.

Later I heard about xbase++ implementation and I’ve found the cargo codeblock attaching as very nice feature so I implemented it but internally it operates on workarea sets from my original idea and still it’s possible to introduce support for multiple WA zones if we decide to add .prg level API for it. In some cases it maybe usable. Also the internal WA isolation in native RDDs is different. For POSIX systems it’s necessary to introduce file handle sharing and this mechanism is already used so now we can easy extended it adding support for pseudo exclusive mode (other threads will be able to access tables open in exclusive mode which is exclusive only for external programs) or add common to aliased WA caches. Of course Harbour supports also other xbase++ extensions but they were added rather for compatibility with xbase++ on xbase++ users and internally use basic Harbour MT API.

2-nd this old API from 1980 is a real problem in some places and probably will be good to change it. But I also do not find the xbase++ API as the only one final solution. Harbour gives full protection for read access to complex items. User have to protect only write access and only if he will want to change exactly the same item not complex item member, f.e. this code;

aVal[ threadID() ] += aVal[ threadID() ] * 2 + 100

is MT safe in Harbour even if the same aVal is used by many different threads.

Important is the fact that each thread operates on different aVal items and aVal is not resized. Otherwise it may cause data corruption. But when complex items can be resized the we usually need additional protection also in xbase++ because user code makes many operations which have to be atomic in some logical sense so in most of cases there is only one difference here between Harbour and xbase++; in xbase++ with full internal protection and missing user protection RT error is generated.

In Harbour it may cause internal data corruption. I agree here that it’s very important difference but in mouse of such cases we are talking about wrong user code which needs additional user protection in both languages. And here we have one fundamental question;

What is the cost of internal protection for scalability?

and if we can or cannot accept it. My personal feeling is that the cost will be high, even very high but I haven’t made any tests myself though some xbase++ users confirmed that it’s a problem in xbase++. I’m really interested in some scalability tests of xbase++ and Harbour. It could give few very important answers. If some xbase++ user can port tests/speedtst.prg to xbase++ then it will be very helpful.

Of course it’s possible that I missed something here but I’ve never used xbase++ and I cannot see its source code so I only guess how some things are implemented in this language.

Xbase++ Steffen
This is in fact one of the biggest differences between Xbase++ and the "Harbours" from a pure architectual point of view, we designed a runtime architecture from the beginning to be MT/MP and Distributed, they designed a runtime based on the DOS Clipper blueprint.

In fact, I could argue on and on, specifically it it comes to dedicated implementations of the Harbour runtime core or the Harbour VM but sharing these type of technical details is of course definitively not what I am paid for -;) Anyway allow me to make it clear in a general terms.


See above. It’s not such clear as you said.

I think that you will find users which can say that the cost of scalability is definitively not what they be paid for. Especially when the missing user protection is also problem for xbase++ and the bad results are only different.

For sure RT error is much better then internal data corruption but how much users can paid for such functionality.

Xbase++ Steffen
First, any feature/functionality of Xbase++ is reentrant there is not a single exception of this rule.

Second, any datatype and storage type is thread-safe regardless of its complexity so there is no way to crash an Xbase++ process using multithreading.

Third, the runtime guarantees that there is no possibility of a deadlock in terms of its internal state regardless what you are doing in different threads. There is a clean isolation and inheritance relationship of settings between different threads.

In practical terms that means, you can output to the console from different threads without any additional code, you can execute methods or access state of GUI (XbasePARTS) objects from different threads, you can create a codeblock which detaches a local variable and pass it to another thread, you are performing file I/O or executing a remote procedure call and in the meanwhile the async. garbagge collector cleans up your memory - and the list goes on...

But in Xbase++ you can do all that without the need to think about MT or ask a question such as "Is the ASort() function thread safe" or can I change the caption of a GUI control from another thread. Thats all a given, no restrictions apply, the runtime does it all automatically for you.


Most of the above is also true in Harbour with the exception to missing GUI components and obligatory internal item storage protection. But it’s the subject of efficiency discussed above.

Let’s make some scalability tests and we can decide if we want to pay the same cost of xbase++ users.

Xbase++ Steffen
Anyway, I like Harbour more than xHarbour in terms of MT support.

However the crux is still there, no real architecture around the product, leading to the fact that MT is supported form a technical point of view but not from a 4GL therefore leading to a potential of unnecessary burden for the average programmers, and of course that was and is still not the idea of Clipper as a tool.


The only one fundamental difference between Harbour and xbase++ in the above is obligatory internal items protection. At least visible for me now and as I said the cost of such functionality may not be acceptable for users. But let’s make some real tests to see how big problem it creates in real life.

Xbase++ Steffen
Btw, the same is true for VO or so, they left the idea of the language and moved to something more like a system -language, while I can understand that somewhat I strongly disagree with that type of language design for a simple reasons; its not practical in the long term - we will see that in the following years as more and more multi core system will find their way in the mainstream and developers need to make use of them for performance and scaleability reasons.

In 10 - 15 years from now we will have 100 if not thousands cores per die - handling multithreading , synchronisation issues by hand becomes then impossible, the same is true for offloading tasks for performance reasons.

So there is a need for a clean model in terms of the language - thats at least into what we believe at Alaska Software. It goes even further, the current attempty by MS in terms of multicore support with VS2010 or NET 4.0 are IMO absolutely wrong, as they force the developer to write code depending on the underlaying execution infrastructure alias cores available.

In other words, infrastructure related code/algorithms get mixed with the original algorithm the developers writes and of course the developer gets payed for. Thats a catastrophic path which for sure does not contribute to increased productivity and reliability of software solutions.


I agree with you only partially. Over some reasonable cost limit the MT programing stops to be usable and is much more efficient, safer and easier to use separated processes.

The cost of data exchanging between them will be simply smaller the cost of internal obligatory MT synchronization. So why to use MT mode? For marketing reasons?

Xbase++ Steffen
Funnily enough, the most critical, and most difficult aspect in that area; getting performance gains from multi core usage is even not touched with my technical arguments right now.

However it adds another dimension of complexity to the previous equation as it needs to take into account the memory hierarchy which must be handled by a 4GL runtime totally different as it is with the simple approach of Harbour/xHarbour. Their RT core and VM needs a more or less complete rewrite and redesign to go that path


I do not see bigger problems with Harbour core code modifications. If we decide that it’s worth then I’ll implement it.

Probably the real problem will be forcing different API to 3-rd party developers. Here we probably should chose something close to xbase++ C API to not introduce additional problems for 3-rd party developers which have to create code for both projects to have some basic ompatibility f.e. at C preprocessor level.

Anyhow I’m still not sure I want to pay for the cost of full item access serialization.

Xbase++ Steffen
In other words, Xbase++ is playing in the Multithreading ballpark since a decade.

Harbour is still finding its way into the MT ballpark while xHarbour is in that context at a dead-end.

I would bet that Xbase++ will play in the multicore ballpack while the Harbours are still with their MT stuff.


And it’s highly possible that it will happen. But Harbour is free project and if we decide that adding full item protection with the cost of speed is valuable feature then maybe we implement it.

It’s also possible that we add such functionality as alternative VM library. Just like now we have hbvm and hbvmmt we will have hbvmpmt (protected mt).

Xbase++ Steffen
In a more theoretical sense, it is important to understand that a programming language and its infrastructure shall not adapt any technical feature, requirement or hype. Because then the language and infrastucture are getting more and more complicated up to an point of lost control.

Also backward compatibility and therefore protection of existing investments becomes more and more a mess with Q&A costs going through the roof.


_FULLY_AGREE_. Things should be as simple as possible. Any hacks or workarounds for single features in longer terms create serious problems and blocks farther developing. For me it was the main of xHarbour problem when I was working on this project.

Xbase++ Steffen
Nor is it a good idea to provide software developers any freedom - the point here is, a good MT modell does smoothly guide the developer through the hurdels and most of the time is even not in the awareness of the developer.

The contrary is providing the developer all freedom, but this leads to letting him first build the gun-powder, then the gun to finally shoot a bullet -;)



Xbase++ Steffen
Therefore let me rephrase my initial statement to be more specific; As of today there is still no tool available in the market which provides that clean and easy to use way of multithreading, however there are other tools which support MT - but they support it just as an technical feature without a modell and thats simple wrong as it leads to additional dimensions in code complexity - finally ending in applications with lesser reliability and overall quality. Just my point of view on that subject - enough said


Thank you very much for this very interesting text. I hope that now the main internal difference between Harbour and xbase++ is well visible for users. To the above we should add yet tests/speedtst.prg results to compare scalability so we will know the real cost which is important part of the above description. I’m very interesting in real life results and I hope that some xbase++ users will port tests/speedtst.prg to xbase++ so we can compare the results.

Pritpal Bedi :
So, for those who were eager to understand underlying concepts of MT and how it is woven in these products, must be feeling at ease with above discussion. Believe me, me also found it very rewarding.

I will turn-up to it some other time.

Pritpal Bedi

Welcome to Clipper... Clipper... Clipper

In 1997, then using Delphi 3, I had already created 32-bits Windows applications for HRIS, ERP and CRM. In 2007, using Ruby on Rails, an AJAX powered CRM site running on Apache & MySQL was created and I am now using Visual Studio .Net 2008 to create web-based projects and Delphi 7 for Win32 applications using SQL2005 & DBFCDX.

So, why then am I reviving the Original Clipper... Clipper... Clipper via a Blog as CA-Clipper is a programming language for the DOS world ? Believe it or not, there are still some clients using my mission-critical CA-Clipper applications for DOS installed in the late 80's and up to the mid 90's. This is testimony to CA-Clipper's robustness as a language :-)

With the widespread introduction of Windows 7 64-bits as the standard O/S for new Windows based PCs & Notebooks, CA-Clipper EXE simply will not work and it has become imperative for Clipper programmers to migrate immediately to Harbour to build 32/64 bits EXEs

Since 28th January 2009, this blog has been read by 134,389 (10/3/11 - 39,277) unique visitors (of which 45,151 (10/3/11 - 13,929) are returning visitors) from 103 countries and 1,574 cities & towns in Europe (37; 764 cities), North America (3; 373 cities) , Central America & Caribeans (6; 13 cities), South America(10; 226 cities), Africa & Middle-East (12; 44 cities) , Asia-Pacific (21; 175 cities). So, obviously Clipper is Alive & Well : -)

TIA & Enjoy ! (10th October 2012, 11:05; 13th November 2015)

Original Welcome Page for Clipper... Clipper... Clipper

This is the original Welcome Page for Clipper... Clipper... Clipper, which I am republishing for historical and sentimental reasons. The only changes that I have made was to fix all the broken links. BTW, the counter from is still working :-)

Welcome to Chee Chong Hwa's Malaysian WWW web site which is dedicated to Clipperheads throughout the world.

This site started out as a teeny-weeny section of Who the heck is Chee Chong Hwa ? and has graduated into a full blown web site of more than 140 pages (actually hundreds of A4 size pages) ! This is due to its growing popularity and tremendous encouragements from visiting Clipperheads from 100 countries worldwide, from North America, Central America, Caribbean, South America, Europe, Middle-East, Africa and Asia-Pacific. Thanx Clipperheads, you all made this happen !

What is Clipper ?

You may ask, what is this Clipper stuff ? Could Clipper be something to do with sailing as it is the name of a very fast sailing American ship in the 19th century ?

Well, Clipper or to be precise, CA-Clipper is the premier PC-Software development tool for DOS. It was first developed by Nantucket Corporation initially as a compiler for dBase3+ programs. Since then, CA-Clipper has evolved away from its x-base roots with the introduction of lexical scoping & pre-defined objects like TBrowse. As at today, the most stable version ofClipper is 5.2e while the latest version, 5.3a was introduced on 21 May 1996.

As at 11th November, 1996, an unofficial 5.3a fixes file was made available by Jo French. See the About CA-Clipper 5.3a section for more details. BTW, Jo French uploaded the revised 5.3a fixes file on 20th November, 1996.

Latest News

The latest news is that CA has finally released the long-awaited 5.3b patch on 21 May, 1997.

For 5.3b users, you must a take a look at Jo French's comments on unfixed bugs in 5.3b.

BTW, have you used Click ? If you're a serious Clipperprogrammer and need an excellent code formatter, Click is a natural choice. How to get it ? Simple, access Phil Barnett's site via my Cool Clipper Sites.

32-bits Clipper for Windows ?

Have you tried Xbase ++ ? Well, I have and compared to Delphi (my current Windows programming tool of choice), I'm still sticking to Delphi.

Anyway, you should visit the Alaska Home Page. Give it a chance and then draw your own conclusions !.

The Harbour Project

Is this the future of Xbase ? Take a look at at the Harbour Project

You are Visitor # ...

According to, you are visitor since 3 June 1996.

If you like or dislike what you see on this website, please drop me a line by clicking the email button at the bottom of this page or better still, by filling out the form in my guest book. If you are not sure what to write,click here to take a look at what other Clipperheads have to say.