home *** CD-ROM | disk | FTP | other *** search
Text File | 1990-06-04 | 43.1 KB | 1,508 lines |
- Rick Spence's advanced C Presentation for 1990 Devcon
- =====================================================
-
- This presentation assumes the attendees are familiar with:
-
- ■ Clipper
- ■ C
- ■ Clipper's extend / extor system.
-
- We cover the following subjects:
-
- ■ Calling philosophy
- How to structure your C routines to allow access both from
- C and Assembler. This is important if you work in both
- languages. There is no need to duplicate your work if you
- structure the functions correctly.
-
- ■ Documenting Clipper callable C routines
- Clipper callable C routines are inherently less readable
- than their C equivalent. C routines are defined with a formal
- parameter list and return value; Clipper callable C routines
- are not. This section proposes a documentation standard for
- these Clipper callable routines.
-
- ■ When to use C rather than Clipper
- C is not a panacea, but neither is Clipper. This section discuss
- in what circumstances C is preferable to Clipper. For each case
- we present detailed examples.
-
-
- Calling Philosophy
- ------------------
-
- If you develop both in C and Clipper, you should write utility
- functions so they are callable from either. As you know, Clipper
- callable C routines get their parameters from Clipper by calling one
- of the _par routines. C routines, on the other hand, receive their
- parameters on the stack. Clipper routines return their result with
- a _ret function, whereas C routines return them in machine registers.
- This means you cannot call the same routine from both C and Clipper.
-
-
- What you can do though, is isolate the Clipper specific C code.
- In practice, this means retrieving parameters, and returning results,
- so what you can do is write the Clipper callable C function as
- a "front-end" to the real C function. The C function does the real
- work; it receives parameters on the stack and returns a value in
- machine registers. All the Clipper function does is get the parameters
- from Clipper, call the C function, then return the result to Clipper.
- This way you can call the C routine directly from other C routines.
-
-
- As an example, consider a communications function which sends a character
- to the serial port, and returns a logical. The real function prototype
- is:
-
- int _send_ch(int, int); /* takes com. port# and char to send */
- /* Returns 0 - false, !0 true */
-
- The Clipper callable routine would then be written as:
-
- CLIPPER send_ch()
-
- {
- _retl(_send_ch(_parni(1), _parni(2)));
- }
-
- The function's only job is to translate between Clipper's method of
- passing parameters and returning values and C's.
-
-
- If you write your Clipper callable functions this way, they will usually
- be one line functions. If you need to start updating values passed by
- reference though (with the _storxx functions), they will be slightly
- longer.
-
-
- Documenting Clipper callable C functions
- ----------------------------------------
-
- Reading Clipper callable C routines is difficult because they do not have
- a formal parameter list; this makes it difficult to ascertain parameters'
- types. The same applies to return values.
-
-
- To overcome this, you should explicitly document parameter types and
- function return values. You do this with a comment before each
- function. Use the following keywords before the formal parameter
- name to define Clipper types:
-
- ■ CHARACTER
- ■ DATE
- ■ NUMERIC
- ■ LOGICAL
-
- To indicate that a parameter must be passed by reference, append an
- asterisk (pointer dereference operator in C) to the type specifier, as in:
-
- NUMERIC *attribute
-
- This indicates you must pass a numeric parameter by reference.
-
- To indicate an array must be passed, append the size to the name
- in square brackets, as in:
-
- NUMERIC test[10]
-
- This indicates the parameter must be a numeric array with 10 elements.
- If you need to pass an array whose elements have different types, document
- this in a comment following the declaration.
-
-
- As a detailed example, we show below the comment for the splitpath
- function discussed later:
-
- /***
- * VOID splitpath(path, drive, dir, fname, ext)
- *
- * CHARACTER path - The path to split
- * CHARACTER *drive - The drive letter
- * CHARACTER *dir - The directory path
- * CHARACTER *fname - The file name
- * CHARACTER *ext - The file extension
- *
- * The function splits the passed path name into its constituent
- * parts. Each part is passed by reference, and must be defined
- * to at least these sizes:
- *
- * _MAX_DRIVE 3 max. length of drive component
- * MAX_DIR 13 max. length of path component
- * _MAX_FNAME 9 max. length of file name component
- * _MAX_EXT 5 max. length of extension component
- */
-
- The first section defines the parameters, the second a brief
- comment on usage and parameter sizes. The parameters drive, dir, fname,
- and ext must be passed by reference.
-
-
- When To Use C
- -------------
-
- There are certain situations where you just cannot do what you need
- with pure Clipper code. Examples are:
-
- ■ Accessing pre-defined C or assembly routines, such as Microsoft
- C library functions, or third-party C libraries.
-
- ■ Interfacing to software interrupt handlers, such as MS-DOS, the
- BIOS, and the MOUSE.
-
- ■ Performing bit manipulation.
-
- ■ Manipulating I/O ports.
-
- ■ Directly accessing machine addresses (such as accessing the BIOS data
- area to manipulate the cursor shape).
-
-
- There are other cases where you could write the same code in Clipper,
- but you want the efficiency or economy of representation afforded by C.
-
- Examples are:
-
- ■ String processing
-
- ■ Implementing dynamic data structures, such as queues and linked-
- lists.
-
- The remainder of this presentation presents a number of examples.
- We start by showing how to access a number of Microsoft Library functions,
- followed by a general-purpose interrupt caller. We finish by implementing
- a SET data structure.
-
-
-
- Calling Microsoft LIB routines
- ------------------------------
-
- To use Microsoft library routines, you simply include the header
- file which defines the function's prototype (the Microsoft documentation
- defines which are needed), and call the function.
-
-
- Program 9 - 1 shows two example functions. cdos_getfi gets the file
- attribute of the passed file name, cdos_setfi sets it. They both return
- an integer indicating whether they succeeded; 0 means it succeeded, non-
- zero is the error code. cdos_getfi returns the attribute in a parameter
- which you must pass by reference.
-
-
- #include "nandef.h"
- #include "extend.h"
-
- #include <dos.h>
-
- /***
- * NUMERIC cdos_getfi(path, attribute)
- *
- * CHARACTER path Full path
- * NUMERIC *attributes Numeric to store attributes in. Must be
- * passed by reference
- *
- * Get a file or directory attributes. Return in attribute (must pass it
- * by reference). Function returns 0 if ok, otherwise the error code.
- */
-
- CLIPPER cdos_getfi()
-
- {
- unsigned attributes, errcode;
-
- errcode = _dos_getfileattr(_parc(1), &attributes);
- _storni(attributes, 2);
- _retni(errcode);
- }
-
-
- /***
- * NUMERIC cdos_setfi(path, attribute)
- *
- * CHARACTER path Full path
- * NUMERIC attribute New attributes
- *
- * Set a file or directory attribute. Function returns 0 if ok,
- * otherwise the error code.
- */
-
- CLIPPER cdos_setfi()
-
- {
- _retni(_dos_setfileattr(_parc(1), _parni(2)));
- }
-
- Program 9 - 1 Getting and Setting File Attributes
-
-
- Attributes are represented as individual bits in a byte. The following
- bit values are defined for C:
-
-
- #define _A_NORMAL 0x00 /* Normal file - No read/write
- restrictions */
- #define _A_RDONLY 0x01 /* Read only file */
- #define _A_HIDDEN 0x02 /* Hidden file */
- #define _A_SYSTEM 0x04 /* System file */
- #define _A_VOLID 0x08 /* Volume ID file */
- #define _A_SUBDIR 0x10 /* Subdirectory */
- #define _A_ARCH 0x20 /* Archive file */
-
-
- Because Clipper does not support hexadecimal notation, we write these in
- Clipper as:
-
- #define _A_NORMAL 00 /* Normal file - No read/write
- restrictions */
- #define _A_RDONLY 01 /* Read only file */
- #define _A_HIDDEN 02 /* Hidden file */
- #define _A_SYSTEM 04 /* System file */
- #define _A_VOLID 08 /* Volume ID file */
- #define _A_SUBDIR 16 /* Subdirectory */
- #define _A_ARCH 32 /* Archive file */
-
-
- A hidden, read-only file for example has the value 3, a subdirectory
- 16.
-
-
- To make the file customer.dbf hidden, you write:
-
- #define ATTR_HIDDEN 2
-
- cdos_setfi("customer.dbf", _A_HIDDEN)
-
-
- Program 9 - 2 shows three functions that work with directories:
-
- ■ cchdir - Change directory
-
- ■ cmkdir - Make directory
-
- ■ crmdir - Remove a directory
-
-
- Each returns a zero if it succeeded, or the DOS error-code if it failed.
-
-
- #include "nandef.h"
- #include "extend.h"
-
- #include <dos.h>
-
- /***
- * NUMERIC cchdir(path)
- *
- * CHARACTER path Path name of directory to change to
- *
- * Change current working directory to the directory
- * specified by path. Function returns 0 if OK, otherwise
- * the error code.
- */
-
- CLIPPER cchdir()
-
- {
- _retni(chdir(_parc(1)));
- }
-
-
- /***
- * NUMERIC cmkdir(path)
- *
- * CHARACTER path
- *
- * Make a new directory with the specified path.
- *
- * RETURN: 0 if OK, error code otherwise
- */
-
- CLIPPER cmkdir()
-
- {
- _retni(mkdir(_parc(1)));
- }
-
-
- /***
- * NUMERIC crmdir(path)
- *
- * CHARACTER path
- *
- * Delete the directory specified by path.
- *
- * RETURN: 0 if OK, -1 if error
- */
-
- CLIPPER crmdir()
-
- {
- _retni(rmdir(_parc(1)));
- }
-
-
- Program 9 - 2 Making, Changing, and Removing Directories
-
-
- As you can see, each is simply a one-line function that gets the
- parameters from Clipper, calls the library routine, then returns the
- result.
-
-
- Program 9 - 3 shows an interface to the Microsoft random-number
- generator. You first call the csrand function with a numeric parameter
- (the seed) to initialize it, then repeatedly call crand to return the
- random numbers. Again, our Clipper callable routines are short.
-
-
- #include "nandef.h"
- #include "extend.h"
-
- /***
- * VOID csrand(seed)
- *
- * NUMERIC seed - Any value to set the starting point for
- * the generator. 1 reinitializes it.
- *
- * Initialize random number generator
- */
-
- CLIPPER csrand()
-
- {
- srand(_parni(1));
- _ret();
- }
-
-
- /***
- * NUMERIC crand()
- *
- * RETURN : A random number in the range 0 to 32767
- */
-
- CLIPPER crand()
-
- {
- _retni(rand());
- }
-
-
- Program 9 - 3 Calling the Random Number Generator
-
-
- You will find that a lot of functions you may consider writing in
- Clipper are already available in the Microsoft library. An example
- is a function to split a file name into its constituent parts.
- _splitpath exists in the Microsoft library and works well, so why
- reinvent the wheel? Program 9 - 4 shows a Clipper callable C function
- that interfaces to it. You have to initialize the drive, directory,
- file-name, and extension to their maximum sizes (as defined by Microsoft),
- and pass these to _splitpath. Since we need to return four values, the
- function does this with parameters passed by reference. We store these
- return results back with the _stoxx functions.
-
-
- /***
- * VOID splitpath(path, drive, dir, fname, ext)
- *
- * CHARACTER path - The path to split
- * CHARACTER *drive - The drive letter
- * CHARACTER *dir - The directory path
- * CHARACTER *fname - The file name
- * CHARACTER *ext - The file extension
- *
- * The function splits the passed path name into its constituent
- * parts. Each part is passed by reference, and must be defined
- * to at least these sizes:
- *
- * _MAX_DRIVE 3 max. length of drive component
- * MAX_DIR 13 max. length of path component
- * _MAX_FNAME 9 max. length of file name component
- * _MAX_EXT 5 max. length of extension component
- */
-
- #include "stdlib.h"
-
- CLIPPER splitpath()
-
- {
- char *drive, *dir, *fname, *ext;
-
- /* First allocate memory from Clipper's free pool */
- drive = (char *) _exmgrab(_MAX_DRIVE);
- dir = (char *) _exmgrab(_MAX_DIR);
- fname = (char *) _exmgrab(_MAX_FNAME);
- ext = (char *) _exmgrab(_MAX_EXT);
-
- _splitpath(_parc(1), drive, dir, fname, ext);
-
- /* set parameters (they were passed by reference) */
- _storc(drive, 2);
- _storc(dir, 3);
- _storc(fname, 4);
- _storc(ext, 5);
-
- /* Release memory */
- _exmback(drive, _MAX_DRIVE);
- _exmback(dir, _MAX_DIR);
- _exmback(fname, _MAX_FNAME);
- _exmback(ext, _MAX_EXT);
-
- _ret();
- }
-
- Program 9 - 4 Splitting a File Name into its Constituent Parts
-
-
- As a final example of calling Microsoft Library routines, Program 9 - 5
- shows an interface to two string handling functions, strcspn and strspn.
- They both take two strings as parameters. strcspn returns the index
- into the first string of the first character matching any character from
- string2. Note that this is not a substring match, as Clipper's at function
- is. strspn does a similar job except it returns the offset of the first
- character NOT in the second string.
-
-
- /***
- * NUMERIC cstrcspn(string1, string2)
- *
- * CHARACTER string1, string2
- *
- * RETURN : Index into string1 of the first character in the set of
- * characters specified by string2. This is the length of the
- * initial substring in string1 of characters not in string2.
- */
-
- CLIPPER cstrcspn()
-
- {
- /* Add one to strcspn's result to convert from
- C's 0 based strings to Clipper's 1 based */
- _retni(strcspn(_parc(1), _parc(2)) + 1);
- }
-
-
- /***
- * NUMERIC cstrspn(string1, string2)
- *
- * CHARACTER string1, string2
- *
- * RETURN : Index into string1 of the first character NOT in the set of
- * characters specified by string2. This is the length of the
- * initial substring in string1 of characters in string2.
- */
-
- CLIPPER cstrspn()
-
- {
- /* Add one to strspn's result to convert from
- C's 0 based strings to Clipper's 1 based */
- _retni(strspn(_parc(1), _parc(2)) + 1);
- }
-
- Program 9 - 5 Interfacing to Strcspn and Strspn
-
-
- These functions are mostly used for parsing. Let's say you have a
- character string with words separated by tabs, spaces, carriage-
- return / line-feed pairs, and punctuation characters. You want to process
- the string a word at a time, ignoring these separators. The strcspn function
- is ideal for this. We pass it the list of separators and it returns
- where the first one starts. Note that we want to break on ANY of these
- characters, so a simple at would not suffice. Neither would the Clipper
- contains operator ($); it only returns whether any of the characters
- were contained in the first string. It does not tell us which one, or
- where it starts.
-
-
- To split a string into tokens then, we would write two Clipper functions
- as a front-end. We call a function tok_start to start the search. We
- pass it the string we will tokenize and the list of separators.
- Next_tok is the second function; you call it to return successive tokens.
- The token is returned in a parameter which must have been passed by
- reference; the function returns .T. if another token was found, .F.
- otherwise.
-
-
- Program 9 - 6 shows the functions. They use two "external statics", so
- you must compile with the /n option.
-
-
- #define VOID .T.
-
- STATIC _token_string, _separators
-
- FUNCTION tok_start(tok_str, seps)
-
- _token_string = tok_str
- _separators = seps
-
- RETURN VOID
-
-
- FUNCTION next_tok(token)
-
- LOCAL where_sep, ret_val
-
- ret_val = .F.
- IF !(_token_string == "")
- where_sep = cstrcspn(_token_string, _separators)
- DO WHILE where_sep = 1
- _token_string = substr(_token_string, 2)
- where_sep = cstrcspn(_token_string, _separators)
- ENDDO
-
- IF where_sep > 1
- token = substr(_token_string, 1, where_sep - 1)
- _token_string = substr(_token_string, where_sep + 1)
- ret_val = .T.
- ENDIF
- ENDIF
-
- RETURN ret_val
-
- Program 9 - 6 Tokenizing Functions
-
-
- If you call these functions with:
-
- tok_string = "One of these days Alice" + chr(13) + chr(10) + ;
- "POW, right in the kisser"
-
- separators = " ,;:" + chr(13) + chr(10)
- tok_start(tok_string, separators)
-
- token = ""
- DO WHILE next_tok(@token)
- ? token
- ENDDO
-
- It returns:
-
- One
- of
- these
- .
- .
- .
- kisser
-
- as you expect.
-
-
- A General Purpose Interrupt Caller
- ----------------------------------
-
- If you need to call interrupt service routines, such as those provided
- by DOS, the BIOS, and the mouse driver, you have two alternatives.
- Either you custom code a routine for each service you want, or you
- write a general purpose interrupt caller. For obvious reasons, the
- second is a better approach.
-
-
- There is a function in the Microsoft library, int86. You pass
- it the interrupt number you want to call, a pointer to a structure
- of input registers, and a pointer to a structure of output registers.
- The register structures are a memory equivalent of machine registers.
- There is a structure member for AX, BX etc. You set the input registers
- to the required values, then call int86. It returns the interrupt
- function's return values in a another memory-based register structure.
-
-
- We will write a Clipper callable routine, cint86, to interface to int86.
- We pass our "registers" as single dimensional arrays; each element
- corresponds to a register. We use the following layout:
-
- #define NUM_REGS 7
-
- * Array index of each register. These values are important
- * and must not be changed
-
- #define AX 1
- #define BX 2
- #define CX 3
- #define DX 4
- #define SI 5
- #define DI 6
- #define CFLAG 7
-
- These values are important as they match the position of each register
- in the register structure used by Mircosoft's int86 function. We use this
- to our advantage as you will see.
-
- So, we use one array element per machine register. One problem we have
- to resolve is that word registers can also be addressed as two byte
- registers. The C REGS structure is defined as a union of word and byte
- registers so you can assign to either and the C compiler does the work
- for you. Clipper programmers are not so fortunate. What we will do is
- write compiler macros to set and read the byte values. These are shown
- in Program 9 - 7.
-
- #define NUM_REGS 7
-
- * Array index of each register. These values are important
- * and must not be changed
-
- #define AX 1
- #define BX 2
- #define CX 3
- #define DX 4
- #define SI 5
- #define DI 6
- #define CFLAG 7
-
- #define GET_HREG(regs, reg_num) (int(regs[reg_num] / 256))
- #define GET_LREG(regs, reg_num) (int(regs[reg_num] % 256))
-
- * This macro is only split for presentation reasons. It should
- * be on one line
- #define SET_HREG(regs, reg_num, value) regs[reg_num] = ((value) * 256 + ;
- GET_LREG(regs, reg_num))
-
- * Same goes for this one
- #define SET_LREG(regs, reg_num, value) regs[reg_num] = ((value) + ;
- GET_HREG(regs, reg_num) * 256)
-
- Program 9 - 7 Clipper Register Simulation
-
-
- Program 9 - 7 defined four macros:
-
- ■ GET_HREG - This returns the H register of a given register
- in the given structure
-
- ■ GET_LREG - This returns the L register of a given register
- in the given structure
-
- ■ SET_HREG - This sets the H register of a given register
- in the given structure
-
- ■ SET_LREG - This sets the L register of a given resister
- in the given structure
-
-
- To set AH to 7 in the registers in_regs for example, write:
-
- LOCAL in_regs[REGS_SIZE]
-
- .
- .
- .
-
- SET_HREG(in_regs, AX, 7)
-
-
- Program 9 - 8 shows the Clipper callable routine, cint86. You pass
- it the interrupt to call, and the input and output arrays. The function
- sets each element in REGS from the passed array. This is done in a loop
- using a pointer, so we are assuming the order of the passed array is
- the same as the REGS structure. That is why the values of the array
- index constants are so important!
-
-
- /***
- * VOID cint86(interrupt, in_regs, out_regs)
- *
- * NUMERIC interrupt - The interrupt to call
- * ARRAY NUMERIC in_regs - Input Registers
- * ARRAY NUMERIC out_regs - Output Registers
- *
- * The function calls the specified interrupt with the passed
- * input registers (in_regs). It places the output registers in
- * the passed output registers (out_regs)
- */
-
- #define NUM_REGS sizeof(union REGS) / sizeof(int)
-
- CLIPPER cint86()
-
- {
- union REGS regs;
- int *ptr_reg;
- int reg_no;
-
- /* First load regs from Clipper's input array */
- ptr_reg = (int *) ®s;
- for (reg_no = 0; reg_no < NUM_REGS; reg_no++)
- ptr_reg[reg_no] = _parni(2, reg_no + 1);
-
- int86(_parni(1), ®s, ®s);
-
- /* Now put results into Clipper's output array */
-
- ptr_reg = (int *) ®s;
- for (reg_no = 0; reg_no < NUM_REGS; reg_no++)
- _storni(ptr_reg[reg_no], 3, reg_no + 1);
-
- _ret();
- }
-
-
- Program 9 - 8 General Purpose Interrupt Caller
-
-
- Here are two examples using cint86. The first simply invokes interrupt
- 5 to print the screen:
-
- LOCAL regs[MAX_REGS]
-
- FOR i = 1 TO MAX_REGS
- regs[i] = 0
- NEXT
-
- cint86(5, regs, regs)
-
-
- We ignore the output registers. The second calls the BIOS video
- service routine, 16, to set the cursor to half height. The BIOS requires
- AH to be set to 1 (set cursor shape) and the shape to be defined in terms
- of starting and ending scan lines in registers CH and CL respectively.
-
-
- LOCAL regs[MAX_REGS]
-
- FOR i = 1 TO MAX_REGS
- regs[i] = 0
- NEXT
-
- SET_HREG(regs, AX, 1)
- SET_HREG(regs, CX, 7)
- SET_LREG(regs, CX, 4)
-
-
- The Same Function in Assembler
- ------------------------------
-
- Although this is the Advanced C class, for the benefit of those who
- don't own a C compiler but still want to use these functions, we present
- an assembler version of int86. This way you can use the object files
- (as supplied Jack ???) without linking the Microsoft library.
-
-
- The only problem with writing such a routine in assembler is you can't
- write:
-
- INT [memvar]
-
- or
-
- INT [reg]
-
- to effect the interrupt. You have to hard-code the interrupt number as
- a literal, as in:
-
- INT 21h
-
- This is not much good for a general purpose routine such
- as ours, however, so we need to develop a fix. What we can do, is assemble
- a call to an arbitrary interrupt, then overwrite that interrupt number
- with the value we need. Program 9 - 9 shows this. The function
- is called _aint86, but it operates identically to Microsoft's int86.
-
-
-
- PUBLIC _aint86 ; Assembly version of int86
-
- _code SEGMENT BYTE PUBLIC 'CODE'
-
- ASSUME CS:_code
-
- ; _aint86(int int_num, union REGS *in_regs, union REGS *out_regs)
-
-
- regs_word STRUC
-
- AX_REG DW 0
- BX_REG DW 0
- CX_REG DW 0
- DX_REG DW 0
- SI_REG DW 0
- DI_REG DW 0
- FLAGS_REG DW 0
-
- regs_word ENDS
-
-
- _aint86 PROC FAR
-
- PUSH BP
- MOV BP, SP
-
- PUSH DS
- PUSH ES
- PUSH SI
- PUSH DI
-
- ; get int_num
- MOV AX, WORD PTR [BP + 06]
-
- ; Write over second byte of INT instruction
- MOV CS: BYTE PTR int_lab + 1, AL
-
- ; Pointer to input registers.
- LDS SI, DWORD PTR [BP + 08]
-
- ; load in regs
- MOV AX, [SI.AX_REG]
- MOV BX, [SI.BX_REG]
- MOV CX, [SI.CX_REG]
- MOV DX, [SI.DX_REG]
- MOV DI, [SI.DI_REG]
- MOV SI, [SI.SI_REG] ; This must be last ...
-
- ; Assemble INT 0, overwrite the actual interrupt vector!
-
- int_lab:
- INT 0
-
- PUSHF
-
- ; Save SI so we can use it to point to the out regs
- PUSH SI
-
- ; out regs
- LDS SI, DWORD PTR [BP + 0CH]
-
- MOV [SI.AX_REG], AX
- MOV [SI.BX_REG], BX
- MOV [SI.CX_REG], CX
- MOV [SI.DX_REG], DX
- MOV [SI.DI_REG], DI
-
- ; GET saved SI into AX so we can save it back
- POP AX
- MOV [SI.SI_REG], AX
-
- ; now pop flags we saved off stack
- POP AX
- MOV [SI.FLAGS_REG], AX
-
- ; And finally, we return
- POP DI
- POP SI
- POP ES
- POP DS
-
- POP BP
- RET
-
- _aint86 ENDP
-
- _code ENDS
-
- END
-
-
- Program 9 - 9 Assembly Language Version of int86
-
-
- Set Functions
- -------------
-
- In this final section we implement a new data structure known as a set.
- You may recall set-theory from college math's. A set is a collection
- of arbitrary elements which you can perform operations on. You can
- add elements, delete elements, and test for inclusion
- in sets. You can also perform operations on sets as a whole. You can
- take the intersection of two sets (AND), and the union of two sets
- (OR). You can also take one set away from another (difference).
-
-
- Their importance to programming is that they are another data structure
- you can work with. Just like arrays and databases, they are another
- general purpose data structure you can use as you see fit.
-
-
- A good example is to detect loops in directed graphs. Let's say
- you have a tracking system where you keep track of where parts are being
- moved to and from. You want to detect whether a part has ever been
- around in a circle. You can do this by following the path of the part until
- it either comes to an end, or it returns to a location it has already
- visited.
-
-
- You use a set to detect this in the following way. Every time you "visit"
- a new node in the graph (in this case a storage location), add that location
- to the set. Before you move to a new node, first check to see if that node
- has already been visited by seeing if it is already a member of the set.
- If it is, you have detected a loop.
-
-
- We will implement the following set of routines to operate on a set:
-
- set_init() - Initialize a set of the give size
- set_kill() - Release a previously allocated set
- set_add() - Add an element to a set
- set_del() - Delete an element from a set
- set_in() - Check for membership of an element in a set
- set_or() - Take the union of two sets returning a third
- set_and() - Take the intersection of two sets returning a third
- set_diff() - Take the difference of two sets returning a third
- set_first() - Return the first element of a set
- set_next() - Return the next element of a set
-
-
- The routines operate using handles. When you initialize a set, it returns
- a handle. You use that handle in subsequent operations on the set.
-
-
- Since we write our Clipper callable routines just to get parameters
- and return results, we can immediately write them, even though we have
- not yet decided on a representation. Program 9 - 10 shows the Clipper
- callable C routines.
-
-
- /***
- * set.c
- *
- * SET handling functions
- *
- */
-
- #include "nandef.h"
- #include "extend.h"
-
-
- /* Forwards */
- extern void _set_kill(int);
- extern void _set_add(int, int);
- extern int _set_in(int, int);
- extern void _set_del(int, int);
-
-
- /***
- * NUMERIC set_init(set_size)
- *
- * NUMERIC set_size - The size of the set in bytes. Each byte
- * can store 8 entries.
- *
- * RETURN : A handle for this set for future reference.
- */
-
- CLIPPER set_init()
-
- {
- _retni(_set_init(_parni(1)));
- }
-
-
- /***
- * VOID set_kill(set_handle)
- *
- * NUMERIC set_handle - The ahndle of the set to delete
- *
- * The function removes this set from its internal structure.
- */
-
- CLIPPER set_kill()
-
- {
- _set_kill(_parni(1));
- _ret();
- }
-
-
- /***
- * VOID set_add(set_handle, set_element, ...)
- *
- * NUMERIC set_handle - The handle of the set to add to
- * NUMERIC set_element - The element to add to the set
- *
- * NOTE : you can pass any number of elements to set_add
- *
- * The function adds set_element to the set defined by set_handle.
- */
-
-
- CLIPPER set_add()
-
- {
- int i;
-
- for (i = 2; i <= PCOUNT; i++)
- _set_add(_parni(1), _parni(i));
- }
-
-
- /***
- * VOID set_del(set_handle, set_element, ...)
- *
- * NUMERIC set_handle - The handle of the set to delete from
- * NUMERIC set_element - The element to delete from the set
- *
- * NOTE : you can pass any number of elements to set_add
- *
- * The function deletes set_element from the set defined by set_handle.
- */
-
- CLIPPER set_del()
-
- {
- int i;
-
- for (i = 2; i <= PCOUNT; i++)
- _set_del(_parni(1), _parni(i));
- }
-
-
- /***
- * LOGICAL set_in(set_handle, set_element)
- *
- * NUMERIC set_handle - The handle of the set to test
- * NUMERIC set_element - The element to test
- *
- *
- * RETURN : The function returns whether set_element is contained in
- * the set defined by set_handle.
- */
-
- CLIPPER set_in()
-
- {
- _retl(_set_in(_parni(1), _parni(2)));
- }
-
-
- /***
- * NUMERIC set_or(set1_handle, set2_handle)
- *
- * NUMERIC set1_handle, set2_handle - Two set handles
- *
- * RETURN : - Set handle of new set constructed from the OR of
- * the two sets
- */
-
- CLIPPER set_or()
-
- {
- _retni(_set_or(_parni(1), _parni(2)));
- }
-
-
- /***
- * NUMERIC set_and(set1_handle, set2_handle)
- *
- * NUMERIC set1_handle, set2_handle - Two set handles
- *
- * RETURN : - Set handle of new set constructed from the AND of
- * the two sets
- */
-
- CLIPPER set_and()
-
- {
- _retni(_set_and(_parni(1), _parni(2)));
- }
-
-
- /***
- * NUMERIC set_diff(set1_handle, set2_handle)
- *
- * NUMERIC set1_handle, set2_handle - Two set handles
- *
- * RETURN : - Set handle of new set constructed from the DIFFERENCE of
- * the two sets (i.e. set1_handle - set2_handle)
- */
-
- CLIPPER set_diff()
-
- {
- _retni(_set_diff(_parni(1), _parni(2)));
- }
-
-
- /***
- * NUMERIC set_first(set_handle)
- *
- * NUMERIC set_handle
- *
- * RETURN : - The first element of the set
- */
-
- CLIPPER set_first()
-
- {
- _retni(_set_first(_parni(1)));
- }
-
-
- /***
- * NUMERIC set_next(set_handle)
- *
- * NUMERIC set_handle
- *
- * RETURN : - The next element of the set
- */
-
- CLIPPER set_next()
-
- {
- _retni(_set_next(_parni(1)));
- }
-
-
- Program 9 - 10 Clipper callable C Set Routines
-
-
- We represent the elements of a set by a bit in a byte. If element number
- 1 is in the set, bit number 31 is a 1. If it is not, it is a zero.
- We also need to save some "housekeeping" information for each set;
- specifically, we need to know how big it is, and to implement the sequential
- scan (set_first, set_next) we need to keep track of the last element we
- found.
-
-
- As usual in C, we combine these three things into a structure which we
- call a set descriptor. This is what we use to describe, or implement
- a set.
-
- /* This is the structure of a set descriptor */
-
- typedef struct
- {
- char *set; /* Pointer to the set */
- unsigned int set_len; /* The length of this set */
- unsigned int cur_bit; /* Used when stepping through a set to
- indicate next position */
- } SET_DESC;
-
-
- We need a set descriptor for each set we will represent. The problem
- is, to define these statically we have to know many there will be. We
- could make this dynamic of course, using a linked-list of set descriptors,
- but for simplicity in this presentation we define a fixed number. We use
- seven in the following examples, but you can simply change the #define
- statement to either increase or decrease this number.
-
- #define MAX_SETS 7
-
- /* Declare static array of set descriptors */
- static SET_DESC set_desc[MAX_SETS];
-
-
- We now have an array of set descriptors, one for each set. Before we start
- with the code proper, let's define a few constants and macros to make our
- job a little easier:
-
- /* number of elements represented by one byte in the set */
- #define BITS_ELEM 8
-
-
- /* Determine set byte number given element */
- #define SET_BYTE_NUM(ELEM_NUM) ELEM_NUM / BITS_ELEM
-
- /* Determine set bit number (within byte) given element */
- #define SET_BIT_NUM(ELEM_NUM) ELEM_NUM % BITS_ELEM
-
- /* Macro to return a set's size */
- #define SET_SIZE(set_num) (set_desc[set_num].set_len)
-
- /* Macro to return the number of element's that can be represented
- in a set */
- #define SET_NUM_ELEMS(set_num) (SET_SIZE(set_num) * BITS_ELEM)
-
- /* Return value for sequential scan termination */
- #define NO_MORE_ELEMS -1
-
-
- The macros SET_BYTE_NUM and SET_BIT_NUM determine which bit in which byte
- represents a given element. Figure 9 - 1 shows how the set elements map
- to our memory.
-
- Byte 0 Byte 1
-
- 7 6 5 4 3 2 1 0│15 14 13 12 11 10 9 8
- ┌──┬──┬──┬──┬──┬──┬──┬──┼──┬──┬──┬──┬──┬──┬──┬──┐
- │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │
- └──┴──┴──┴──┴──┴──┴──┴──┼──┴──┴──┴──┴──┴──┴──┴──┘
- │
-
-
- Figure 9 - 1 Set Elements Mapping to Bytes
-
-
-
- Program 9 - 11 shows the implementation of the _set_init and _set_kill
- functions.
-
-
- static int _set_init(set_size)
-
- int set_size;
-
- {
- unsigned int set_handle;
-
- set_handle = get_set_handle();
-
- set_desc[set_handle].set_len = set_size;
- set_desc[set_handle].cur_bit = 0;
-
- set_desc[set_handle].set = malloc(set_size);
- memset(set_desc[set_handle].set, 0, set_size);
-
- return set_handle;
- }
-
-
- static int get_set_handle()
-
- {
- int set_handle;
-
- for (set_handle = 0; set_handle < MAX_SETS &&
- set_desc[set_handle].set; set_handle++)
- ;
-
- if (set_handle >= MAX_SETS)
- /* Fatal error exit ... */
- exit(1);
-
- return (set_handle);
- }
-
-
- static void _set_kill(set_handle)
-
- int set_handle;
-
- {
- free(set_desc[set_handle].set);
- set_desc[set_handle].set = NULL;
- }
-
-
- Program 9 - 11 Set Initialize and Release Functions
-
-
- _set_init uses the utility routine get_set_handle to find the next free
- unused handle. It hten initilizes the appropriate set descriptor.
- _set_kill does the opposite. It releases the dynamic memory used by
- the set, and resets the pointer to NULL so this descriptor will be
- reused.
-
-
- Program 9 - 12 shows the _set_add, _set_del, and _set_in routines.
-
-
- static void _set_add(set_handle, set_element)
-
- int set_handle, set_element;
-
- {
- /* OR in the appropriate bit ... */
- if (set_element < SET_NUM_ELEMS(set_handle))
- set_desc[set_handle].set[SET_BYTE_NUM(set_element)]
- |= 1 << SET_BIT_NUM(set_element);
-
- }
-
-
- static void _set_del(set_handle, set_element)
-
- int set_handle, set_element;
-
- {
- /* AND out the appropriate bit ... */
- if (set_element < SET_NUM_ELEMS(set_handle))
- set_desc[set_handle].set[SET_BYTE_NUM(set_element)]
- &= ~(1 << SET_BIT_NUM(set_element));
- }
-
-
- static int _set_in(set_handle, set_element)
-
- int set_handle, set_element;
-
- {
- if (set_element < SET_NUM_ELEMS(set_handle))
- return (set_desc[set_handle].set[SET_BYTE_NUM(set_element)] &
- 1 << SET_BIT_NUM(set_element));
- }
-
-
- Program 9 - 12 Set_add, set_del, and set_in Routines
-
-
- Note how simple the routines are as a result of defining the macros
- and constants. They do all the work for us!
-
-
-
- Program 9 - 13 shows the set traversal routines, _set_first and
- _set_next. _set_first locates the first element in the set, keeping
- a copy of it in its descriptor (cur_bit). _set_next finds the next
- element, starting from the previous one.
-
-
- static int _set_first(set_handle)
-
- int set_handle;
-
- {
- int cur_bit, set_ptr;
-
- cur_bit = 0;
-
- while (!_set_in(set_handle, cur_bit)
- && cur_bit < SET_NUM_ELEMS(set_handle))
- cur_bit++;
-
- if (cur_bit != SET_NUM_ELEMS(set_handle))
- {
- set_desc[set_handle].cur_bit = cur_bit;
- }
-
- return ((cur_bit == SET_NUM_ELEMS(set_handle))
- ? NO_MORE_ELEMS : cur_bit);
- }
-
-
- static int _set_next(set_handle)
-
- int set_handle;
-
- {
- int cur_bit;
-
- /* Start search from next element */
- cur_bit = set_desc[set_handle].cur_bit + 1;
-
- while (!_set_in(set_handle, cur_bit) &&
- cur_bit < SET_NUM_ELEMS(set_handle))
- cur_bit++;
-
- if (cur_bit != SET_NUM_ELEMS(set_handle))
- {
- set_desc[set_handle].cur_bit = cur_bit;
- }
-
- return ((cur_bit == SET_NUM_ELEMS(set_handle))
- ? NO_MORE_ELEMS : cur_bit);
- }
-
-
- Program 9 - 13 _set_first and _set_next routines
-
-
- The final routines we need to write are _set_or, _set_and, and _set_diff.
- These are relatively starighforward now we have the lower level routines
- in place. They are shown in Program 9 - 14.
-
-
- static int _set_or(set1_handle, set2_handle)
-
- int set1_handle, set2_handle;
-
- {
- int new_set_handle, new_set_size, elem_num, smaller_set_size;
-
- new_set_size = max(SET_SIZE(set1_handle), SET_SIZE(set2_handle));
- smaller_set_size = min(SET_SIZE(set1_handle),
- SET_SIZE(set2_handle));
-
- new_set_handle = _set_init(new_set_size);
-
- /* Add any from either set */
- for (elem_num = 0; elem_num < smaller_set_size * BITS_ELEM;
- elem_num++)
- if (_set_in(set1_handle, elem_num) ||
- _set_in(set2_handle, elem_num))
- _set_add(new_set_handle, elem_num);
-
- /* Now add trailing from the longer set */
- if (smaller_set_size == SET_SIZE(set1_handle))
- for(; elem_num < new_set_size * BITS_ELEM; elem_num++)
- {
- if (_set_in(set2_handle, elem_num))
- _set_add(new_set_handle, elem_num);
- }
- else
- for(; elem_num < new_set_size * BITS_ELEM; elem_num++)
- {
- if (_set_in(set1_handle, elem_num))
- _set_add(new_set_handle, elem_num);
- }
-
- return (new_set_handle);
- }
-
-
- static int _set_and(set1_handle, set2_handle)
-
- int set1_handle, set2_handle;
-
- {
- int new_set_handle, new_set_size, elem_num;
-
- new_set_size = min(SET_SIZE(set1_handle), SET_SIZE(set2_handle));
- new_set_handle = _set_init(new_set_size);
-
- for (elem_num = 0; elem_num < new_set_size * BITS_ELEM; elem_num++)
- if (_set_in(set1_handle, elem_num) &&
- _set_in(set2_handle, elem_num))
- _set_add(new_set_handle, elem_num);
-
- return (new_set_handle);
- }
-
-
- static int _set_diff(set1_handle, set2_handle)
-
- int set1_handle, set2_handle;
-
- {
- int new_set_handle, new_set_size, elem_num, smaller_set_size;
-
- new_set_size = SET_SIZE(set1_handle);
- new_set_handle = _set_init(new_set_size);
-
- smaller_set_size = min(SET_SIZE(set1_handle),
- SET_SIZE(set2_handle));
-
- for (elem_num = 0; elem_num < smaller_set_size * BITS_ELEM;
- elem_num++)
- if (_set_in(set1_handle, elem_num) &&
- !_set_in(set2_handle, elem_num))
- _set_add(new_set_handle, elem_num);
-
- /* Now add any trailing from the first set */
- if (smaller_set_size == SET_SIZE(set2_handle))
- for(; elem_num < new_set_size * BITS_ELEM; elem_num++)
- if (_set_in(set1_handle, elem_num))
- _set_add(new_set_handle, elem_num);
-
- return (new_set_handle);
- }
-
- Program 9 - 14 _set_or, _set_and, and _set_diff routines.
-
-
-
- SUMMARY
- -------
-
- In this presentation we looked at some more advanced aspects of using
- C with Clipper. To reiterate the main points:
-
- ■ Isolate the Clipper specific code to handle parameters and return
- values in one function. This let's you use other routines as
- utility functions (consider the use of _set_in in Program 9 - 14
- for example). It also helps you to protect yourself from any Nantucket
- changes to the extend or extor system in future CLipper's.
-
- ■ Be sure to comment your Clipper callable C routines well. Explicitly
- document parameter types and return values.
-
- ■ Before you start writing a complex Clipper function, check to see
- if it's already defined in the Microsoft, or any third-party C
- library you may own. _split_path is a good example of this, as are
- chdir, mkdir, and rmdir.
-
- ■ Use the general purpose cint86 function to call DOS, BIOS, and any
- other software that interfaces through a software interrupt vector.
-
- ■ Consider implementing other data structures as we did here with SETs.
- Linked-lists, queues, and stacks would be easy to implement.