home *** CD-ROM | disk | FTP | other *** search
/ Piper's Pit BBS/FTP: ibm 0010 - 0019 / ibm0010-0019 / ibm0010.tar / ibm0010 / CLIPB52.ZIP / SPENCE.ZIP / C.TXT next >
Encoding:
Text File  |  1990-06-04  |  43.1 KB  |  1,508 lines

  1. Rick Spence's advanced C Presentation for 1990 Devcon
  2. =====================================================
  3.  
  4. This presentation assumes the attendees are familiar with:
  5.  
  6.     ■ Clipper
  7.     ■ C
  8.     ■ Clipper's extend / extor system.
  9.  
  10. We cover the following subjects:
  11.  
  12.     ■ Calling philosophy
  13.         How to structure your C routines to allow access both from
  14.         C and Assembler. This is important if you work in both
  15.         languages. There is no need to duplicate your work if you
  16.         structure the functions correctly.
  17.  
  18.     ■ Documenting Clipper callable C routines
  19.         Clipper callable C routines are inherently less readable
  20.         than their C equivalent. C routines are defined with a formal
  21.         parameter list and return value; Clipper callable C routines
  22.         are not. This section proposes a documentation standard for
  23.         these Clipper callable routines.
  24.  
  25.     ■ When to use C rather than Clipper
  26.         C is not a panacea, but neither is Clipper. This section discuss
  27.         in what circumstances C is preferable to  Clipper. For each case
  28.         we present detailed examples.
  29.  
  30.  
  31. Calling Philosophy
  32. ------------------
  33.  
  34. If you develop both in C and Clipper, you should write utility
  35. functions so they are callable from either. As you know, Clipper
  36. callable C routines get their parameters from Clipper by calling one
  37. of the _par routines. C routines, on the other hand, receive their
  38. parameters on the stack. Clipper routines return their result with
  39. a _ret function, whereas C routines return them in machine registers.
  40. This means you cannot call the same routine from both C and Clipper.
  41.  
  42.  
  43. What you can do though, is isolate the Clipper specific C code.
  44. In practice, this means retrieving parameters, and returning results,
  45. so what you can do is write the Clipper callable C function as
  46. a "front-end" to the real C function. The C function does the real
  47. work; it receives parameters on the stack and returns a value in
  48. machine registers. All the Clipper function does is get the parameters
  49. from Clipper, call the C function, then return the result to Clipper.
  50. This way you can call the C routine directly from other C routines.
  51.  
  52.  
  53. As an example, consider a communications function which sends a character
  54. to the serial port, and returns a logical. The real function prototype
  55. is:
  56.  
  57.     int _send_ch(int, int);   /* takes com. port# and char to send */
  58.                               /* Returns 0 - false, !0 true */
  59.  
  60. The Clipper callable routine would then be written as:
  61.  
  62.     CLIPPER send_ch()
  63.  
  64.     {
  65.         _retl(_send_ch(_parni(1), _parni(2)));
  66.     }
  67.  
  68. The function's only job is to translate between Clipper's method of
  69. passing parameters and returning values and C's.
  70.  
  71.  
  72. If you write your Clipper callable functions this way, they will usually
  73. be one line functions. If you need to start updating values passed by
  74. reference though (with the _storxx functions), they will be slightly
  75. longer.
  76.  
  77.  
  78. Documenting Clipper callable C functions
  79. ----------------------------------------
  80.  
  81. Reading Clipper callable C routines is difficult because they do not have
  82. a formal parameter list; this makes it difficult to ascertain parameters'
  83. types. The same applies to return values.
  84.  
  85.  
  86. To overcome this, you should explicitly document parameter types and
  87. function return values. You do this with a comment before each
  88. function. Use the following keywords before the formal parameter
  89. name to define Clipper types:
  90.  
  91.     ■ CHARACTER
  92.     ■ DATE
  93.     ■ NUMERIC
  94.     ■ LOGICAL
  95.  
  96. To indicate that a parameter must be passed by reference, append an
  97. asterisk (pointer dereference operator in C) to the type specifier, as in:
  98.  
  99.     NUMERIC *attribute
  100.  
  101. This indicates you must pass a numeric parameter by reference.
  102.  
  103. To indicate an array must be passed, append the size to the name
  104. in square brackets, as in:
  105.  
  106.     NUMERIC test[10]
  107.  
  108. This indicates the parameter must be a numeric array with 10 elements.
  109. If you need to pass an array whose elements have different types, document
  110. this in a comment following the declaration.
  111.  
  112.  
  113. As a detailed example, we show below the comment for the splitpath
  114. function discussed later:
  115.  
  116.     /***
  117.     * VOID splitpath(path, drive, dir, fname, ext)
  118.     *
  119.     * CHARACTER path                   - The path to split
  120.     * CHARACTER *drive                 - The drive letter
  121.     * CHARACTER *dir                   - The directory path
  122.     * CHARACTER *fname                 - The file name
  123.     * CHARACTER *ext                   - The file extension
  124.     *
  125.     * The function splits the passed path name into its constituent
  126.     * parts. Each part is passed by reference, and must be defined
  127.     * to at least these sizes:
  128.     *
  129.     *     _MAX_DRIVE   3       max. length of drive component
  130.     *     MAX_DIR     13       max. length of path component
  131.     *     _MAX_FNAME   9       max. length of file name component
  132.     *     _MAX_EXT     5       max. length of extension component
  133.     */
  134.  
  135. The first section defines the parameters, the second a brief
  136. comment on usage and parameter sizes. The parameters drive, dir, fname,
  137. and ext must be passed by reference.
  138.  
  139.  
  140. When To Use C
  141. -------------
  142.  
  143. There are certain situations where you just cannot do what you need
  144. with pure Clipper code. Examples are:
  145.  
  146.     ■ Accessing pre-defined C or assembly routines, such as Microsoft
  147.       C library functions, or third-party C libraries.
  148.  
  149.     ■ Interfacing to software interrupt handlers, such as MS-DOS, the
  150.       BIOS, and the MOUSE.
  151.  
  152.     ■ Performing bit manipulation.
  153.  
  154.     ■ Manipulating I/O ports.
  155.  
  156.     ■ Directly accessing machine addresses (such as accessing the BIOS data
  157.       area to manipulate the cursor shape).
  158.  
  159.  
  160. There are other cases where you could write the same code in Clipper,
  161. but you want the efficiency or economy of representation afforded by C.
  162.  
  163. Examples are:
  164.  
  165.     ■ String processing
  166.  
  167.     ■ Implementing dynamic data structures, such as queues and linked-
  168.       lists.
  169.  
  170. The remainder of this presentation presents a number of examples.
  171. We start by showing how to access a number of Microsoft Library functions,
  172. followed by a general-purpose interrupt caller. We finish by implementing
  173. a SET data structure.
  174.  
  175.  
  176.  
  177. Calling Microsoft LIB routines
  178. ------------------------------
  179.  
  180. To use Microsoft library routines, you simply include the header
  181. file which defines the function's prototype (the Microsoft documentation
  182. defines which are needed), and call the function.
  183.  
  184.  
  185. Program 9 - 1 shows two example functions. cdos_getfi gets the file
  186. attribute of the passed file name, cdos_setfi sets it. They both return
  187. an integer indicating whether they succeeded; 0 means it succeeded, non-
  188. zero is the error code. cdos_getfi returns the attribute in a parameter
  189. which you must pass by reference.
  190.  
  191.  
  192.     #include "nandef.h"
  193.     #include "extend.h"
  194.  
  195.     #include <dos.h>
  196.  
  197.     /***
  198.     * NUMERIC cdos_getfi(path, attribute)
  199.     *
  200.     * CHARACTER path            Full path 
  201.     * NUMERIC   *attributes     Numeric to store attributes in. Must be
  202.     *                           passed by reference
  203.     *
  204.     * Get a file or directory attributes. Return in attribute (must pass it
  205.     * by reference). Function returns 0 if ok, otherwise the error code.
  206.     */
  207.  
  208.     CLIPPER cdos_getfi()
  209.  
  210.     {
  211.         unsigned attributes, errcode;
  212.  
  213.         errcode = _dos_getfileattr(_parc(1), &attributes);
  214.         _storni(attributes, 2);
  215.         _retni(errcode);
  216.     }
  217.  
  218.  
  219.     /***
  220.     * NUMERIC cdos_setfi(path, attribute)
  221.     *
  222.     * CHARACTER path            Full path
  223.     * NUMERIC   attribute       New attributes
  224.     *
  225.     * Set a file or directory attribute. Function returns 0 if ok,
  226.     * otherwise the error code.
  227.     */
  228.  
  229.     CLIPPER cdos_setfi()
  230.  
  231.     {
  232.         _retni(_dos_setfileattr(_parc(1), _parni(2)));
  233.     }
  234.  
  235.         Program 9 - 1 Getting and Setting File Attributes
  236.  
  237.  
  238. Attributes are represented as individual bits in a byte. The following
  239. bit values are defined for C:
  240.  
  241.  
  242.     #define _A_NORMAL       0x00    /* Normal file - No read/write
  243.                                        restrictions */
  244.     #define _A_RDONLY       0x01    /* Read only file */
  245.     #define _A_HIDDEN       0x02    /* Hidden file */
  246.     #define _A_SYSTEM       0x04    /* System file */
  247.     #define _A_VOLID        0x08    /* Volume ID file */
  248.     #define _A_SUBDIR       0x10    /* Subdirectory */
  249.     #define _A_ARCH         0x20    /* Archive file */
  250.  
  251.  
  252. Because Clipper does not support hexadecimal notation, we write these in
  253. Clipper as:
  254.  
  255.     #define _A_NORMAL         00    /* Normal file - No read/write
  256.                                        restrictions */
  257.     #define _A_RDONLY         01    /* Read only file */
  258.     #define _A_HIDDEN         02    /* Hidden file */
  259.     #define _A_SYSTEM         04    /* System file */
  260.     #define _A_VOLID          08    /* Volume ID file */
  261.     #define _A_SUBDIR         16    /* Subdirectory */
  262.     #define _A_ARCH           32    /* Archive file */
  263.  
  264.  
  265. A hidden, read-only file for example has the value 3, a subdirectory
  266. 16.
  267.  
  268.  
  269. To make the file customer.dbf hidden, you write:
  270.  
  271.     #define ATTR_HIDDEN 2
  272.  
  273.     cdos_setfi("customer.dbf", _A_HIDDEN)
  274.  
  275.  
  276. Program 9 - 2 shows three functions that work with directories:
  277.  
  278.     ■ cchdir  - Change directory
  279.  
  280.     ■ cmkdir  - Make directory
  281.  
  282.     ■ crmdir  - Remove a directory
  283.  
  284.  
  285. Each returns a zero if it succeeded, or the DOS error-code if it failed.
  286.  
  287.  
  288.     #include "nandef.h"
  289.     #include "extend.h"
  290.  
  291.     #include <dos.h>
  292.  
  293.     /***
  294.     * NUMERIC cchdir(path)
  295.     *
  296.     * CHARACTER path            Path name of directory to change to
  297.     *
  298.     * Change current working directory to the directory
  299.     * specified by path. Function returns 0 if OK, otherwise
  300.     * the error code.
  301.     */
  302.  
  303.     CLIPPER cchdir()
  304.  
  305.     {
  306.         _retni(chdir(_parc(1)));
  307.     }
  308.  
  309.  
  310.     /***
  311.     * NUMERIC cmkdir(path)
  312.     *
  313.     * CHARACTER path
  314.     *
  315.     * Make a new directory with the specified path.
  316.     *
  317.     * RETURN: 0 if OK, error code otherwise
  318.     */
  319.  
  320.     CLIPPER cmkdir()
  321.  
  322.     {
  323.         _retni(mkdir(_parc(1)));
  324.     }
  325.  
  326.  
  327.     /***
  328.     * NUMERIC crmdir(path)
  329.     *
  330.     * CHARACTER path
  331.     *
  332.     * Delete the directory specified by path.
  333.     *
  334.     * RETURN: 0 if OK, -1 if error
  335.     */
  336.  
  337.     CLIPPER crmdir()
  338.  
  339.     {
  340.         _retni(rmdir(_parc(1)));
  341.     }
  342.  
  343.  
  344.         Program 9 - 2  Making, Changing, and Removing Directories
  345.  
  346.  
  347. As you can see, each is simply a one-line function that gets the
  348. parameters from Clipper, calls the library routine, then returns the
  349. result.
  350.  
  351.  
  352. Program 9 - 3 shows an interface to the Microsoft random-number
  353. generator. You first call the csrand function with a numeric parameter
  354. (the seed) to initialize it, then repeatedly call crand to return the
  355. random numbers. Again, our Clipper callable routines are short.
  356.  
  357.     
  358.     #include "nandef.h"
  359.     #include "extend.h"
  360.  
  361.     /***
  362.     * VOID csrand(seed)
  363.     *
  364.     * NUMERIC seed         - Any value to set the starting point for
  365.     *                        the generator. 1 reinitializes it.
  366.     *
  367.     * Initialize random number generator
  368.     */
  369.  
  370.     CLIPPER csrand()
  371.  
  372.     {
  373.         srand(_parni(1));
  374.         _ret();
  375.     }
  376.  
  377.  
  378.     /***
  379.     * NUMERIC crand()
  380.     *
  381.     * RETURN : A random number in the range 0 to 32767
  382.     */
  383.  
  384.     CLIPPER crand()
  385.  
  386.     {
  387.         _retni(rand());
  388.     }
  389.  
  390.  
  391.         Program 9 - 3  Calling the Random Number Generator
  392.  
  393.  
  394. You will find that a lot of functions you may consider writing in
  395. Clipper are already available in the Microsoft library. An example
  396. is a function to split a file name into its constituent parts.
  397. _splitpath exists in the Microsoft library and works well, so why
  398. reinvent the wheel? Program 9 - 4 shows a Clipper callable C function
  399. that interfaces to it. You have to initialize the drive, directory,
  400. file-name, and extension to their maximum sizes (as defined by Microsoft),
  401. and pass these to _splitpath. Since we need to return four values, the
  402. function does this with parameters passed by reference. We store these
  403. return results back with the _stoxx functions.
  404.  
  405.  
  406.     /***
  407.     * VOID splitpath(path, drive, dir, fname, ext)
  408.     *
  409.     * CHARACTER path                   - The path to split
  410.     * CHARACTER *drive                 - The drive letter
  411.     * CHARACTER *dir                   - The directory path
  412.     * CHARACTER *fname                 - The file name
  413.     * CHARACTER *ext                   - The file extension
  414.     *
  415.     * The function splits the passed path name into its constituent
  416.     * parts. Each part is passed by reference, and must be defined
  417.     * to at least these sizes:
  418.     *
  419.     *     _MAX_DRIVE   3       max. length of drive component
  420.     *     MAX_DIR     13       max. length of path component
  421.     *     _MAX_FNAME   9       max. length of file name component
  422.     *     _MAX_EXT     5       max. length of extension component
  423.     */
  424.  
  425.     #include "stdlib.h"
  426.  
  427.     CLIPPER splitpath()
  428.  
  429.     {
  430.         char *drive, *dir, *fname, *ext;
  431.  
  432.         /* First allocate memory from Clipper's free pool */
  433.         drive = (char *) _exmgrab(_MAX_DRIVE);
  434.         dir = (char *) _exmgrab(_MAX_DIR);
  435.         fname = (char *) _exmgrab(_MAX_FNAME);
  436.         ext = (char *) _exmgrab(_MAX_EXT);
  437.  
  438.         _splitpath(_parc(1), drive, dir, fname, ext);
  439.  
  440.         /* set parameters (they were passed by reference) */
  441.         _storc(drive, 2);
  442.         _storc(dir, 3);
  443.         _storc(fname, 4);
  444.         _storc(ext, 5);
  445.  
  446.         /* Release memory */
  447.         _exmback(drive, _MAX_DRIVE);
  448.         _exmback(dir, _MAX_DIR);
  449.         _exmback(fname, _MAX_FNAME);
  450.         _exmback(ext, _MAX_EXT);
  451.  
  452.         _ret();
  453.     }
  454.  
  455.       Program 9 - 4  Splitting a File Name into its Constituent Parts
  456.  
  457.  
  458. As a final example of calling Microsoft Library routines, Program 9 - 5
  459. shows an interface to two string handling functions, strcspn and strspn.
  460. They both take two strings as parameters. strcspn returns the index
  461. into the first string of the first character matching any character from
  462. string2. Note that this is not a substring match, as Clipper's at function
  463. is. strspn does a similar job except it returns the offset of the first
  464. character NOT in the second string.
  465.  
  466.  
  467.     /***
  468.     * NUMERIC cstrcspn(string1, string2)
  469.     *
  470.     * CHARACTER string1, string2
  471.     *
  472.     * RETURN : Index into string1 of the first character in the set of
  473.     *          characters specified by string2. This is the length of the
  474.     *          initial substring in string1 of characters not in string2.
  475.     */
  476.  
  477.     CLIPPER cstrcspn()
  478.  
  479.     {
  480.         /* Add one to strcspn's result to convert from
  481.            C's 0 based strings to Clipper's 1 based */
  482.         _retni(strcspn(_parc(1), _parc(2)) + 1);
  483.     }
  484.  
  485.  
  486.     /***
  487.     * NUMERIC cstrspn(string1, string2)
  488.     *
  489.     * CHARACTER string1, string2
  490.     *
  491.     * RETURN : Index into string1 of the first character NOT in the set of
  492.     *          characters specified by string2. This is the length of the
  493.     *          initial substring in string1 of characters in string2.
  494.     */
  495.  
  496.     CLIPPER cstrspn()
  497.  
  498.     {
  499.         /* Add one to strspn's result to convert from
  500.            C's 0 based strings to Clipper's 1 based */
  501.         _retni(strspn(_parc(1), _parc(2)) + 1);
  502.     }
  503.  
  504.         Program 9 - 5 Interfacing to Strcspn and Strspn
  505.  
  506.  
  507. These functions are mostly used for parsing. Let's say you have a
  508. character string with words separated by tabs, spaces, carriage-
  509. return / line-feed pairs, and punctuation characters. You want to process
  510. the string a word at a time, ignoring these separators. The strcspn function
  511. is ideal for this. We pass it the list of separators and it returns
  512. where the first one starts. Note that we want to break on ANY of these
  513. characters, so a simple at would not suffice. Neither would the Clipper 
  514. contains operator ($); it only returns whether any of the characters
  515. were contained in the first string. It does not tell us which one, or
  516. where it starts.
  517.  
  518.  
  519. To split a string into tokens then, we would write two Clipper functions
  520. as a front-end. We call a function tok_start to start the search. We
  521. pass it the string we will tokenize and the list of separators.
  522. Next_tok is the second function; you call it to return successive tokens.
  523. The token is returned in a parameter which must have been passed by
  524. reference; the function returns .T. if another token was found, .F.
  525. otherwise.
  526.  
  527.  
  528. Program 9 - 6 shows the functions. They use two "external statics", so
  529. you must compile with the /n option.
  530.  
  531.  
  532.     #define VOID .T.
  533.  
  534.     STATIC _token_string, _separators
  535.  
  536.     FUNCTION tok_start(tok_str, seps)
  537.  
  538.         _token_string = tok_str
  539.         _separators = seps
  540.  
  541.     RETURN VOID
  542.  
  543.  
  544.     FUNCTION next_tok(token)
  545.  
  546.     LOCAL where_sep, ret_val
  547.  
  548.         ret_val = .F.
  549.         IF !(_token_string == "")
  550.             where_sep = cstrcspn(_token_string, _separators)
  551.             DO WHILE where_sep = 1
  552.                 _token_string = substr(_token_string, 2)
  553.                 where_sep = cstrcspn(_token_string, _separators)
  554.             ENDDO
  555.  
  556.             IF where_sep > 1
  557.                 token = substr(_token_string, 1, where_sep - 1)
  558.                 _token_string = substr(_token_string, where_sep + 1)
  559.                 ret_val = .T.
  560.             ENDIF
  561.         ENDIF
  562.  
  563.     RETURN ret_val
  564.  
  565.             Program 9 - 6  Tokenizing Functions
  566.  
  567.  
  568. If you call these functions with:
  569.  
  570.     tok_string = "One of these days Alice" + chr(13) + chr(10) + ;
  571.                  "POW, right in the kisser"
  572.  
  573.     separators = " ,;:" + chr(13) + chr(10)
  574.     tok_start(tok_string, separators)
  575.  
  576.     token = ""
  577.     DO WHILE next_tok(@token)
  578.         ? token
  579.     ENDDO
  580.  
  581. It returns:
  582.  
  583.     One
  584.     of
  585.     these
  586.     .
  587.     .
  588.     .
  589.     kisser
  590.  
  591. as you expect.
  592.  
  593.  
  594. A General Purpose Interrupt Caller
  595. ----------------------------------
  596.  
  597. If you need to call interrupt service routines, such as those provided
  598. by DOS, the BIOS, and the mouse driver, you have two alternatives.
  599. Either you custom code a routine for each service you want, or you
  600. write a general purpose interrupt caller. For obvious reasons, the
  601. second is a better approach.
  602.  
  603.  
  604. There is a function in the Microsoft library, int86. You pass
  605. it the interrupt number you want to call, a pointer to a structure
  606. of input registers, and a pointer to a structure of output registers.
  607. The register structures are a memory equivalent of machine registers.
  608. There is a structure member for AX, BX etc. You set the input registers
  609. to the required values, then call int86. It returns the interrupt
  610. function's return values in a another memory-based register structure.
  611.  
  612.  
  613. We will write a Clipper callable routine, cint86, to interface to int86.
  614. We pass our "registers" as single dimensional arrays; each element
  615. corresponds to a register. We use the following layout:
  616.  
  617.     #define NUM_REGS 7
  618.  
  619.     * Array index of each register. These values are important
  620.     * and must not be changed
  621.  
  622.     #define AX 1
  623.     #define BX 2
  624.     #define CX 3
  625.     #define DX 4
  626.     #define SI 5
  627.     #define DI 6
  628.     #define CFLAG 7
  629.  
  630. These values are important as they match the position of each register
  631. in the register structure used by Mircosoft's int86 function. We use this
  632. to our advantage as you will see.
  633.  
  634. So, we use one array element per machine register. One problem we have
  635. to resolve is that word registers can also be addressed as two byte
  636. registers. The C REGS structure is defined as a union of word and byte
  637. registers so you can assign to either and the C compiler does the work
  638. for you. Clipper programmers are not so fortunate. What we will do is
  639. write compiler macros to set and read the byte values. These are shown
  640. in Program 9 - 7.
  641.  
  642.     #define NUM_REGS 7
  643.  
  644.     * Array index of each register. These values are important
  645.     * and must not be changed
  646.  
  647.     #define AX 1
  648.     #define BX 2
  649.     #define CX 3
  650.     #define DX 4
  651.     #define SI 5
  652.     #define DI 6
  653.     #define CFLAG 7
  654.  
  655.     #define GET_HREG(regs, reg_num) (int(regs[reg_num] / 256))
  656.     #define GET_LREG(regs, reg_num) (int(regs[reg_num] % 256))
  657.  
  658.     * This macro is only split for presentation reasons. It should
  659.     * be on one line
  660.     #define SET_HREG(regs, reg_num, value) regs[reg_num] = ((value) * 256 + ;
  661.                                            GET_LREG(regs, reg_num))
  662.  
  663.     * Same goes for this one
  664.     #define SET_LREG(regs, reg_num, value) regs[reg_num] = ((value) + ;
  665.                                            GET_HREG(regs, reg_num) * 256)
  666.  
  667.         Program 9 - 7 Clipper Register Simulation
  668.  
  669.  
  670. Program 9 - 7 defined four macros:
  671.  
  672.     ■ GET_HREG  - This returns the H register of a given register
  673.                   in the given structure
  674.  
  675.     ■ GET_LREG  - This returns the L register of a given register
  676.                   in the given structure
  677.  
  678.     ■ SET_HREG  - This sets the H register of a given register
  679.                   in the given structure
  680.  
  681.     ■ SET_LREG  - This sets the L register of a given resister
  682.                   in the given structure
  683.  
  684.  
  685. To set AH to 7 in the registers in_regs for example, write:
  686.  
  687.     LOCAL in_regs[REGS_SIZE]
  688.  
  689.     .
  690.     .
  691.     .
  692.  
  693.     SET_HREG(in_regs, AX, 7)
  694.  
  695.  
  696. Program 9 - 8 shows the Clipper callable routine, cint86. You pass
  697. it the interrupt to call, and the input and output arrays. The function
  698. sets each element in REGS from the passed array. This is done in a loop
  699. using a pointer, so we are assuming the order of the passed array is
  700. the same as the REGS structure. That is why the values of the array
  701. index constants are so important!
  702.  
  703.  
  704.     /***
  705.     * VOID cint86(interrupt, in_regs, out_regs)
  706.     *
  707.     * NUMERIC interrupt                - The interrupt to call
  708.     * ARRAY NUMERIC in_regs            - Input Registers
  709.     * ARRAY NUMERIC out_regs           - Output Registers
  710.     *
  711.     * The function calls the specified interrupt with the passed
  712.     * input registers (in_regs). It places the output registers in
  713.     * the passed output registers (out_regs)
  714.     */
  715.  
  716.     #define NUM_REGS sizeof(union REGS)  / sizeof(int)
  717.  
  718.     CLIPPER cint86()
  719.  
  720.     {
  721.         union REGS regs;
  722.         int *ptr_reg;
  723.         int reg_no;
  724.  
  725.         /* First load regs from Clipper's input array */
  726.         ptr_reg = (int *) ®s;
  727.         for (reg_no = 0; reg_no < NUM_REGS; reg_no++)
  728.             ptr_reg[reg_no] = _parni(2, reg_no + 1);
  729.  
  730.         int86(_parni(1), ®s, ®s);
  731.  
  732.         /* Now put results into Clipper's output array */
  733.  
  734.         ptr_reg = (int *) ®s;
  735.         for (reg_no = 0; reg_no < NUM_REGS; reg_no++)
  736.             _storni(ptr_reg[reg_no], 3, reg_no + 1);
  737.  
  738.         _ret();
  739.     }
  740.  
  741.  
  742.         Program 9 - 8  General Purpose Interrupt Caller
  743.  
  744.  
  745. Here are two examples using cint86. The first simply invokes interrupt
  746. 5 to print the screen:
  747.  
  748.     LOCAL regs[MAX_REGS]
  749.  
  750.     FOR i = 1 TO MAX_REGS
  751.         regs[i] = 0
  752.     NEXT
  753.    
  754.     cint86(5, regs, regs)
  755.  
  756.  
  757. We ignore the output registers. The second calls the BIOS video
  758. service routine, 16, to set the cursor to half height. The BIOS requires
  759. AH to be set to 1 (set cursor shape) and the shape to be defined in terms
  760. of starting and ending scan lines in registers CH and CL respectively.
  761.  
  762.  
  763.     LOCAL regs[MAX_REGS]
  764.  
  765.     FOR i = 1 TO MAX_REGS
  766.         regs[i] = 0
  767.     NEXT
  768.  
  769.     SET_HREG(regs, AX, 1)
  770.     SET_HREG(regs, CX, 7)
  771.     SET_LREG(regs, CX, 4)
  772.  
  773.  
  774. The Same Function in Assembler
  775. ------------------------------
  776.  
  777. Although this is the Advanced C class, for the benefit of those who
  778. don't own a C compiler but still want to use these functions, we present
  779. an assembler version of int86. This way you can use the object files
  780. (as supplied Jack ???) without linking the Microsoft library.
  781.  
  782.  
  783. The only problem with writing such a routine in assembler is you can't
  784. write:
  785.  
  786.     INT [memvar]
  787.  
  788. or
  789.  
  790.     INT [reg]
  791.  
  792. to effect the interrupt. You have to hard-code the interrupt number as
  793. a literal, as in:
  794.  
  795.     INT 21h
  796.  
  797. This is not much good for a general purpose routine such
  798. as ours, however, so we need to develop a  fix. What we can do, is assemble
  799. a call to an arbitrary interrupt, then overwrite that interrupt number
  800. with the value we need. Program 9 - 9 shows this. The function
  801. is called _aint86, but it operates identically to Microsoft's int86.
  802.  
  803.  
  804.  
  805.     PUBLIC _aint86                 ; Assembly version of int86
  806.  
  807.     _code  SEGMENT BYTE PUBLIC 'CODE'
  808.  
  809.     ASSUME CS:_code
  810.  
  811.     ; _aint86(int int_num, union REGS *in_regs,  union REGS *out_regs)
  812.  
  813.  
  814.     regs_word STRUC
  815.  
  816.         AX_REG    DW 0
  817.         BX_REG    DW 0
  818.         CX_REG    DW 0
  819.         DX_REG    DW 0
  820.         SI_REG    DW 0
  821.         DI_REG    DW 0
  822.         FLAGS_REG DW 0
  823.  
  824.     regs_word ENDS
  825.  
  826.  
  827.     _aint86 PROC FAR
  828.  
  829.         PUSH    BP
  830.         MOV     BP, SP
  831.  
  832.         PUSH    DS
  833.         PUSH    ES
  834.         PUSH    SI    
  835.         PUSH    DI    
  836.  
  837.         ; get int_num
  838.         MOV     AX, WORD PTR [BP + 06]
  839.  
  840.         ; Write over second byte of INT instruction
  841.         MOV     CS: BYTE PTR int_lab + 1, AL
  842.     
  843.         ; Pointer to input registers.
  844.         LDS     SI, DWORD PTR [BP + 08]
  845.  
  846.         ; load in regs
  847.         MOV     AX, [SI.AX_REG]
  848.         MOV     BX, [SI.BX_REG]
  849.         MOV     CX, [SI.CX_REG]
  850.         MOV     DX, [SI.DX_REG]
  851.         MOV     DI, [SI.DI_REG]
  852.         MOV     SI, [SI.SI_REG]     ; This must be last ...
  853.  
  854.         ; Assemble INT 0, overwrite the actual interrupt vector!
  855.  
  856.     int_lab:
  857.         INT     0
  858.  
  859.         PUSHF
  860.  
  861.         ; Save SI so we can use it to point to the out regs
  862.         PUSH    SI
  863.  
  864.         ; out regs
  865.         LDS     SI, DWORD PTR [BP + 0CH]
  866.  
  867.         MOV     [SI.AX_REG], AX
  868.         MOV     [SI.BX_REG], BX
  869.         MOV     [SI.CX_REG], CX
  870.         MOV     [SI.DX_REG], DX
  871.         MOV     [SI.DI_REG], DI
  872.  
  873.         ; GET saved SI into AX so we can save it back
  874.         POP     AX
  875.         MOV     [SI.SI_REG], AX
  876.  
  877.         ; now pop flags we saved off stack
  878.         POP     AX
  879.         MOV     [SI.FLAGS_REG], AX
  880.  
  881.         ; And finally, we return
  882.         POP     DI
  883.         POP     SI
  884.         POP     ES
  885.         POP     DS
  886.  
  887.         POP     BP
  888.         RET    
  889.  
  890.     _aint86 ENDP
  891.  
  892.     _code ENDS
  893.  
  894.     END
  895.  
  896.  
  897.         Program 9 - 9 Assembly Language Version of int86
  898.  
  899.  
  900. Set Functions
  901. -------------
  902.  
  903. In this final section we implement a new data structure known as a set.
  904. You may recall set-theory from college math's. A set is a collection
  905. of arbitrary elements which you can perform operations on. You can
  906. add elements, delete elements, and test for inclusion
  907. in sets. You can also perform operations on sets as a whole. You can
  908. take the intersection of two sets (AND), and the union of two sets
  909. (OR). You can also take one set away from another (difference).
  910.  
  911.  
  912. Their importance to programming is that they are another data structure
  913. you can work with. Just like arrays and databases, they are another
  914. general purpose data structure you can use as you see fit.
  915.  
  916.  
  917. A good example is to detect loops in directed graphs. Let's say
  918. you have a tracking system where you keep track of where parts are being
  919. moved to and from. You want to detect whether a part has ever been 
  920. around in a circle. You can do this by following the path of the part until
  921. it either comes to an end, or it returns to a location it has already
  922. visited.
  923.  
  924.  
  925. You use a set to detect this in the following way. Every time you "visit"
  926. a new node in the graph (in this case a storage location), add that location
  927. to the set. Before you move to a new node, first check to see if that node
  928. has already been visited by seeing if it is already a member of the set.
  929. If it is, you have detected a loop.
  930.  
  931.  
  932. We will implement the following set of routines to operate on a set:
  933.  
  934.     set_init()  -    Initialize a set of the give size
  935.     set_kill()  -    Release a previously allocated set
  936.     set_add()   -    Add an element to a set
  937.     set_del()   -    Delete an element from a set
  938.     set_in()    -    Check for membership of an element in a set
  939.     set_or()    -    Take the union of two sets returning a third
  940.     set_and()   -    Take the intersection of two sets returning a third
  941.     set_diff()  -    Take the difference of two sets returning a third
  942.     set_first() -    Return the first element of a set
  943.     set_next()  -    Return the next element of a set
  944.  
  945.  
  946. The routines operate using handles. When you initialize a set, it returns
  947. a handle. You use that handle in subsequent operations on the set.
  948.  
  949.  
  950. Since we write our Clipper callable routines just to get parameters
  951. and return results, we can immediately write them, even though we have
  952. not yet decided on a representation. Program 9 - 10 shows the Clipper
  953. callable C routines.
  954.  
  955.  
  956.     /***
  957.     * set.c
  958.     *
  959.     * SET handling functions
  960.     *
  961.     */
  962.  
  963.     #include "nandef.h"
  964.     #include "extend.h"
  965.  
  966.  
  967.     /* Forwards */
  968.     extern void _set_kill(int);
  969.     extern void _set_add(int, int);
  970.     extern int  _set_in(int, int);
  971.     extern void _set_del(int, int);
  972.  
  973.  
  974.     /***
  975.     * NUMERIC set_init(set_size)
  976.     *
  977.     * NUMERIC set_size          - The size of the set in bytes. Each byte
  978.     *                             can store 8 entries.
  979.     *
  980.     * RETURN : A handle for this set for future reference.
  981.     */
  982.  
  983.     CLIPPER set_init()
  984.  
  985.     {
  986.         _retni(_set_init(_parni(1)));
  987.     }
  988.  
  989.  
  990.     /***
  991.     * VOID set_kill(set_handle)
  992.     *
  993.     * NUMERIC set_handle          - The ahndle of the set to delete
  994.     *
  995.     * The function removes this set from its internal structure.
  996.     */
  997.  
  998.     CLIPPER set_kill()
  999.  
  1000.     {
  1001.         _set_kill(_parni(1));
  1002.         _ret();
  1003.     }
  1004.  
  1005.  
  1006.     /***
  1007.     * VOID set_add(set_handle, set_element, ...)
  1008.     *
  1009.     * NUMERIC set_handle          - The handle of the set to add to
  1010.     * NUMERIC set_element         - The element to add to the set
  1011.     *
  1012.     * NOTE : you can pass any number of elements to set_add
  1013.     *
  1014.     * The function adds set_element to the set defined by set_handle.
  1015.     */
  1016.  
  1017.  
  1018.     CLIPPER set_add()
  1019.  
  1020.     {
  1021.         int i;
  1022.  
  1023.         for (i = 2; i <= PCOUNT; i++)
  1024.             _set_add(_parni(1), _parni(i));
  1025.     }
  1026.  
  1027.  
  1028.     /***
  1029.     * VOID set_del(set_handle, set_element, ...)
  1030.     *
  1031.     * NUMERIC set_handle          - The handle of the set to delete from
  1032.     * NUMERIC set_element         - The element to delete from the set
  1033.     *
  1034.     * NOTE : you can pass any number of elements to set_add
  1035.     *
  1036.     * The function deletes set_element from the set defined by set_handle.
  1037.     */
  1038.  
  1039.     CLIPPER set_del()
  1040.  
  1041.     {
  1042.         int i;
  1043.  
  1044.         for (i = 2; i <= PCOUNT; i++)
  1045.             _set_del(_parni(1), _parni(i));
  1046.     }
  1047.  
  1048.  
  1049.     /***
  1050.     * LOGICAL set_in(set_handle, set_element)
  1051.     *
  1052.     * NUMERIC set_handle          - The handle of the set to test
  1053.     * NUMERIC set_element         - The element to test
  1054.     *
  1055.     *
  1056.     * RETURN : The function returns whether set_element is contained in
  1057.     *           the set defined by set_handle.
  1058.     */
  1059.  
  1060.     CLIPPER set_in()
  1061.  
  1062.     {
  1063.         _retl(_set_in(_parni(1), _parni(2)));
  1064.     }
  1065.  
  1066.  
  1067.     /***
  1068.     * NUMERIC set_or(set1_handle, set2_handle)
  1069.     *
  1070.     * NUMERIC set1_handle, set2_handle       - Two set handles
  1071.     *
  1072.     * RETURN : - Set handle of new set constructed from the OR of
  1073.     *            the two sets
  1074.     */
  1075.  
  1076.     CLIPPER set_or()
  1077.  
  1078.     {
  1079.         _retni(_set_or(_parni(1), _parni(2)));
  1080.     }
  1081.  
  1082.  
  1083.     /***
  1084.     * NUMERIC set_and(set1_handle, set2_handle)
  1085.     *
  1086.     * NUMERIC set1_handle, set2_handle     - Two set handles
  1087.     *
  1088.     * RETURN : - Set handle of new set constructed from the AND of
  1089.     *            the two sets
  1090.     */
  1091.  
  1092.     CLIPPER set_and()
  1093.  
  1094.     {
  1095.         _retni(_set_and(_parni(1), _parni(2)));
  1096.     }
  1097.  
  1098.  
  1099.     /***
  1100.     * NUMERIC set_diff(set1_handle, set2_handle)
  1101.     *
  1102.     * NUMERIC set1_handle, set2_handle          - Two set handles
  1103.     *
  1104.     * RETURN : - Set handle of new set constructed from the DIFFERENCE of
  1105.     *            the two sets (i.e. set1_handle - set2_handle)
  1106.     */
  1107.  
  1108.     CLIPPER set_diff()
  1109.  
  1110.     {
  1111.         _retni(_set_diff(_parni(1), _parni(2)));
  1112.     }
  1113.  
  1114.  
  1115.     /***
  1116.     * NUMERIC set_first(set_handle)
  1117.     *
  1118.     * NUMERIC set_handle
  1119.     *
  1120.     * RETURN : - The first element of the set
  1121.     */
  1122.  
  1123.     CLIPPER set_first()
  1124.  
  1125.     {
  1126.         _retni(_set_first(_parni(1)));
  1127.     }
  1128.  
  1129.  
  1130.     /***
  1131.     * NUMERIC set_next(set_handle)
  1132.     *
  1133.     * NUMERIC set_handle
  1134.     *
  1135.     * RETURN : - The next element of the set
  1136.     */
  1137.  
  1138.     CLIPPER set_next()
  1139.  
  1140.     {
  1141.         _retni(_set_next(_parni(1)));
  1142.     }
  1143.  
  1144.  
  1145.         Program 9 - 10   Clipper callable C Set Routines
  1146.  
  1147.  
  1148. We represent the elements of a set by a bit in a byte. If element number
  1149. 1 is in the set, bit number 31 is a 1. If it is not, it is a zero.
  1150. We also need to save some "housekeeping" information for each set;
  1151. specifically, we need to know how big it is, and to implement the sequential
  1152. scan (set_first, set_next) we need to keep track of the last element we
  1153. found.
  1154.  
  1155.  
  1156. As usual in C, we combine these three things into a structure which we
  1157. call a set descriptor. This is what we use to describe, or implement
  1158. a set.
  1159.  
  1160.     /* This is the structure of a set descriptor */
  1161.  
  1162.     typedef struct
  1163.     {
  1164.         char *set;             /* Pointer to the set */
  1165.         unsigned int set_len;  /* The length of this set */
  1166.         unsigned int cur_bit;  /* Used when stepping through a set to
  1167.                                   indicate next position */
  1168.     } SET_DESC;
  1169.  
  1170.  
  1171. We need a set descriptor for each set we will represent. The problem
  1172. is, to define these statically we have to know many there will be. We
  1173. could make this dynamic of course, using a linked-list of set descriptors,
  1174. but for simplicity in this presentation we define a fixed number. We use
  1175. seven in the following examples, but you can simply change the #define
  1176. statement to either increase or decrease this number.
  1177.  
  1178.     #define MAX_SETS 7
  1179.  
  1180.     /* Declare static array of set descriptors */
  1181.     static SET_DESC set_desc[MAX_SETS];
  1182.  
  1183.  
  1184. We now have an array of set descriptors, one for each set. Before we start
  1185. with the code proper, let's define a few constants and macros to make our
  1186. job a little easier:
  1187.  
  1188.     /* number of elements represented by one byte in the set */
  1189.     #define BITS_ELEM 8 
  1190.  
  1191.  
  1192.     /* Determine set byte number given element */
  1193.     #define SET_BYTE_NUM(ELEM_NUM) ELEM_NUM / BITS_ELEM 
  1194.  
  1195.     /* Determine set bit number (within byte) given element */
  1196.     #define SET_BIT_NUM(ELEM_NUM)  ELEM_NUM % BITS_ELEM
  1197.  
  1198.     /* Macro to return a set's size */
  1199.     #define SET_SIZE(set_num) (set_desc[set_num].set_len)
  1200.  
  1201.     /* Macro to return the number of element's that can be represented
  1202.        in a set */
  1203.     #define SET_NUM_ELEMS(set_num) (SET_SIZE(set_num) * BITS_ELEM)
  1204.  
  1205.     /* Return value for sequential scan termination */
  1206.     #define NO_MORE_ELEMS -1
  1207.  
  1208.  
  1209. The macros SET_BYTE_NUM and SET_BIT_NUM determine which bit in which byte
  1210. represents a given element. Figure 9 - 1 shows how the set elements map
  1211. to our memory.
  1212.  
  1213.             Byte 0                   Byte 1
  1214.  
  1215.       7  6  5  4  3  2  1  0│15 14 13 12 11 10  9  8
  1216.     ┌──┬──┬──┬──┬──┬──┬──┬──┼──┬──┬──┬──┬──┬──┬──┬──┐
  1217.     │  │  │  │  │  │  │  │  │  │  │  │  │  │  │  │  │
  1218.     └──┴──┴──┴──┴──┴──┴──┴──┼──┴──┴──┴──┴──┴──┴──┴──┘
  1219.                             │
  1220.  
  1221.  
  1222.         Figure 9 - 1  Set Elements Mapping to Bytes
  1223.  
  1224.  
  1225.  
  1226. Program 9 - 11 shows the implementation of the _set_init and _set_kill
  1227. functions.
  1228.  
  1229.  
  1230.     static int _set_init(set_size)
  1231.  
  1232.     int set_size;
  1233.  
  1234.     {
  1235.         unsigned int set_handle;
  1236.  
  1237.         set_handle = get_set_handle();
  1238.  
  1239.         set_desc[set_handle].set_len = set_size;
  1240.         set_desc[set_handle].cur_bit = 0;
  1241.  
  1242.         set_desc[set_handle].set = malloc(set_size);
  1243.         memset(set_desc[set_handle].set, 0, set_size);
  1244.  
  1245.         return set_handle;
  1246.     }
  1247.  
  1248.  
  1249.     static int get_set_handle()
  1250.  
  1251.     {
  1252.         int set_handle;
  1253.  
  1254.         for (set_handle = 0; set_handle < MAX_SETS &&
  1255.                              set_desc[set_handle].set; set_handle++)
  1256.             ;
  1257.  
  1258.         if (set_handle >= MAX_SETS)
  1259.             /* Fatal error exit ... */
  1260.             exit(1);
  1261.     
  1262.         return (set_handle);
  1263.     }
  1264.  
  1265.  
  1266.     static void _set_kill(set_handle)
  1267.  
  1268.     int set_handle;
  1269.  
  1270.     {
  1271.         free(set_desc[set_handle].set);
  1272.         set_desc[set_handle].set = NULL;
  1273.     }
  1274.  
  1275.  
  1276.         Program 9 - 11  Set Initialize and Release Functions
  1277.  
  1278.  
  1279. _set_init uses the utility routine get_set_handle to find the next free
  1280. unused handle. It hten initilizes the appropriate set descriptor.
  1281. _set_kill does the opposite. It releases the dynamic memory used by
  1282. the set, and resets the pointer to NULL so this descriptor will be
  1283. reused.
  1284.  
  1285.  
  1286. Program 9 - 12 shows the _set_add, _set_del, and _set_in routines.
  1287.  
  1288.  
  1289.     static void _set_add(set_handle, set_element)
  1290.  
  1291.     int set_handle, set_element;
  1292.  
  1293.     {
  1294.        /* OR in the appropriate bit ... */
  1295.        if (set_element < SET_NUM_ELEMS(set_handle))
  1296.            set_desc[set_handle].set[SET_BYTE_NUM(set_element)]
  1297.                                     |= 1 << SET_BIT_NUM(set_element);
  1298.  
  1299.     }
  1300.  
  1301.  
  1302.     static void _set_del(set_handle, set_element)
  1303.  
  1304.     int set_handle, set_element;
  1305.  
  1306.     {
  1307.         /* AND out the appropriate bit ... */
  1308.         if (set_element < SET_NUM_ELEMS(set_handle))
  1309.             set_desc[set_handle].set[SET_BYTE_NUM(set_element)]
  1310.                                     &= ~(1 << SET_BIT_NUM(set_element));
  1311.     }
  1312.  
  1313.  
  1314.     static int _set_in(set_handle, set_element)
  1315.  
  1316.     int set_handle, set_element;
  1317.  
  1318.     {
  1319.         if (set_element < SET_NUM_ELEMS(set_handle))
  1320.             return (set_desc[set_handle].set[SET_BYTE_NUM(set_element)] &
  1321.                     1 << SET_BIT_NUM(set_element));
  1322.     }
  1323.  
  1324.  
  1325.         Program 9 - 12 Set_add, set_del, and set_in Routines
  1326.  
  1327.  
  1328. Note how simple the routines are as a result of defining the macros
  1329. and constants. They do all the work for us!
  1330.  
  1331.  
  1332.  
  1333. Program 9 - 13 shows the set traversal routines, _set_first and
  1334. _set_next. _set_first locates the first element in the set, keeping
  1335. a copy of it in its descriptor (cur_bit). _set_next finds the next
  1336. element, starting from the previous one.
  1337.  
  1338.  
  1339.     static int _set_first(set_handle)
  1340.  
  1341.     int set_handle;
  1342.  
  1343.     {
  1344.         int cur_bit, set_ptr;
  1345.  
  1346.         cur_bit = 0;
  1347.  
  1348.         while (!_set_in(set_handle, cur_bit)
  1349.                && cur_bit < SET_NUM_ELEMS(set_handle))
  1350.             cur_bit++;
  1351.  
  1352.         if (cur_bit != SET_NUM_ELEMS(set_handle))
  1353.         {
  1354.             set_desc[set_handle].cur_bit = cur_bit;
  1355.         }
  1356.  
  1357.         return ((cur_bit == SET_NUM_ELEMS(set_handle))
  1358.                  ? NO_MORE_ELEMS : cur_bit);
  1359.     }
  1360.  
  1361.  
  1362.     static int _set_next(set_handle)
  1363.  
  1364.     int set_handle;
  1365.  
  1366.     {
  1367.         int cur_bit;
  1368.  
  1369.         /* Start search from next element */
  1370.         cur_bit = set_desc[set_handle].cur_bit + 1;
  1371.  
  1372.         while (!_set_in(set_handle, cur_bit) &&
  1373.                cur_bit < SET_NUM_ELEMS(set_handle))
  1374.             cur_bit++;
  1375.  
  1376.         if (cur_bit != SET_NUM_ELEMS(set_handle))
  1377.         {
  1378.             set_desc[set_handle].cur_bit = cur_bit;
  1379.         }
  1380.  
  1381.         return ((cur_bit == SET_NUM_ELEMS(set_handle))
  1382.                  ? NO_MORE_ELEMS : cur_bit);
  1383.     }
  1384.  
  1385.  
  1386.         Program 9 - 13  _set_first and _set_next routines
  1387.  
  1388.  
  1389. The final routines we need to write are _set_or, _set_and, and _set_diff.
  1390. These are relatively starighforward now we have the lower level routines
  1391. in place. They are shown in Program 9 - 14.
  1392.  
  1393.  
  1394.     static int _set_or(set1_handle, set2_handle)
  1395.  
  1396.     int set1_handle, set2_handle;
  1397.  
  1398.     {
  1399.         int new_set_handle, new_set_size, elem_num, smaller_set_size;
  1400.  
  1401.         new_set_size = max(SET_SIZE(set1_handle), SET_SIZE(set2_handle));
  1402.         smaller_set_size = min(SET_SIZE(set1_handle),
  1403.                                SET_SIZE(set2_handle));
  1404.  
  1405.         new_set_handle = _set_init(new_set_size);
  1406.  
  1407.         /* Add any from either set */
  1408.         for (elem_num = 0; elem_num < smaller_set_size * BITS_ELEM;
  1409.              elem_num++)
  1410.             if (_set_in(set1_handle, elem_num) ||
  1411.                 _set_in(set2_handle, elem_num))
  1412.                 _set_add(new_set_handle, elem_num);
  1413.  
  1414.         /* Now add trailing from the longer set */
  1415.         if (smaller_set_size == SET_SIZE(set1_handle))
  1416.             for(; elem_num < new_set_size * BITS_ELEM; elem_num++)
  1417.             {
  1418.                 if (_set_in(set2_handle, elem_num))
  1419.                     _set_add(new_set_handle, elem_num);
  1420.             }
  1421.         else
  1422.             for(; elem_num < new_set_size * BITS_ELEM; elem_num++)
  1423.             {
  1424.                 if (_set_in(set1_handle, elem_num))
  1425.                     _set_add(new_set_handle, elem_num);
  1426.             }
  1427.  
  1428.         return (new_set_handle);
  1429.     }
  1430.  
  1431.  
  1432.     static int _set_and(set1_handle, set2_handle)
  1433.  
  1434.     int set1_handle, set2_handle;
  1435.  
  1436.     {
  1437.         int new_set_handle, new_set_size, elem_num;
  1438.  
  1439.         new_set_size = min(SET_SIZE(set1_handle), SET_SIZE(set2_handle));
  1440.         new_set_handle = _set_init(new_set_size);
  1441.  
  1442.         for (elem_num = 0; elem_num < new_set_size * BITS_ELEM; elem_num++)
  1443.             if (_set_in(set1_handle, elem_num) &&
  1444.                 _set_in(set2_handle, elem_num))
  1445.                 _set_add(new_set_handle, elem_num);
  1446.  
  1447.         return (new_set_handle);
  1448.     }
  1449.  
  1450.  
  1451.     static int _set_diff(set1_handle, set2_handle)
  1452.  
  1453.     int set1_handle, set2_handle;
  1454.  
  1455.     {
  1456.         int new_set_handle, new_set_size, elem_num, smaller_set_size;
  1457.  
  1458.         new_set_size = SET_SIZE(set1_handle);
  1459.         new_set_handle = _set_init(new_set_size);
  1460.  
  1461.         smaller_set_size = min(SET_SIZE(set1_handle),
  1462.                                SET_SIZE(set2_handle));
  1463.  
  1464.         for (elem_num = 0; elem_num < smaller_set_size * BITS_ELEM;
  1465.              elem_num++)
  1466.             if (_set_in(set1_handle, elem_num) &&
  1467.                 !_set_in(set2_handle, elem_num))
  1468.                 _set_add(new_set_handle, elem_num);
  1469.   
  1470.         /* Now add any trailing from the first set */
  1471.         if (smaller_set_size == SET_SIZE(set2_handle))
  1472.             for(; elem_num < new_set_size * BITS_ELEM; elem_num++)
  1473.                 if (_set_in(set1_handle, elem_num))
  1474.                     _set_add(new_set_handle, elem_num);
  1475.  
  1476.         return (new_set_handle);
  1477.     }
  1478.  
  1479.         Program 9 - 14  _set_or, _set_and, and _set_diff routines.
  1480.  
  1481.  
  1482.  
  1483. SUMMARY
  1484. -------
  1485.  
  1486. In this presentation we looked at some more advanced aspects of using
  1487. C with Clipper. To reiterate the main points:
  1488.  
  1489.     ■ Isolate the Clipper specific code to handle parameters and return
  1490.       values in one function. This let's you use other routines as
  1491.       utility functions (consider the use of _set_in in Program 9 - 14
  1492.       for example). It also helps you to protect yourself from any Nantucket
  1493.       changes to the extend or extor system in future CLipper's.
  1494.  
  1495.     ■ Be sure to comment your Clipper callable C routines well. Explicitly
  1496.       document parameter types and return values. 
  1497.  
  1498.     ■ Before you start writing a complex Clipper function, check to see
  1499.       if it's already defined in the Microsoft, or any third-party C
  1500.       library you may own. _split_path is a good example of this, as are
  1501.       chdir, mkdir, and rmdir.
  1502.  
  1503.     ■ Use the general purpose cint86 function to call DOS, BIOS, and any
  1504.       other software that interfaces through a software interrupt vector.
  1505.  
  1506.     ■ Consider implementing other data structures as we did here with SETs.
  1507.       Linked-lists, queues, and stacks would be easy to implement.
  1508.