OSDN Git Service

added a cool document
authorsparky4 <sparky4@lappy4.4ch.mooo.com>
Thu, 11 Dec 2014 19:08:56 +0000 (13:08 -0600)
committersparky4 <sparky4@lappy4.4ch.mooo.com>
Thu, 11 Dec 2014 19:08:56 +0000 (13:08 -0600)
new file:   doc/CGUIDE_3.TXT

doc/CGUIDE_3.TXT [new file with mode: 0644]

diff --git a/doc/CGUIDE_3.TXT b/doc/CGUIDE_3.TXT
new file mode 100644 (file)
index 0000000..23f6a6b
--- /dev/null
@@ -0,0 +1,14499 @@
+\r
+                                    \r
+                   THE IBM PC PROGRAMMER'S GUIDE TO C\r
+                                    \r
+                                    \r
+                                    \r
+                               3rd Edition\r
+                                    \r
+                                    \r
+                                    \r
+                             Matthew Probert\r
+\r
+\r
+                                    \r
+                            COPYRIGHT NOTICE\r
+\r
+\r
+This publication remains the property of Matthew Probert. License is\r
+hereby given for this work to be freely distibuted in whole under the\r
+proviso that credit is given to the author. Sections of this work may be\r
+used and distributed without payment under the proviso that credit is\r
+given to both this work and the author. Source code occuring in this work\r
+may be used within commercial and non-commercial applications without\r
+charge and without reference to the author.\r
+\r
+ BIOGRAPHICAL NOTES\r
+\r
+\r
+Matthew Probert is a software consultant working for his own firm,\r
+Servile Software. He has been involved with micro-computer software\r
+design and programming since the age of eighteen and has been involved\r
+with the C programming language for the past ten years.\r
+\r
+His educational background lies in the non-too distinguished honour of\r
+having a nervous break down during his last year of secondary school\r
+which resulted in a lack of formal qualifications. However, Matthew has\r
+made up for it by achieving a complete recovery and has been studying\r
+Psychology with particular attention to behaviourism and conversation\r
+ever since.\r
+\r
+His chequered career spans back twelve years during which time he has\r
+trained people in the design and use of database management applications,\r
+been called upon to design and implement structured methodologies and has\r
+been a "good old fashioned" analyst programmer.\r
+\r
+Matthew Probert is currently researching Artificial Intelligence with\r
+particular reference to the application of natural language processing,\r
+whereby a computer software package may decode written human language and\r
+respond to it in an intelligent manner. He is also monitoring the\r
+progress of facilitated communication amongst autistic and children with\r
+severe learning and challenging behaviour and hopes one day to be able to\r
+develope a computer based mechanism for true and reliable communication\r
+between autistic people and the rest of society.\r
+\r
+\r
+Matthew Probert can be contacted via\r
+     Servile Software\r
+     5 Longcroft Close\r
+     Basingstoke\r
+     Hampshire\r
+     RG21 8XG\r
+     England\r
+\r
+     Telephone 01256 478576\r
+\r
+                                    \r
+                                 PREFACE\r
+\r
+In 1992, an English software house, Servile Software published a paper\r
+entitled "HOW TO C", which sought to introduce computer programmers to\r
+the C programming language. That paper was written by Matthew Probert. A\r
+follow up effort was "HOW TO CMORE", a document that was also published\r
+by Servile Software. Now those two documents have been amalgamated and\r
+thoroughly revamped to create this book. I have included loads of new\r
+source code that can be lifted directly out of the text.\r
+\r
+All the program listings have been typed in to the Turbo C compiler,\r
+compiled and executed successfully before being imported into this\r
+document.\r
+\r
+I hope you enjoy my work, and more I hope that you learn to program in C.\r
+It really is a great language, there can be no other language that gives\r
+the computer the opportunity to live up to the old saying;\r
+\r
+\r
+"To err is human, to make a complete balls up requires a computer!"\r
+\r
+\r
+\r
+Warning!\r
+\r
+This document is the result of over ten years experience as a software\r
+engineer. This document contains professional source code that is not\r
+intended for beginers.\r
+\r
+\r
+                                    \r
+                              INTRODUCTION\r
+\r
+\r
+The major distinguishing features of the C programming language are;\r
+\r
+ ú    block-structured flow-control constructs (typical of most high-level\r
+      languages);\r
+ ú    freedom to manipulate basic machine objects (eg: bytes) and to refer\r
+      to them using any particular object view desired (typical of assembly-\r
+      languages);\r
+ ú    both high-level operations (eg: floating-point arithmetic) and low-\r
+      level operations (which map closely onto machine-language instructions,\r
+      thereby offering the means to code in an optimal, yet portable, manner).\r
+\r
+This book sets out to describe the C programming language, as commonly\r
+found with compilers for the IBM PC, to enable a computer programmer with\r
+no previous knowledge of the C programming language to program in C using\r
+the IBM PC including the ROM facilities provided by the PC and facilities\r
+provided DOS.\r
+\r
+It is assumed that the reader has access to a C compiler, and to the\r
+documentation that accompanies it regarding library functions.\r
+\r
+The example programs were written with Borland's Turbo C, most of the non-\r
+standard facilities provided by Turbo C should be found in later releases\r
+of Microsoft C.\r
+\r
+\r
+\r
+Differences Between the Various Versions of C\r
+\r
+The original C (prior to the definitive book by K&R) defined the\r
+combination assignment operators (eg: +=, *=, etc.) backwards (ie: they\r
+were written =+, =*, etc.).  This caused terrible confusion when a\r
+statement such as\r
+\r
+ x=-y;\r
+was compiled - it could have meant\r
+\r
+ x = x - y;\r
+or\r
+\r
+ x = (-y);\r
+Ritchie soon spotted this ambiguity and changed the language to have\r
+these operators written in the now-familiar manner (+=, *=, etc.).\r
+\r
+The major variations, however, are between K&R C and ANSI C.  These can\r
+be summarized as follows:\r
+\r
+ ú    introduction of function prototypes in declarations; change of\r
+      function definition preamble to match the style of prototypes;\r
+ ú    introduction of the ellipsis ("...") to show variable-length\r
+      function argument lists;\r
+ ú    introduction of the keyword `void' (for functions not returning a\r
+      value) and the type `void *' for generic pointer variables;\r
+ ú    addition of string-merging, token-pasting and stringizing functions\r
+      in the preprocessor;\r
+ ú    addition of trigraph translation in the preprocessor;\r
+ ú    addition of the `#pragma' directive and formalization of the\r
+      `declared()' pseudofunction in the preprocessor;\r
+ ú    introduction of multi-byte strings and characters to support non-\r
+      English languages;\r
+ ú    introduction of the `signed' keyword (to complement the `unsigned'\r
+      keyword when used in integer declarations) and the unary plus (`+')\r
+      operator.\r
+\r
+\r
+\r
+C is a medium level language\r
+\r
+The powerful facilities offered by C to allow manipulation of direct\r
+memory addresses and data, even down to the bit level, along with C's\r
+structured approach to programming cause C to be classified as a "medium\r
+level" programming language. It possesses fewer ready made facilities\r
+than a high level language, such as BASIC, but a higher level of\r
+structure than low level Assembler.\r
+\r
+\r
+Key words\r
+\r
+The original C language as described in; "The C programming language", by\r
+Kernighan and Ritchie, provided 27 key words. To those 27 the ANSI\r
+standards committee on C have added five more. This confusingly results\r
+in two standards for the C language. However, the ANSI standard is\r
+quickly taking over from the old K & R standard.\r
+\r
+\r
+The 32 C key words are;\r
+\r
+auto              double             int                struct\r
+break             else               long               switch\r
+case              enum               register           typedef\r
+char              extern             return             union\r
+const             float              short              unsigned\r
+continue          for                signed             void\r
+default           goto               sizeof             volatile\r
+do                if                 static             while\r
+\r
+Some C compilers offer additional key words specific to the hardware\r
+environment that they operate on. You should be aware of your own C\r
+compilers additional key words. Most notably on the PC these are;\r
+\r
+\r
+near      far        huge\r
+\r
+\r
+\r
+Structure\r
+\r
+C programs are written in a structured manner. A collection of code\r
+blocks are created that call each other to comprise the complete program.\r
+As a structured language C provides various looping and testing commands\r
+such as;\r
+\r
+\r
+           do-while,  for, while, if\r
+\r
+and the use of jumps, while provided for, are rarely used.\r
+\r
+A C code block is contained within a pair of curly braces "{ }", and may\r
+be a complete procedure, in C terminology called a "function", or a\r
+subset of code within a function. For example the following is a code\r
+block. The statements within the curly braces are only executed upon\r
+satisfaction of the condition that "x < 10";\r
+\r
+\r
+if (x < 10)\r
+{\r
+     a = 1;\r
+     b = 0;\r
+}\r
+\r
+while this, is a complete function code block containing a sub code block\r
+as a do-while loop;\r
+\r
+int GET_X()\r
+{\r
+     int x;\r
+\r
+     do\r
+     {\r
+          printf("\nEnter a number between 0 and 10 ");\r
+          scanf("%d",&x);\r
+     }\r
+     while(x < 0 || x > 10);\r
+     return(x);\r
+}\r
+\r
+Notice how every statement line is terminated in a semicolon, unless that\r
+statement marks the start of a code block, in which case it is followed\r
+by a curly brace. C is a case sensitive but free flow language, spaces\r
+between commands are ignored, and therefore the semicolon delimiter is\r
+required to mark the end of the command line.\r
+\r
+Having a freeflow structure the following commands are recognised as the\r
+same by the C compiler;\r
+\r
+\r
+     x = 0;\r
+     x      =0;\r
+     x=0;\r
+\r
+\r
+The general form of a C program is as follows;\r
+\r
+     compiler preprocessor statements\r
+     global data declarations\r
+     \r
+     \r
+     \r
+     \r
+     return-type main(parameter list)\r
+     {\r
+          statements\r
+     }\r
+     \r
+     return-type f1(parameter list)\r
+     {\r
+          statements\r
+     }\r
+     \r
+     return-type f2(parameter list)\r
+     {\r
+          statements\r
+     }\r
+     .\r
+     .\r
+     .\r
+     return-type fn(parameter list)\r
+     {\r
+          statements\r
+     }\r
+     \r
+\r
+\r
+Comments\r
+\r
+C allows comments to be included in the program. A comment line is\r
+defined by being enclosed within "/*" and "*/". Thus the following is a\r
+comment;\r
+\r
+\r
+/* This is a legitimate C comment line */\r
+\r
+\r
+Libraries\r
+\r
+C programs are compiled and combined with library functions provided with\r
+the C compiler. These libraries are of generally standard functions, the\r
+functionality of which are defined in the ANSI standard of the C\r
+language, but are provided by the individual C compiler manufacturers to\r
+be machine dependant. Thus, the standard library function "printf()"\r
+provides the same facilities on a DEC VAX as on an IBM PC, although the\r
+actual machine language code in the library is quite different for each.\r
+The C programmer however, does not need to know about the internals of\r
+the libraries, only that each library function will behave in the same\r
+way on any computer.\r
+\r
+\r
+                                    \r
+                               DATA TYPES\r
+\r
+\r
+There are four basic types of data in the C language; character, integer,\r
+floating point, and valueless that are referred to by the C key words;\r
+\r
+"char", "int", "float" and "void" respectively.\r
+\r
+To the basic data types may be added the type modifiers; signed,\r
+unsigned, long and short to produce further data types. By default data\r
+types are assumed signed, and the signed modifier is rarely used, unless\r
+to overide a compiler switch defaulting a data type to unsigned.\r
+\r
+The size of each data type varies from one hardware platform to another,\r
+but the least range of values that can be held is described in the ANSI\r
+standard as follows;\r
+\r
+\r
+Type                     Size                     Range\r
+                                                  \r
+char                     8                        -127 to 127\r
+unsigned char            8                        0 to 255\r
+int                      16                       -32767 to 32767\r
+unsigned int             16                       0 to 65535\r
+long int                 32                       -2147483647 to\r
+                                                  2147483647\r
+unsigned long int        32                       0 to 4294967295\r
+float                    32                       Six digit precision\r
+double                   64                       Ten digit precision\r
+long double              80                       Ten digit precision\r
+\r
+\r
+In practice, this means that the data type `char' is particularly\r
+suitable for storing flag type variables, such as status codes, which\r
+have a limited range of values. The `int' data type can be used, but if\r
+the range of values does not exceed 127 (or 255 for an unsigned char),\r
+then each declared variable would be wasting storage space.\r
+\r
+Which real number data type to use: `float', `double' or `long double' is\r
+another tricky question. When numeric accuracy is required, for example\r
+in an accounting application, the instinct would be to use the `long\r
+double', but this requires at least 10 bytes of storage space for each\r
+variable. And real numbers are not as precise as integers anyway, so\r
+perhaps one should use integer data types instead and work around the\r
+problem. The data type `float' is worse than useless since its six digit\r
+precision is too inaccurate to be relied upon. Generally, then, you\r
+should use integer data types where ever possible, but if real numbers\r
+are required use at least a `double'.\r
+\r
+\r
+Declaring a variable\r
+\r
+All variables in a C program must be declared before they can be used.\r
+The general form of a variable definition is;\r
+\r
+\r
+     type name;\r
+\r
+So, for example to declare a variable "x", of data type "int" so that it\r
+may store a value in the range -32767 to 32767, you use the statement;\r
+\r
+\r
+     int x;\r
+\r
+Character strings may be declared, which are in reality arrays of\r
+characters. They are declared as follows;\r
+\r
+\r
+     char name[number_of_elements];\r
+\r
+So, to declare a string thirty characters long, and called `name' you\r
+would use the declaration;\r
+\r
+\r
+     char name[30];\r
+\r
+\r
+Arrays of other data types also may be declared in one, two or more\r
+dimensions in the same way. For example to declare a two dimensional\r
+array of integers;\r
+\r
+\r
+     int x[10][10];\r
+\r
+The elements of this array are then accessed as;\r
+\r
+     x[0][0]\r
+     x[0][1]\r
+     x[n][n]\r
+\r
+There are three levels of access to variable; local, module and global. A\r
+variable declared within a code block is only known to the statements\r
+within that code block. A variable declared outside any function code\r
+blocks but prefixed with the storage modifier "static" is known only to\r
+the statements within that source module. A variable declared outside any\r
+functions and not prefixed with the static storage type modifier may be\r
+accessed by any statement within any source module of the program.\r
+\r
+\r
+For example;\r
+\r
+     int error;\r
+     static int a;\r
+     \r
+     main()\r
+     {\r
+          int x;\r
+          int y;\r
+     \r
+     }\r
+     \r
+     funca()\r
+     {\r
+          /* Test variable 'a' for equality with 0 */\r
+          if (a == 0)\r
+          {\r
+               int b;\r
+               for(b = 0; b < 20; b++)\r
+                    printf("\nHello World");\r
+          }\r
+     \r
+     }\r
+     \r
+\r
+In this example the variable `error' is accessible by all source code\r
+modules compiled together to form the finished program. The variable `a'\r
+is accessible by statements in both functions `main()' and `funca()', but\r
+is invisible to any other source module. Variables `x' and `y' are only\r
+accessible by statements within function `main()'. The variable `b' is\r
+only accessible by statements within the code block following the `if'\r
+statement.\r
+\r
+If a second source module wished to access the variable `error' it would\r
+need to declare `error' as an `extern' global variable thus;\r
+\r
+\r
+     extern int error;\r
+     \r
+     funcb()\r
+     {\r
+     }\r
+     \r
+C will quite happily allow you, the programmer, to assign different data\r
+types to each other. For example, you may declare a variable to be of\r
+type `char' in which case a single byte of data will be allocated to\r
+store the variable. To this variable you can attempt to allocate larger\r
+values, for example;\r
+\r
+\r
+     main()\r
+     {\r
+     \r
+          x = 5000;\r
+     \r
+     }\r
+     \r
+In this example the variable `x' can only store a value between -127 and\r
+128, so the figure 5000 will NOT be assigned to the variable `x'. Rather\r
+the value 136 will be assigned!\r
+\r
+\r
+Often you may wish to assign different data types to each other, and to\r
+prevent the compiler from warning you of a possible error you can use a\r
+cast to tell the compiler that you know what you're doing. A cast\r
+statement is a data type in parenthesis preceding a variable or\r
+expression;\r
+\r
+\r
+     main()\r
+     {\r
+          float x;\r
+          int y;\r
+     \r
+          x = 100 / 25;\r
+     \r
+          y = (int)x;\r
+     }\r
+\r
+In this example the (int) cast tells the compiler to convert the value of\r
+the floating point variable x to an integer before assigning it to the\r
+variable y.\r
+\r
+\r
+\r
+Formal parameters\r
+\r
+A C function may receive parameters from a calling function. These\r
+parameters are declared as variables within the parentheses of the\r
+function name, thus;\r
+\r
+\r
+     int MULT(int x, int y)\r
+     {\r
+          /* Return parameter x multiplied by parameter y */\r
+          return(x * y);\r
+     }\r
+     \r
+     main()\r
+     {\r
+          int a;\r
+          int b;\r
+          int c;\r
+     \r
+          a = 5;\r
+          b = 7;\r
+          c = MULT(a,b);\r
+     \r
+          printf("%d multiplied by %d equals %d\n",a,b,c);\r
+     }\r
+     \r
+\r
+Access modifiers\r
+\r
+There are two access modifiers; `const' and `volatile'. A variable\r
+declared to be `const' may not be changed by the program, whereas a\r
+variable declared as type  as type `volatile' may be changed by the\r
+program. In addition, declaring a variable to be volatile prevents the C\r
+compiler from allocating the variable to a register, and reduces the\r
+optimization carried out on the variable.\r
+\r
+\r
+\r
+Storage class types\r
+C provides four storage types; `extern', `static', `auto' and `register'.\r
+\r
+The extern storage type is used to allow a source module within a C\r
+program to access a variable declared in another source module.\r
+\r
+Static variables are only accessible within the code block that declared\r
+them, and additionally if the variable is local, rather than global, they\r
+retain their old value between subsequent calls to the code block.\r
+\r
+Register variables are stored within CPU registers where ever possible,\r
+providing the fastest possible access to their values.\r
+\r
+The auto type variable is only used with local variables, and declares\r
+the variable to retain its value locally only. Since this is the default\r
+for local variables the auto storage type is very rarely used.\r
+                                    \r
+                                    \r
+                                    \r
+                                OPERATORS\r
+\r
+Operators are tokens that cause a computation to occur when applied to\r
+variables. C provides the following operators;\r
+\r
+\r
+&                  Address\r
+*                  Indirection\r
++                  Unary plus\r
+-                  Unary minus\r
+~                  Bitwise compliment\r
+!                  Logical negation\r
+++                 As a prefix;\r
+                   preincrement\r
+                   As a suffix;\r
+                   postincrement\r
+--                 As a prefix;\r
+                   predecrement\r
+                   As a suffix;\r
+                   postdecrement\r
++                  Addition\r
+-                  Subtraction\r
+*                  Multiply\r
+/                  Divide\r
+%                  Remainder\r
+<<                 Shift left\r
+>>                 Shift right\r
+&                  Bitwise AND\r
+|                  Bitwise OR\r
+^                  Bitwise XOR\r
+&&                 Logical AND\r
+||                 Logical OR\r
+=                  Assignment\r
+*=                 Assign product\r
+/=                 Assign quotient\r
+%=                 Assign remainder\r
+                   (modulus)\r
++=                 Assign sum\r
+-=                 Assign difference\r
+<<=                Assign left shift\r
+>>=                Assign right shift\r
+&=                 Assign bitwise AND\r
+|=                 Assign bitwise OR\r
+^=                 Assign bitwise XOR\r
+<                  Less than\r
+>                  Greater than\r
+<=                 Less than or equal\r
+                   to\r
+>=                 Greater than or\r
+                   equal to\r
+==                 Equal to\r
+!=                 Not equal to\r
+.                  Direct component\r
+                   selector\r
+->                 Indirect component\r
+                   selector\r
+a ? x:y            "If a is true then\r
+                   x else y"\r
+[]                 Define arrays\r
+()                 Parenthesis\r
+                   isolate conditions\r
+                   and expressions\r
+...                Ellipsis are used\r
+                   in formal\r
+                   parameter lists of\r
+                   function\r
+                   prototypes to show\r
+                   a variable number\r
+                   of\r
+                   parameters or\r
+                   parameters of\r
+                   varying types.\r
+\r
+To illustrate some more commonly used operators consider the following\r
+short program;\r
+\r
+\r
+     main()\r
+     {\r
+          int a;\r
+          int b;\r
+          int c;\r
+          a = 5;           /* Assign a value of 5 to variable 'a' */\r
+          b = a / 2;       /* Assign the value of 'a' divided by two to\r
+     variable 'b' */\r
+          c = b * 2;       /* Assign the value of 'b' multiplied by two\r
+     to variable 'c' */\r
+     \r
+          if (a == c)     /* Test if 'a' holds the same value as 'c' */\r
+     \r
+               puts("Variable 'a' is an even number");\r
+          else\r
+               puts("Variable 'a' is an odd number");\r
+     }\r
+     \r
+     \r
+Normally when incrementing the value of a variable you would write\r
+something like;\r
+\r
+          x = x + 1\r
+\r
+C provides the incremental operator '++' as well so that you can write;\r
+\r
+          x++\r
+\r
+Similarly you can decrement the value of a variable using '--' as;\r
+\r
+          x--\r
+\r
+All the other mathematical operators may be used the same, so in a C\r
+program you can write in shorthand;\r
+\r
+\r
+NORMAL                               C\r
+                                     \r
+x = x + 1                            x++\r
+x = x - 1                            x--\r
+x = x * 2                            x *= 2\r
+x = x / y                            x /= y\r
+x = x % 5                            x %= 5\r
+\r
+and so on.\r
+                                    \r
+                                    \r
+                                    \r
+                                FUNCTIONS\r
+\r
+Functions are the source code procedures that comprise a C program. They\r
+follow the general form;\r
+\r
+     return_type function_name(parameter_list)\r
+     {\r
+          statements\r
+     }\r
+\r
+\r
+The return_type specifies the data type that will be returned by the\r
+function; char, int, double, void &c.\r
+\r
+The code within a C function is invisible to any other C function, and\r
+jumps may not be made from one function into the middle of another,\r
+although functions may call other functions. Also, functions cannot be\r
+defined within functions, only within source modules.\r
+\r
+Parameters may be passed to a function either by value, or by reference.\r
+If a parameter is passed by value, then only a copy of the current value\r
+of the parameter is passed to the function. A parameter passed by\r
+reference however, is a pointer to the actual parameter that may then be\r
+changed by the function.\r
+\r
+\r
+The following example passes two parameters by value to a function,\r
+funca(), which attempts to change the value of the variables passed to\r
+it. And then passes the same two parameters by reference to funcb() which\r
+also attempts to modify their values.\r
+\r
+\r
+     #include <stdio.h>\r
+     \r
+     int funca(int x, int y)\r
+     {\r
+          /* This function receives two parameters by value, x and y */\r
+     \r
+          x = x * 2;\r
+          y = y * 2;\r
+     \r
+          printf("\nValue of x in funca() %d value of y in funca()\r
+     %d",x,y);\r
+     \r
+          return(x);\r
+     }\r
+     \r
+     \r
+     int funcb(int *x, int *y)\r
+     {\r
+          /* This function receives two parameters by reference, x and y\r
+     */\r
+     \r
+          *x = *x * 2;\r
+          *y = *y * 2;\r
+     \r
+          printf("\nValue of x in funcb() %d value of y in funcb()\r
+     %d",*x,*y);\r
+     \r
+          return(*x);\r
+     }\r
+     \r
+     main()\r
+     {\r
+          int x;\r
+          int y;\r
+          int z;\r
+     \r
+          x = 5;\r
+          y = 7;\r
+     \r
+          z = funca(x,y);\r
+          z = funcb(&x,&y);\r
+     \r
+          printf("\nValue of x %d value of y %d value of z %d",x,y,z);\r
+     }\r
+     \r
+\r
+Actually funcb() does not change the values of the parameters it\r
+receives.  Rather it changes the contents of the memory addresses pointed\r
+to by the received parameters. While funca() receives the values of\r
+variables `x' and `y' from function main(), funcb() receives the memory\r
+addresses of the variables `x' and `y' from function main().\r
+\r
+\r
+\r
+Passing an array to a function\r
+\r
+The following program passes an array to a function, funca(), which\r
+initialises the array elements;\r
+\r
+     #include <stdio.h>\r
+     \r
+     void funca(int x[])\r
+     {\r
+          int n;\r
+     \r
+          for(n = 0; n < 100; n++)\r
+          x[n] = n;\r
+     }\r
+     \r
+     main()\r
+     {\r
+          int array[100];\r
+          int counter;\r
+     \r
+          funca(array);\r
+     \r
+          for(counter = 0; counter < 100; counter++)\r
+               printf("\nValue of element %d is\r
+     %d",counter,array[counter]);\r
+     }\r
+\r
+The parameter of funca() `int x[]' is declared to be an array of any\r
+length.  This works because the compiler passes the address of the start\r
+of the array parameter to the function, rather than the value of the\r
+individual elements.  This does of course mean that the function can\r
+change the value of the array elements. To prevent a function from\r
+changing the values you can specify the parameter as type `const';\r
+\r
+\r
+     funca(const int x[])\r
+     {\r
+     }\r
+\r
+This will then generate a compiler error at the line that attempts to\r
+write a value to the array. However, specifying a parameter to be const\r
+does not protect the parameter from indirect assignment as the following\r
+program illustrates;\r
+\r
+\r
+     #include <stdio.h>\r
+     \r
+     int funca(const int x[])\r
+     {\r
+          int *ptr;\r
+          int n;\r
+     \r
+          /* This line gives a 'suspicious pointer conversion warning' */\r
+          /* because x is a const pointer, and ptr is not */\r
+          ptr = x;\r
+     \r
+          for(n = 0; n < 100; n++)\r
+          {\r
+               *ptr = n;\r
+               ptr++;\r
+          }\r
+     }\r
+     \r
+     main()\r
+     {\r
+          int array[100];\r
+          int counter;\r
+     \r
+          funca(array);\r
+     \r
+          for(counter = 0; counter < 100; counter++)\r
+               printf("\nValue of element %d is\r
+     %d",counter,array[counter]);\r
+     }\r
+     \r
+\r
+\r
+Passing parameters to main()\r
+\r
+C allows parameters to be passed from the operating system to the program\r
+when it starts executing through two parameters; argc and argv[], as\r
+follows;\r
+\r
+\r
+     #include <stdio.h>\r
+     \r
+     main(int argc, char *argv[])\r
+     {\r
+          int n;\r
+     \r
+          for(n = 0; n < argc; n++)\r
+          printf("\nParameter %d equals %s",n,argv[n]);\r
+     }\r
+     \r
+\r
+Parameter argc holds the number of parameters passed to the program, and\r
+the array argv[] holds the addresses of each parameter passed. argv[0] is\r
+always the program name. This feature may be put to good use in\r
+applications that need to access system files. Consider the following\r
+scenario:\r
+\r
+A simple database application stores its data in a single file called\r
+"data.dat". The application needs to be created so that it may be stored\r
+in any directory on either a floppy diskette or a hard disk, and executed\r
+both from within the host directory and through a DOS search path. To\r
+work correctly the application must always know where to find the data\r
+file; "data.dat". This is solved by assuming that the data file will be\r
+in the same directory as the executable module, a not unreasonable\r
+restriction to place upon the operator. The following code fragment then\r
+illustrates how an application may apply this algorithm into practice to\r
+be always able to locate a desired system file:\r
+\r
+\r
+     #include <string.h>\r
+     \r
+     char system_file_name[160];\r
+     \r
+     void main(int argc,char *argv[])\r
+     {\r
+          char *data_file = "DATA.DAT";\r
+          char *p;\r
+     \r
+          strcpy(system_file_name,argv[0]);\r
+          p = strstr(system_file_name,".EXE");\r
+          if (p == NULL)\r
+          {\r
+               /* The executable is a .COM file */\r
+             p = strstr(system_file_name,".COM");\r
+          }\r
+     \r
+          /* Now back track to the last '\' character in the file name */\r
+          while(*(p - 1) != '\\')\r
+               p--;\r
+     \r
+          strcpy(p,data_file);\r
+     }\r
+\r
+In practice this code creates a string in system_file_name that is\r
+comprised of path\data.dat, so if for example the executable file is\r
+called "test.exe" and resides in the directory \borlandc, then\r
+system_file_name will be assigned with: \borlandc\data.dat\r
+\r
+\r
+\r
+Returning from a function\r
+\r
+The command `return' is used to return immediately from a function. If\r
+the function was declared with a return data type, then return should be\r
+used with a parameter of the same data type.\r
+\r
+\r
+\r
+Function prototypes\r
+\r
+Prototypes for functions allow the C compiler to check that the type of\r
+data being passed to and from functions is correct. This is very\r
+important to prevent data overflowing its allocated storage space into\r
+other variables areas.\r
+\r
+A function prototype is placed at the beginning of the program, after any\r
+preprocessor commands, such as #include <stdio.h>, and before the\r
+declaration of any functions.\r
+                                    \r
+                           THE C PREPROCESSOR\r
+\r
+C allows for commands to the compiler to be included in the source code.\r
+These commands are then called preprocessor commands and are defined by\r
+the ANSI standard to be;\r
+\r
+\r
+    #if\r
+    #ifdef\r
+    #ifndef\r
+    #else\r
+    #elif\r
+    #include\r
+    #define\r
+    #undef\r
+    #line\r
+    #error\r
+    #pragma\r
+\r
+All preprocessor commands start with a hash symbol, "#", and must be on a\r
+line on their own (although comments may follow).\r
+\r
+\r
+\r
+#define\r
+\r
+The #define command specifies an identifier and a string that the\r
+compiler will substitute every time it comes accross the identifier\r
+within that source code module. For example;\r
+\r
+\r
+#define FALSE 0\r
+#define TRUE !FALSE\r
+\r
+The compiler will replace any subsequent occurence of `FALSE' with `0'\r
+and any subsequent occurence of `TRUE' with `!0'. The substitution does\r
+NOT take place if the compiler finds that the identifier is enclosed by\r
+quotation marks, so\r
+\r
+\r
+     printf("TRUE");\r
+\r
+would NOT be replaced, but\r
+\r
+     printf("%d",FALSE);\r
+\r
+would be.\r
+\r
+The #define command also can be used to define macros that may include\r
+parameters. The parameters are best enclosed in parenthesis to ensure\r
+that correct substitution occurs.\r
+\r
+\r
+This example declares a macro `larger()' that accepts two parameters and\r
+returns the larger of the two;\r
+\r
+     #include <stdio.h>\r
+     \r
+     #define larger(a,b) (a > b) ? (a) : (b)\r
+     \r
+     int main()\r
+     {\r
+          printf("\n%d is largest",larger(5,7));\r
+     \r
+     }\r
+\r
+\r
+#error\r
+\r
+The #error command causes the compiler to stop compilation and to display\r
+the text following the #error command. For example;\r
+\r
+\r
+#error REACHED MODULE B\r
+\r
+will cause the compiler to stop compilation and display;\r
+\r
+     REACHED MODULE B\r
+\r
+\r
+\r
+#include\r
+\r
+The #include command tells the compiler to read the contents of another\r
+source file. The name of the source file must be enclosed either by\r
+quotes or by angular brackets thus;\r
+\r
+\r
+     #include "module2.c"\r
+     #include <stdio.h>\r
+\r
+Generally, if the file name is enclosed in angular brackets, then the\r
+compiler will search for the file in a directory defined in the\r
+compiler's setup. Whereas if the file name is enclosed in quotes then the\r
+compiler will look for the file in the current directory.\r
+\r
+\r
+\r
+#if, #else, #elif, #endif\r
+\r
+The #if set of commands provide conditional compilation around the\r
+general form;\r
+\r
+     #if constant_expression\r
+          statements\r
+     #else\r
+          statements\r
+     #endif\r
+\r
+#elif stands for '#else if' and follows the form;\r
+\r
+     #if expression\r
+          statements\r
+     #elif expression\r
+          statements\r
+     #endif\r
+\r
+\r
+\r
+#ifdef, #ifndef\r
+\r
+These two commands stand for '#if defined' and '#if not defined'\r
+respectively and follow the general form;\r
+\r
+     #ifdef macro_name\r
+          statements\r
+     #else\r
+          statements\r
+     #endif\r
+\r
+     #ifndef macro_name\r
+          statements\r
+     #else\r
+          statements\r
+     #endif\r
+\r
+where 'macro_name' is an identifier declared by a #define statement.\r
+\r
+\r
+\r
+#undef\r
+\r
+Undefines a macro previously defined by #define.\r
+\r
+\r
+#line\r
+\r
+Changes the compiler declared global variables __LINE__ and __FILE__. The\r
+general form of #line is;\r
+\r
+     #line number "filename"\r
+\r
+where number is inserted into the variable '__LINE__' and 'filename' is\r
+assigned to '__FILE__'.\r
+\r
+\r
+#pragma\r
+\r
+This command is used to give compiler specific commands to the compiler.\r
+The compiler's manual should give you full details of any valid options\r
+to go with the particular implementation of #pragma that it supports.\r
+                                    \r
+                       PROGRAM CONTROL STATEMENTS\r
+\r
+As with any computer language, C includes statements that test the\r
+outcome of an expression. The outcome of the test is either TRUE or\r
+FALSE. The C language defines a value of TRUE as non-zero, and FALSE as\r
+zero.\r
+\r
+\r
+\r
+Selection statements\r
+\r
+The general purpose selection statement is "if" that follows the general\r
+form;\r
+\r
+          if (expression)\r
+               statement\r
+          else\r
+               statement\r
+\r
+Where "statement" may be a single statement, or a code block enclosed in\r
+curly braces. The "else" is optional. If the result of the expression\r
+equates to TRUE, then the statement(s) following the if() will be\r
+evaluated.  Otherwise the statement(s) following the else, if there is\r
+one, will be evaluated.\r
+\r
+\r
+An alternative to the if....else combination is the ?: command that takes\r
+the form;\r
+\r
+\r
+           expression ? true_expression : false_expression\r
+\r
+Where if the expression evaluates to TRUE, then the true_expression will\r
+be evaluated, otherwise the false_expression will be evaluated. Thus we\r
+get;\r
+\r
+\r
+     #include <stdio.h>\r
+     \r
+     main()\r
+     {\r
+          int x;\r
+     \r
+          x = 6;\r
+     \r
+          printf("\nx is an %s number", x % 2 == 0 ? "even" : "odd");\r
+     }\r
+     \r
+C also provides a multiple branch selection statement, switch, which\r
+successively tests a value of an expression against a list of values and\r
+branches program execution to the first match found. The general form of\r
+switch is;\r
+\r
+\r
+     switch (expression)\r
+     {\r
+          case value1 :  statements\r
+                    break;\r
+          case value2 :  statements\r
+                    break;\r
+          .\r
+          .\r
+          .\r
+          .\r
+          case valuen :  statements\r
+                    break;\r
+          default :      statements\r
+     }\r
+\r
+The break statement is optional, but if omitted, program execution will\r
+continue down the list.\r
+\r
+     #include <stdio.h>\r
+     \r
+     main()\r
+     {\r
+          int x;\r
+     \r
+          x = 6;\r
+     \r
+          switch(x)\r
+          {\r
+               case 0 : printf("\nx equals zero");\r
+                     break;\r
+               case 1 : printf("\nx equals one");\r
+                     break;\r
+               case 2 : printf("\nx equals two");\r
+                     break;\r
+               case 3 : printf("\nx equals three");\r
+                     break;\r
+               default : printf("\nx is larger than three");\r
+          }\r
+     }\r
+\r
+Switch statements may be nested within one another. This is a\r
+particularly useful feature for confusing people who read your source\r
+code!\r
+\r
+\r
+Iteration statements\r
+C provides three looping or iteration statements; for, while, and do-\r
+while. The for loop has the general form;\r
+\r
+\r
+     for(initialization;condition;increment)\r
+\r
+and is useful for counters such as in this example that displays the\r
+entire ascii character set;\r
+\r
+     #include <stdio.h>\r
+     \r
+     main()\r
+     {\r
+          int x;\r
+     \r
+          for(x = 32; x < 128; x++)\r
+               printf("%d\t%c\t",x,x);\r
+     }\r
+     \r
+An infinite for loop is also quite valid;\r
+\r
+     for(;;)\r
+     {\r
+          statements\r
+     }\r
+\r
+Also, C allows empty statements. The following for loop removes leading\r
+spaces from a string;\r
+\r
+     for(; *str == ' '; str++)\r
+          ;\r
+\r
+Notice the lack of an initializer, and the empty statement following the\r
+loop.\r
+\r
+The while loop is somewhat simpler than the for loop and follows the\r
+general form;\r
+\r
+     while (condition)\r
+          statements\r
+\r
+The statement following the condition, or statements enclosed in curly\r
+braces will be executed until the condition is FALSE. If the condition is\r
+false before the loop commences, the loop statements will not be\r
+executed. The do-while loop on the other hand is always executed at least\r
+once. It takes the general form;\r
+\r
+\r
+     do\r
+     {\r
+          statements\r
+     }\r
+     while(condition);\r
+\r
+\r
+Jump statements\r
+\r
+The "return" statement is used to return from a function to the calling\r
+function. Depending upon the declared return data type of the function it\r
+may or may not return a value;\r
+\r
+\r
+     int MULT(int x, int y)\r
+     {\r
+          return(x * y);\r
+     }\r
+\r
+or;\r
+\r
+     void FUNCA()\r
+     {\r
+          printf("\nHello World");\r
+          return;\r
+     }\r
+\r
+The "break" statement is used to break out of a loop or from a switch\r
+statement. In a loop it may be used to terminate the loop prematurely, as\r
+shown here;\r
+\r
+\r
+     #include <stdio.h>\r
+     \r
+     main()\r
+     {\r
+          int x;\r
+     \r
+          for(x = 0; x < 256; x++)\r
+          {\r
+               if (x == 100)\r
+                    break;\r
+     \r
+               printf("%d\t",x);\r
+          }\r
+     }\r
+\r
+In contrast to "break" is "continue", which forces the next iteration of\r
+the loop to occur, effectively forcing program control back to the loop\r
+statement.\r
+\r
+\r
+C provides a function for terminating the program prematurely, "exit()".\r
+Exit() may be used with a return value to pass back to the calling\r
+program;\r
+\r
+\r
+     exit(return_value);\r
+\r
+\r
+More About ?:\r
+\r
+\r
+A powerful, but often misunderstood feature of the C programming language\r
+is ?:. This is an operator that acts upon a boolean expression, and\r
+returns one of two values dependant upon the result of the expression;\r
+\r
+\r
+     <boolean expression> ? <value for true> : <value for false>\r
+\r
+It can be used almost anywhere, for example it was used in the binary\r
+search demonstration program;\r
+\r
+     printf("\n%s\n",(result == 0) ? "Not found" : "Located okay");\r
+\r
+Here it passes either "Not found" or "Located okay" to the printf()\r
+function dependant upon the outcome of the boolean expression `result ==\r
+0'. Alternatively it can be used for assigning values to a variable;\r
+\r
+     x = (a  == 0) ? (b) : (c);\r
+\r
+Which will assign the value of b to variable x if a is equal to zero,\r
+otherwise it will assign the value of c to variable x.\r
+\r
+This example returns the name of the executing program, without any path\r
+description;\r
+\r
+     #include <stdio.h>\r
+     #include <stddef.h>\r
+     #include <string.h>\r
+     \r
+     char *progname(char *pathname)\r
+     {\r
+          /* Return name of running program */\r
+          unsigned l;\r
+          char *p;\r
+          char *q;\r
+          static char bnbuf[256];\r
+     \r
+          return pathname? p = strrchr (pathname, '\\'),\r
+                     q = strrchr (pathname, '.'),\r
+                     l = (q == NULL? strchr (pathname, '\0'): q)\r
+                       - (p == NULL? p = pathname: ++p),\r
+                     strncpy (bnbuf, p, l),\r
+                     bnbuf[l] = '\0',\r
+                     strlwr (bnbuf)\r
+                  : NULL;\r
+     }\r
+     \r
+     void main(int argc, char *argv[])\r
+     {\r
+          printf("\n%s",progname(argv[0]));\r
+     }\r
+     \r
+     \r
+\r
+Continue\r
+\r
+The continue keyword forces control to jump to the test statement of the\r
+innermost loop (while, do...while()). This can be useful for terminating\r
+a loop gracefuly as this program that reads strings from a file until\r
+there are no more illustrates;\r
+\r
+\r
+     #include <stdio.h>\r
+     \r
+     void main()\r
+     {\r
+          FILE *fp;\r
+          char *p;\r
+          char buff[100];\r
+     \r
+          fp = fopen("data.txt","r");\r
+          if (fp == NULL)\r
+          {\r
+               fprintf(stderr,"Unable to open file data.txt");\r
+               exit(0);\r
+          }\r
+     \r
+          do\r
+          {\r
+               p = fgets(buff,100,fp);\r
+               if (p == NULL)\r
+                    /* Force exit from loop */\r
+                    continue;\r
+               puts(p);\r
+          }\r
+          while(p);\r
+     }\r
+     \r
+With a for() loop however, continue passes control back to the third\r
+parameter!\r
+                                    \r
+                            INPUT AND OUTPUT\r
+\r
+\r
+\r
+Input\r
+Input to a C program may occur from the console, the standard input\r
+device (unless otherwise redirected this is the console), from a file or\r
+from a data port.\r
+\r
+The general input command for reading data from the standard input stream\r
+`stdin' is scanf(). Scanf() scans a series of input fields, one character\r
+at a time. Each field is then formatted according to the appropriate\r
+format specifier passed to the scanf() function as a parameter. This\r
+field is then stored at the ADDRESS passed to scanf() following the\r
+format specifiers list.\r
+\r
+For example, the following program will read a single integer from the\r
+stream stdin;\r
+\r
+\r
+     main()\r
+     {\r
+          int x;\r
+     \r
+          scanf("%d",&x);\r
+     }\r
+\r
+Notice the address operator & prefixing the variable name `x' in the\r
+scanf() parameter list. This is because scanf() stores values at\r
+ADDRESSES rather than assigning values to variables directly.\r
+\r
+The format string is a character string that may contain three types of\r
+data:\r
+\r
+whitespace characters (space, tab and newline), non-whitespace characters\r
+(all ascii characters EXCEPT %) and format specifiers.\r
+\r
+Format specifiers have the general form;\r
+\r
+\r
+     %[*][width][h|l|L]type_character\r
+\r
+After the % sign the format specifier is comprised of:\r
+\r
+ an optional assignment suppression character, *, which suppresses\r
+ assignment of the next input field.\r
\r
+ an optional width specifier, width, which declares the maximum number\r
+ of characters to be read.\r
\r
+ an optional argument type modifier, h or l or L, where:\r
+           h is a short integer\r
+           l is a long\r
+           L is a long double\r
+\r
+     the data type character that is one of;\r
+\r
+\r
+d      Decimal integer\r
+D      Decimal long integer\r
+o      Octal integer\r
+O      Octal long integer\r
+i      Decimal, octal or hexadecimal integer\r
+I      Decimal, octal or hexadecimal long\r
+       integer\r
+u      Decimal unsigned integer\r
+U      Decimal unsigned long integer\r
+x      Hexadecimal integer\r
+X      Hexadecimal long integer\r
+e      Floating point\r
+f      Floating point\r
+g      Floating point\r
+s      Character string\r
+c      Character\r
+%      % is stored\r
+\r
+An example using scanf();\r
+\r
+\r
+     #include <stdio.h>\r
+     \r
+     main()\r
+     {\r
+          char name[30];\r
+          int age;\r
+     \r
+          printf("\nEnter your name and age ");\r
+          scanf("%30s%d",name,&age);\r
+          printf("\n%s %d",name,age);\r
+     }\r
+\r
+Notice the include line, "#include <stdio.h>", this is to tell the\r
+compiler to also read the file stdio.h that contains the function\r
+prototypes for scanf() and printf().\r
+\r
+\r
+If you type in and run this sample program you will see that only one\r
+name can be entered, that is you can't enter;\r
+\r
+\r
+     JOHN SMITH\r
+\r
+because scanf() detects the whitespace between "JOHN" and "SMITH" and\r
+moves on to the next input field, which is age, and attempts to assign\r
+the value "SMITH" to the age field! The limitations of scanf() as an\r
+input function are obvious.\r
+\r
+An alternative input function is gets() that reads a string of characters\r
+from the stream stdin until a newline character is detected. The newline\r
+character is replaced by a null (0 byte) in the target string. This\r
+function has the advantage of allowing whitespace to be read in. The\r
+following program is a modification to the earlier one, using gets()\r
+instead of scanf().\r
+\r
+\r
+     #include <stdio.h>\r
+     #include <stdlib.h>\r
+     #include <string.h>\r
+     \r
+     main()\r
+     {\r
+          char data[80];\r
+          char *p;\r
+          char name[30];\r
+          int age;\r
+     \r
+          printf("\nEnter your name and age ");\r
+          /* Read in a string of data */\r
+          gets(data);\r
+     \r
+     \r
+          /* P is a pointer to the last character in the input string */\r
+          p = &data[strlen(data) - 1];\r
+     \r
+          /* Remove any trailing spaces by replacing them with null bytes\r
+     */\r
+          while(*p == ' '){\r
+               *p = 0;\r
+               p--;\r
+          }\r
+     \r
+          /* Locate last space in the string */\r
+          p = strrchr(data,' ');\r
+     \r
+          /* Read age from string and convert to an integer */\r
+          age = atoi(p);\r
+     \r
+          /* Terminate data string at start of age field */\r
+          *p = 0;\r
+     \r
+          /* Copy data string to name variable */\r
+          strcpy(name,data);\r
+     \r
+          /* Display results */\r
+          printf("\nName is %s age is %d",name,age);\r
+     }\r
+\r
+\r
+Output\r
+The most common output function is printf() that is very similar to\r
+scanf() except that it writes formatted data out to the standard output\r
+stream stdout.  Printf() takes a list of output data fields and applies\r
+format specifiers to each and outputs the result. The format specifiers\r
+are the same as for scanf() except that flags may be added to the format\r
+specifiers. These flags include;\r
+\r
+\r
+     - Which left justifies the output padding to the right with spaces.\r
+     + Which causes numbers to be prefixed by their sign\r
+\r
+The width specifier is also slightly different for printf(). In its most\r
+useful form is the precision specifier;\r
+\r
+\r
+     width.precision\r
+\r
+So, to print a floating point number to three decimal places you would\r
+use;\r
+\r
+     printf("%.3f",x);\r
+\r
+And to pad a string with spaces to the right or truncate the string to\r
+twenty characters if it is longer, to form a fixed twenty character\r
+width;\r
+\r
+     printf("%-20.20s",data);\r
+\r
+Special character constants may appear in the printf() parameter list.\r
+These are;\r
+\r
+\n         Newline\r
+\r         Carriage return\r
+\t         Tab\r
+\b         Sound the computer's bell\r
+\f         Formfeed\r
+\v         Vertical tab\r
+\\         Backslash character\r
+\'         Single quote\r
+\"         Double quote\r
+\?         Question mark\r
+\O         Octal string\r
+\x         Hexadecimal string\r
+\r
+Thus, to display "Hello World", surrounded in quotation marks and\r
+followed by a newline you would use;\r
+\r
+     printf("\"Hello World\"\n");\r
+\r
+The following program shows how a decimal integer may be displayed as a\r
+decimal, hexadecimal or octal integer. The 04 following the % in the\r
+printf() format tells the compiler to pad the displayed figure to a width\r
+of at least four digits padded with leading zeroes if required.\r
+\r
+\r
+     /* A simple decimal to hexadecimal and octal conversion program */\r
+     \r
+     #include <stdio.h>\r
+     \r
+     main()\r
+     {\r
+          int x;\r
+     \r
+          do\r
+          {\r
+               printf("\nEnter a number, or 0 to end ");\r
+               scanf("%d",&x);\r
+               printf("%04d  %04X  %04o",x,x,x);\r
+          }\r
+          while(x != 0);\r
+     \r
+     }\r
+     \r
+There are associated functions to printf() that you should be aware of.\r
+fprintf() has the prototype;\r
+\r
+     fprintf(FILE *fp,char *format[,argument,...]);\r
+\r
+This variation on printf() simply sends the formatted output to the\r
+specified file stream.\r
+\r
+sprintf() has the function prototype;\r
+\r
+     sprintf(char *s,char *format[,argument,...]);\r
+\r
+and writes the formatted output to a string. You should take care using\r
+sprintf() that the target string has been declared long enough to hold\r
+the result of the sprintf() output. Otherwise other data will be\r
+overwritten in memory.\r
+\r
+An example of using sprintf() to copy multiple data to a string;\r
+\r
+     #include<stdio.h>\r
+     \r
+     int main()\r
+     {\r
+          char buffer[50];\r
+     \r
+          sprintf(buffer,"7 * 5 == %d\n",7 * 5);\r
+     \r
+          puts(buffer);\r
+     }\r
+     \r
+\r
+An alternative to printf() for outputting a simple string to the stream\r
+stdout is puts(). This function sends a string to the stream stdout\r
+followed by a newline character. It is faster than printf(), but far less\r
+flexible. Instead of;\r
+\r
+     printf("Hello World\n");\r
+\r
+You can use;\r
+\r
+     puts("Hello World");\r
+\r
+\r
+\r
+Direct Console I/O\r
+Data may be sent to, and read directly from the console (keyboard and\r
+screen) using the direct console I/O functions. These functions are\r
+prefixed `c'. The direct console I/O equivalent of printf() is then\r
+cprintf(), and the equivalent of puts() is cputs(). Direct console I/O\r
+functions differ from standard I?o functions:\r
+\r
+  1. They do not make use of the predefined streams, and hence may not be\r
+     redirected\r
+  2. They are not portable across operating systems (for example you cant\r
+     use direct console I/O functions in a Windows programme).\r
+  3. They are faster than their standard I/O equivalents\r
+  4. They may not work with all video modes (especially VESA display\r
+     modes).\r
+                                    \r
+                                    \r
+                                    \r
+                                POINTERS\r
+\r
+A pointer is a variable that holds the memory address of an item of data.\r
+Therefore it points to another item. A pointer is declared like an\r
+ordinary variable, but its name is prefixed by `*', thus;\r
+\r
+\r
+     char *p;\r
+\r
+This declares the variable `p' to be a pointer to a character variable.\r
+Pointers are very powerful, and similarly dangerous! If only because a\r
+pointer can be inadvertently set to point to the code segment of a\r
+program and then some value assigned to the address of the pointer!\r
+\r
+The following program illustrates a simple, though fairly useless\r
+application of a pointer;\r
+\r
+\r
+     #include <stdio.h>\r
+     \r
+     main()\r
+     {\r
+          int a;\r
+          int *x;\r
+     \r
+          /* x is a pointer to an integer data type */\r
+     \r
+          a = 100;\r
+          x = &a;\r
+     \r
+          printf("\nVariable 'a' holds the value %d at memory address\r
+     %p",a,x);\r
+     }\r
+\r
+Here is a more useful example of a pointer illustrating how because the\r
+compiler knows the type of data pointed to by the pointer, when the\r
+pointer is incremented it is incremented the correct number of bytes for\r
+the data type.  In this case two bytes;\r
+\r
+     #include <stdio.h>\r
+     \r
+     main()\r
+     {\r
+          int n;\r
+          int a[25];\r
+          int *x;\r
+     \r
+          /* x is a pointer to an integer data type */\r
+     \r
+          /* Assign x to point to array element zero */\r
+          x = a;\r
+     \r
+          /* Assign values to the array */\r
+          for(n = 0; n < 25; n++)\r
+               a[n] = n;\r
+     \r
+     \r
+          /* Now print out all array element values */\r
+          for(n = 0; n < 25; n++ , x++)\r
+               printf("\nElement %d holds %d",n,*x);\r
+     }\r
+     \r
+To read or assign a value to the address held by a pointer you use the\r
+indirection operator `*'. Thus in the above example, to print the value\r
+at the memory address pointed to by variable x I have used `*x'.\r
+\r
+Pointers may be incremented and decremented and have other mathematics\r
+applied to them also. For example in the above program to move variable x\r
+along the array one element at a time we put the statement `x++' in the\r
+for loop. We could move x along two elements by saying `x += 2'. Notice\r
+that this doesn't mean "add 2 bytes to the value of x", but rather it\r
+means "add 2 of the pointer's data type size units to the value of x".\r
+\r
+Pointers are used extensively in dynamic memory allocation. When a\r
+program is running it is often necessary to temporarily allocate a block\r
+of data, say a table, in memory. C provides the function malloc() for\r
+this purpose that follows the general form;\r
+\r
+\r
+     any pointer type = malloc(number_of_bytes);\r
+\r
+malloc() actually returns a void pointer type, which means it can be any\r
+type; integer, character, floating point or whatever. This example\r
+allocates a table in memory for 1000 integers;\r
+\r
+     #include <stdio.h>\r
+     #include <stdlib.h>\r
+     \r
+     main()\r
+     {\r
+          int *x;\r
+          int n;\r
+     \r
+          /* x is a pointer to an integer data type */\r
+     \r
+          /* Create a 1000 element table, sizeof() returns the compiler\r
+     */\r
+          /* specific number of bytes used to store an integer */\r
+     \r
+          x = malloc(1000 * sizeof(int));\r
+     \r
+     \r
+          /* Check to see if the memory allocation succeeded */\r
+          if (x == NULL)\r
+          {\r
+               printf("\nUnable to allocate a 1000 element integer\r
+     table");\r
+               exit(0);\r
+          }\r
+     \r
+          /* Assign values to each table element */\r
+          for(n = 0; n < 1000; n++)\r
+          {\r
+               *x = n;\r
+               x++;\r
+          }\r
+     \r
+          /* Return x to the start of the table */\r
+          x -= 1000;\r
+     \r
+          /* Display the values in the table */\r
+          for(n = 0; n < 1000; n++){\r
+               printf("\nElement %d holds a value of %d",n,*x);\r
+               x++;\r
+          }\r
+          /* Deallocate the block of memory now it's no longer required\r
+     */\r
+          free(x);\r
+     }\r
+     \r
+Pointers are also extensively used with character arrays, called strings.\r
+Since all C program strings are terminated by a zero byte we can count\r
+the letters in a string using a pointer;\r
+\r
+     #include <stdio.h>\r
+     #include <string.h>\r
+     \r
+     main()\r
+     {\r
+          char *p;\r
+          char text[100];\r
+          int len;\r
+     \r
+          /* Initialise variable 'text' with some writing */\r
+          strcpy(text,"This is a string of data");\r
+     \r
+          /* Set variable p to the start of variable text */\r
+          p = text;\r
+     \r
+          /* Initialise variable len to zero */\r
+          len = 0;\r
+     \r
+          /* Count the characters in variable text */\r
+          while(*p)\r
+          {\r
+               len++;\r
+               p++;\r
+          }\r
+     \r
+          /* Display the result */\r
+          printf("\nThe string of data has %d characters in it",len);\r
+     }\r
+     \r
+\r
+The 8088/8086 group of CPUs, as used in the IBM PC, divide memory into\r
+64K segments. To address all 1Mb of memory a 20 bit number is used\r
+comprised of an `offset' to and a 64K `segment'. The IBM PC uses special\r
+registers called "segment registers" to record the segments of addresses.\r
+\r
+This leads the C language on the IBM PC to have three new keywords; near,\r
+far and huge.\r
+\r
+near pointers are 16 bits wide and access only data within the current\r
+segment.\r
+\r
+far pointers are comprised of an offset and a segment address allowing\r
+them to access data any where in memory, but arithmetic with far pointers\r
+is fraught with danger! When a value is added or subtracted from a far\r
+pointer it is only actualy the segment part of the pointer that is\r
+affected, thus leading to the situation where a far pointer being\r
+incremented wraps around on its own offset 64K segment.\r
+\r
+huge pointers are a variation on the far pointer that can be successfuly\r
+incremented and decremented through the entire 1Mb range since the\r
+compiler generates code to amend the offset as applicable.\r
+\r
+It will come as no surprise that code using near pointers is executed\r
+faster than code using far pointers, which in turn is faster than code\r
+using huge pointers.\r
+\r
+to give a literal address to a far pointer IBM PC C compilers provide a\r
+macro MK-FP() that has the prototype;\r
+\r
+\r
+     void far *MK_FP(unsigned segment, unsigned offset);\r
+\r
+For example, to create a far pointer to the start of video memory on a\r
+colour IBM PC system you could use;\r
+\r
+          screen = MK_FP(0xB800,0);\r
+\r
+Two coressponding macros provided are;\r
+\r
+FP_SEG() and FP_OFF()\r
+\r
+Which return the segment and offset respectively of a far pointer. The\r
+following example uses FP_SEG() and FP_OFF() to send segment and offset\r
+addresses to a DOS call to create a new directory path;\r
+\r
+\r
+     #include <dos.h>\r
+     \r
+     int makedir(char *path)\r
+     {\r
+          /* Make sub directory, returning non zero on success */\r
+     \r
+          union REGS inreg,outreg;\r
+          struct SREGS segreg;\r
+     \r
+          inreg.h.ah = 0x39;\r
+          segreg.ds = FP_SEG(path);\r
+          inreg.x.dx = FP_OFF(path);\r
+          intdosx(&inreg,&outreg,&segreg);\r
+     \r
+          return(1 - outreg.x.cflag);\r
+     }\r
+     \r
+\r
+Finally, the CPU's segment registers can be read with the function\r
+`segread()'. This is a function unique to C compilers for the the 8086\r
+family of processors. segread() has the function prototype:\r
+\r
+\r
+     void segread(struct SREGS *segp);\r
+\r
+It returns the current values of the segment registers in the SREGS type\r
+structure pointed to by the pointer `segp'. For example:\r
+\r
+     #include <dos.h>\r
+     \r
+     main()\r
+     {\r
+          struct SREGS sregs;\r
+     \r
+          segread(&sregs);\r
+          printf("\nCode segment is currently at %04X, Data segment is at\r
+     %04X",sregs.cs,sregs.ds);\r
+          printf("\nExtra segment is at %04X, Stack segment is at\r
+     %04X",sregs.es,sregs.ss);\r
+     }\r
+                                    \r
+                               STRUCTURES\r
+\r
+\r
+C provides the means to group together variables under one name providing\r
+a convenient means of keeping related information together and providing\r
+a structured approach to data.\r
+\r
+The general form for a structure definition is;\r
+\r
+\r
+     typedef struct\r
+     {\r
+          variable_type variable_name;\r
+          variable_type variable_name;\r
+     }\r
+     structure_name;\r
+\r
+\r
+When accessing data files, with a fixed record structure, the use of a\r
+structure variable becomes essential. The following example shows a\r
+record structure for a very simple name and address file. It declares a\r
+data structure called `data', and comprised of six fields; `name',\r
+`address', `town', `county' , `post' and `telephone'.\r
+\r
+\r
+     typedef struct\r
+     {\r
+          char name[30];\r
+          char address[30];\r
+          char town[30];\r
+          char county[30];\r
+          char post[12];\r
+          char telephone[15];\r
+     }\r
+     data;\r
+\r
+The structure 'data' may then be used in the program as a variable data\r
+type for declaring variables;\r
+\r
+     data record;\r
+\r
+Thus declares a variable called 'record' to be of type 'data'.\r
+\r
+The individual fields of the structure variable are accessed by the\r
+general form;\r
+\r
+     structure_variable.field_name;\r
+\r
+Thus, the name field of the structure variable record is accessed with;\r
+\r
+     record.name\r
+\r
+There is no limit to the number of fields that may comprise a structure,\r
+nor do the fields have to be of the same types, for example;\r
+\r
+\r
+     typedef struct\r
+     {\r
+          char name[30];\r
+          int age;\r
+          char *notes;\r
+     }\r
+     dp;\r
+\r
+Declares a structure 'dp' that is comprised of a character array field,\r
+an integer field and a character pointer field.\r
+\r
+A structure variable may be passed as a parameter by passing the address\r
+of the variable as the parameter with the & operator thus;\r
+\r
+     func(&my_struct)\r
+\r
+\r
+The following is an example program that makes use of a structure to\r
+provide the basic access to the data in a simple name and address file;\r
+\r
+\r
+/* A VERY simple address book written in ANSI C */\r
+\r
+#include <stdio.h>\r
+#include <stdlib.h>\r
+#include <io.h>\r
+#include <string.h>\r
+#include <fcntl.h>\r
+#include <sys\stat.h>\r
+\r
+/* num_lines is the number of screen display lines */\r
+#define num_lines   25\r
+\r
+\r
+typedef struct\r
+{\r
+     char name[30];\r
+     char address[30];\r
+     char town[30];\r
+     char county[30];\r
+     char post[12];\r
+     char telephone[15];\r
+}\r
+data;\r
+\r
+data record;\r
+int handle;\r
+\r
+\r
+/* Function prototypes */\r
+\r
+void ADD_REC(void);\r
+void CLS(void);\r
+void DISPDATA(void);\r
+void FATAL(char *);\r
+void GETDATA(void);\r
+void MENU(void);\r
+void OPENDATA(void);\r
+int SEARCH(void);\r
+\r
+void CLS()\r
+{\r
+     int n;\r
+\r
+     for(n = 0; n < num_lines; n++)\r
+          puts("");\r
+}\r
+\r
+void FATAL(char *error)\r
+{\r
+     printf("\nFATAL ERROR: %s",error);\r
+     exit(0);\r
+}\r
+\r
+void OPENDATA()\r
+{\r
+     /* Check for existence of data file and if not create it */\r
+     /* otherwise open it for reading/writing at end of file */\r
+\r
+     handle = open("address.dat",O_RDWR|O_APPEND,S_IWRITE);\r
+\r
+     if (handle == -1)\r
+     {\r
+          handle = open("address.dat",O_RDWR|O_CREAT,S_IWRITE);\r
+          if (handle == -1)\r
+               FATAL("Unable to create data file");\r
+     }\r
+}\r
+\r
+void GETDATA()\r
+{\r
+     /* Get address data from operator */\r
+\r
+     CLS();\r
+\r
+     printf("Name ");\r
+     gets(record.name);\r
+     printf("\nAddress ");\r
+     gets(record.address);\r
+     printf("\nTown ");\r
+     gets(record.town);\r
+     printf("\nCounty ");\r
+     gets(record.county);\r
+     printf("\nPost Code ");\r
+     gets(record.post);\r
+     printf("\nTelephone ");\r
+     gets(record.telephone);\r
+}\r
+\r
+void DISPDATA()\r
+{\r
+     /* Display address data */\r
+     char text[5];\r
+\r
+     CLS();\r
+\r
+     printf("Name %s",record.name);\r
+     printf("\nAddress %s",record.address);\r
+     printf("\nTown %s",record.town);\r
+     printf("\nCounty %s",record.county);\r
+     printf("\nPost Code %s",record.post);\r
+     printf("\nTelephone %s\n\n",record.telephone);\r
+\r
+     puts("Press RETURN to continue");\r
+     gets(text);\r
+}\r
+\r
+void ADD_REC()\r
+{\r
+     /* Insert or append a new record to the data file */\r
+     int result;\r
+\r
+     result = write(handle,&record,sizeof(data));\r
+\r
+     if (result == -1)\r
+          FATAL("Unable to write to data file");\r
+}\r
+\r
+int SEARCH()\r
+{\r
+     char text[100];\r
+     int result;\r
+\r
+     printf("Enter data to search for ");\r
+     gets(text);\r
+     if (*text == 0)\r
+          return(-1);\r
+\r
+     /* Locate start of file */\r
+     lseek(handle,0,SEEK_SET);\r
+\r
+     do\r
+     {\r
+          /* Read record into memory */\r
+          result = read(handle,&record,sizeof(data));\r
+          if (result > 0)\r
+          {\r
+               /* Scan record for matching data */\r
+               if (strstr(record.name,text) != NULL)\r
+                    return(1);\r
+               if (strstr(record.address,text) != NULL)\r
+                    return(1);\r
+               if (strstr(record.town,text) != NULL)\r
+                    return(1);\r
+               if (strstr(record.county,text) != NULL)\r
+                    return(1);\r
+               if (strstr(record.post,text) != NULL)\r
+                    return(1);\r
+               if (strstr(record.telephone,text) != NULL)\r
+                    return(1);\r
+          }\r
+     }\r
+     while(result > 0);\r
+     return(0);\r
+}\r
+\r
+void MENU()\r
+{\r
+     int option;\r
+     char text[10];\r
+\r
+     do\r
+     {\r
+          CLS();\r
+          puts("\n\t\t\tSelect Option");\r
+          puts("\n\n\t\t\t1 Add new record");\r
+          puts("\n\n\t\t\t2 Search for data");\r
+          puts("\n\n\t\t\t3 Exit");\r
+          puts("\n\n\n\n\n");\r
+          gets(text);\r
+          option = atoi(text);\r
+\r
+          switch(option)\r
+          {\r
+               case 1 : GETDATA();\r
+                     /* Go to end of file to append new record */\r
+                     lseek(handle,0,SEEK_END);\r
+                     ADD_REC();\r
+                     break;\r
+\r
+               case 2 : if (SEARCH())\r
+                          DISPDATA();\r
+                     else\r
+                     {\r
+                          puts("NOT FOUND!");\r
+                          puts("Press RETURN to continue");\r
+                          gets(text);\r
+                     }\r
+                     break;\r
+\r
+               case 3 : break;\r
+          }\r
+     }\r
+     while(option != 3);\r
+}\r
+\r
+void main()\r
+{\r
+     CLS();\r
+     OPENDATA();\r
+     MENU();\r
+}\r
+\r
+Bit Fields\r
+\r
+C allows the inclusion of variables with a size of less than eight bits\r
+to be included in structures. These variables are known as bit fields,\r
+and may be any declared size from one bit upwards.\r
+\r
+The general form for declaring a bit field is;\r
+\r
+\r
+     type name : number_of_bits;\r
+\r
+\r
+For example, to declare a set of status flags, each occupying one bit;\r
+\r
+typedef struct\r
+{\r
+     unsigned carry  : 1;\r
+     unsigned zero   : 1;\r
+     unsigned over   : 1;\r
+     unsigned parity : 1;\r
+}\r
+df;\r
+\r
+df flags;\r
+\r
+The variable `flags' then occupies only four bits in memory, and yet is\r
+comprised of four variables that may be accessed like any other structure\r
+field.\r
+\r
+\r
+\r
+Uunions\r
+\r
+Another facility provided by C for efficient use of available memory is\r
+the union structure. A union structure is a collection of variables that\r
+all share the same memory storage address. As such only one of the\r
+variables is ever accessible at a time.\r
+\r
+The general form of a union definition is;\r
+\r
+\r
+     union name\r
+     {\r
+          type variable_name;\r
+          type variable_name;\r
+          .\r
+          .\r
+          .\r
+          type variable_name;\r
+     };\r
+\r
+Thus, to declare a union structure for two integer variables;\r
+\r
+     union data\r
+     {\r
+          int vara;\r
+          int varb;\r
+     };\r
+\r
+and to declare a variable of type 'data';\r
+\r
+     data my_var;\r
+\r
+The variable 'my_var' is then comprised of the two variables 'vara' and\r
+'varb'  that are accessed like with any form of structure, eg;\r
+\r
+     my_var.vara = 5;\r
+\r
+Assigns a value of 5 to the variable 'vara' of union 'my_var'.\r
+\r
+\r
+Enumerations\r
+\r
+An enumeration assigns ascending integer values to a list of symbols. An\r
+enumeration declaration takes the general form;\r
+\r
+\r
+     enum name { enumeration list } variable_list;\r
+\r
+Thus to define a symbol list of colours, you can say;\r
+\r
+     enum COLOURS\r
+     {\r
+          BLACK,\r
+          BLUE,\r
+          GREEN,\r
+          CYAN,\r
+          RED,\r
+          MAGENTA,\r
+          BROWN,\r
+          LIGHTGRAY,\r
+          DARKGRAY,\r
+          LIGHTBLUE,\r
+          LIGHTGREEN,\r
+          LIGHTCYAN,\r
+          LIGHTRED,\r
+          LIGHTMAGENTA,\r
+          YELLOW,\r
+          WHITE\r
+     };\r
+\r
+Then, the number zero may be referred to by the symbol BLACK, the number\r
+one by the symbol BLUE, the number two by the symbol GREEN and so on.\r
+\r
+The following program illustrates the use of an enumeration list to\r
+symbolise integers;\r
+\r
+     #include <stdio.h>\r
+     \r
+     enum COLOURS\r
+     {\r
+          BLACK,\r
+          BLUE,\r
+          GREEN,\r
+          CYAN,\r
+          RED,\r
+          MAGENTA,\r
+          BROWN,\r
+          LIGHTGRAY,\r
+          DARKGRAY,\r
+          LIGHTBLUE,\r
+          LIGHTGREEN,\r
+          LIGHTCYAN,\r
+          LIGHTRED,\r
+          LIGHTMAGENTA,\r
+          YELLOW,\r
+          WHITE\r
+     };\r
+     \r
+     \r
+     void main()\r
+     {\r
+          int x;\r
+     \r
+          x = RED;\r
+     \r
+          printf("\nVariable 'x' holds %d",x);\r
+     \r
+     }\r
+                                    \r
+                                FILE I/O\r
+\r
+\r
+C provides buffered file streams for file access. Some C platforms, such\r
+as Unix and DOS provide unbuffered file handles as well.\r
+\r
+\r
+\r
+Buffered streams\r
+\r
+Buffered streams are accessed through a variable of type 'file pointer'.\r
+The data type FILE is defined in the header file stdio.h. Thus to declare\r
+a file pointer;\r
+\r
+     #include <stdio.h>\r
+\r
+     FILE *ptr;\r
+\r
+To open a stream C provides the function fopen(), which accepts two\r
+parameters, the name of the file to be opened, and the access mode for\r
+the file to be opened with. The access mode may be any one of;\r
+\r
+\r
+Mode   Description\r
+       \r
+r      Open for reading\r
+w      Create for writing, destroying any\r
+       existing file\r
+a      Open for append, creating a new file\r
+       if it doesn't\r
+        exist\r
+r+      Open an existing file for reading\r
+       and writing\r
+w+      Create for reading and writing,\r
+       destroying any\r
+        existing file\r
+a+      Open for append, creating a new file\r
+       if it doesn't exist.\r
+\r
+\r
+Optionaly either `b' or `t' may be appended for binary or text mode. If\r
+neither is appended, then the file stream will be opened in the mode\r
+described by the global variable _fmode. Data read or written from file\r
+streams opened in text mode undergoes conversion, that is the characters\r
+CR and LF are converted to CR LF pairs on writing, and the CR LF pair is\r
+converted to a single LF on reading. File streams opened in binary mode\r
+do not undergo conversion.\r
+\r
+\r
+If fopen() fails to open the file, it returns a value of NULL (defined in\r
+stdio.h) to the file pointer.\r
+\r
+Thus, the following program will create a new file called "data.txt" and\r
+open it for reading and writing;\r
+\r
+     #include <stdio.h>\r
+     \r
+     void main()\r
+     {\r
+          FILE *fp;\r
+     \r
+          fp = fopen("data.txt","w+");\r
+     \r
+     }\r
+\r
+To close a stream C provides the function fclose(), which accepts the\r
+stream's file pointer as a parameter;\r
+\r
+     fclose(fp);\r
+\r
+If an error occurs in closing the file stream, fclose() returns non zero.\r
+\r
+There are four basic functions for receiving and sending data to and from\r
+streams; fgetc(), fputc(), fgets() and fputs().\r
+\r
+fgetc() simply reads a single character from the specified input stream;\r
+\r
+     char fgetc(FILE *fp);\r
+\r
+Its opposite number is fputc(), which simply writes a single character to\r
+the specified input stream;\r
+\r
+     char fputc(char c, FILE *fp);\r
+\r
+fgets() reads a string from the input stream;\r
+\r
+     char *fgets(char s, int numbytes, FILE *fp);\r
+\r
+It stops reading when either numbytes - 1 bytes have been read, or a\r
+newline character is read in. A null terminating byte is appended to the\r
+read string, s. If an error occurs, fgets() returns NULL.\r
+\r
+\r
+fputs() writes a null terminated string to a stream;\r
+\r
+     int fputs(char *s, FILE *fp);\r
+\r
+Excepting fgets(), which returns a NULL pointer if an error occurs, all\r
+the other functions described above return EOF (defined in stdio.h) if an\r
+error occurs during the operation.\r
+\r
+\r
+The following program creates a copy of the file "data.dat" as "data.old"\r
+and illustrates the use of fopen(), fgetc(), fputc() and fclose();\r
+\r
+\r
+     #include <stdio.h>\r
+     \r
+     int main()\r
+     {\r
+          FILE *in;\r
+          FILE *out;\r
+     \r
+          in = fopen("data.dat","r");\r
+     \r
+          if (in == NULL)\r
+          {\r
+               puts("\nUnable to open file data.dat for reading");\r
+               return(0);\r
+          }\r
+     \r
+          out = fopen("data.old","w+");\r
+     \r
+          if (out == NULL)\r
+          {\r
+               puts("\nUnable to create file data.old");\r
+               return(0);\r
+          }\r
+     \r
+          /* Loop reading and writing one byte at a time until end-of-\r
+     file */\r
+          while(!feof(in))\r
+               fputc(fgetc(in),out);\r
+     \r
+          /* Close the file streams */\r
+          fclose(in);\r
+          fclose(out);\r
+     \r
+          return(0);\r
+     }\r
+\r
+Example program using fputs() to copy text from stream stdin (usually\r
+typed in at the keyboard) to a new file called "data.txt".\r
+\r
+     #include <stdio.h>\r
+     \r
+     int main()\r
+     {\r
+          FILE *fp;\r
+          char text[100];\r
+     \r
+          fp = fopen("data.txt","w+");\r
+     \r
+          do\r
+          {\r
+               gets(text);\r
+               fputs(text,fp);\r
+          }\r
+          while(*text);\r
+     \r
+          fclose(fp);\r
+     }\r
+\r
+\r
+Random access using streams\r
+\r
+Random file access for streams is provided for by the fseek() function\r
+that has the following prototype;\r
+\r
+     int fseek(FILE *fp, long numbytes, int fromwhere);\r
+\r
+fseek() repositions a file pointer associated with a stream previously\r
+opened by a call to fopen(). The file pointer is positioned `numbytes'\r
+from the location `fromwhere', which may be the file beginning, the\r
+current file pointer position, or the end of the file, symbolised by the\r
+constants SEEK_SET, SEEK_CUR and SEEK_END respectively. If a call to\r
+fseek() succeeds, a value of zero is returned.\r
+\r
+\r
+Associated with fseek() is ftell(), which reports the current file\r
+pointer position of a stream, and has the following function prototype;\r
+\r
+\r
+     long int ftell(FILE *fp);\r
+\r
+ftell() returns either the position of the file pointer, measured in\r
+bytes from the start of the file, or -1 upon an error occurring.\r
+\r
+\r
+\r
+Handles\r
+\r
+File handles are opened with the open() function that has the prototype;\r
+\r
+     int open(char *filename,int access[,unsigned mode]);\r
+\r
+If open() is successful, the number of the file handle is returned.\r
+Otherwise open() returns -1.\r
+\r
+The access integer is comprised from bitwise oring together of the\r
+symbolic constants declared in fcntl.h. These vary from compiler to\r
+compiler but may be;\r
+\r
+\r
+     O_APPEND       If set, the file pointer will be set to the end of\r
+the\r
+                    file prior to each write.\r
+     O_CREAT        If the file does not exist it is created.\r
+     O_TRUNC        Truncates the existing file to a length of zero\r
+bytes.\r
+     O_EXCL          Used with O_CREAT\r
+     O_BINARY       Opens the file in binary mode\r
+     O_TEXT         Opens file in text mode\r
+\r
+The optional mode parameter is comprised by bitwise oring of the symbolic\r
+constants defined in stat.h. These vary from C compiler to C compiler but\r
+may be;\r
+\r
+     S_IWRITE       Permission to write\r
+     S_IREAD        Permission to read\r
+\r
+\r
+Once a file handle has been assigned with open(), the file may be\r
+accessed with read() and write().\r
+\r
+Read() has the function prototype;\r
+\r
+     int read(int handle, void *buf, unsigned num_bytes);\r
+\r
+It attempts to read 'num_bytes' and returns the number of bytes actually\r
+read from the file handle 'handle', and stores these bytes in the memory\r
+block pointed to by 'buf'.\r
+\r
+Write() is very similar to read() and has the same function prototype and\r
+return values, but writes 'num_bytes' from the memory block pointed to by\r
+'buf'.\r
+\r
+Files opened with open() are closed using close() that has the function\r
+prototype;\r
+\r
+     int close(int handle);\r
+\r
+close() returns zero on success, and -1 if an error occurs trying to\r
+close the handle.\r
+\r
+Random access is provided by lseek(), which is very similar to fseek(),\r
+except that it accepts an integer file handle as the first parameter\r
+rather than a stream FILE pointer.\r
+\r
+This example uses file handles to read data from stdin (usually the\r
+keyboard) and copy the text to a new file called "data.txt".\r
+\r
+     #include <io.h>\r
+     #include <fcntl.h>\r
+     #include <sys\stat.h>\r
+     \r
+     int main()\r
+     {\r
+          int handle;\r
+          char text[100];\r
+     \r
+          handle = open("data.txt",O_RDWR|O_CREAT|O_TRUNC,S_IWRITE);\r
+     \r
+          do\r
+          {\r
+               gets(text);\r
+               write(handle,&text,strlen(text));\r
+          }\r
+          while(*text);\r
+     \r
+          close(handle);\r
+     }\r
+\r
+\r
+\r
+Advanced File I/O\r
+\r
+The ANSI standard on C defines file IO as by way of file streams, and\r
+defines various functions for file access;\r
+\r
+\r
+fopen() has the prototype;\r
+\r
+     FILE *fopen(const char *name,const char *mode);\r
+\r
+fopen() attempts to open a stream to a file name in a specified mode. If\r
+successful a FILE type pointer is returned to the file stream, if the\r
+call fails NULL is returned. The mode string can be one of the following;\r
+\r
+\r
+Mode      Description\r
+ r        Open for reading only\r
+ w        Create for writing, overwriting any existing file with the same\r
+          name.\r
+ a        Open for append (writing at end of file) or create the file if\r
+it\r
+          does not exist.\r
+ r+       Open an existing file for reading and writing.\r
+ w+       Create a new file for reading and writing.\r
+ a+       Open for append with read and write access.\r
+\r
+\r
+fclose() is used to close a file stream previously opened by a call to\r
+fopen(). It has the prototype;\r
+\r
+     int fclose(FILE *fp);\r
+\r
+When a call to fclose() is successful, all buffers to the stream are\r
+flushed and a value of zero is returned. If the call fails fclose()\r
+returns EOF.\r
+\r
+Many host computers, and the IBM PC is no exception, use buffered file\r
+access, that is when writing to a file stream the data is stored in\r
+memory and only written to the stream when it exceeds a predefined number\r
+of bytes. A power failure occurring before the data has been written to\r
+the stream will result in the data never being written, so the function\r
+fflush() can be called to force all pending data to be written. fflush()\r
+has the prototype;\r
+\r
+     int fflush(FILE *fp);\r
+\r
+When a call to fflush() is successful, the buffers connected with the\r
+stream are flushed and a value of zero is returned. On failure fflush()\r
+returns EOF.\r
+\r
+The location of the file pointer connected with a stream can be\r
+determined with the function ftell(). ftell() has the prototype;\r
+\r
+\r
+     long int ftell(FILE *fp);\r
+\r
+and returns the offset of the file pointer in bytes from the start of the\r
+file, or -1L if the call fails.\r
+\r
+Similarly, you can move the file pointer to a new position with fseek().\r
+fseek() has the prototype;\r
+\r
+     int fseek(FILE *fp, long offset, int from_what_place);\r
+\r
+fseek() attempts to move the file pointer, 'fp' 'offset' bytes from the\r
+position 'from_what_place'. 'from_what_place' is predefined as one of;\r
+\r
+     SEEK_SET       The file's beginning\r
+     SEEK_CUR       The file pointer's current position\r
+     SEEK_END       End of file\r
+\r
+The offset may be a positive value to move the file pointer on through\r
+the file, or negative to move backwards.\r
+\r
+To move a file pointer quickly back to the start of a file, and clear any\r
+references to errors that have occurred C provides the function rewind()\r
+that has the prototype;\r
+\r
+     void rewind(FILE *fp);\r
+\r
+rewind(fp) is similar to fseek(fp,0L,SEEK_SET) in that they both set the\r
+file pointer to the start of the file, but whereas fseek() clears the EOF\r
+error marker, rewind() clears all error indicators.\r
+\r
+Errors occurring with file functions can be checked with the function\r
+ferror() that has the prototype;\r
+\r
+     int ferror(FILE *fp);\r
+\r
+ferror() returns a nonzero value if an error has occurred on the\r
+specified stream. After checking ferror() and reporting any errors you\r
+should clear the error indicators, and this can be done by a call to\r
+clearerr() that has the prototype;\r
+\r
+     void clearerr(FILE *fp);\r
+\r
+The condition of reaching end of file (EOF) can be tested for with the\r
+predefined macro feof() that has the prototype;\r
+\r
+     int feof(FILE *fp);\r
+\r
+feof() returns a nonzero value if an end of file error indicator was\r
+detected on the specified file stream, and zero if the end of file has\r
+not yet been reached.\r
+\r
+Reading data from a file stream can be achieved using several functions;\r
+A single character can be read with fgetc() that has the prototype;\r
+\r
+\r
+     int fgetc(FILE *fp);\r
+\r
+fgetc() returns either the character read converted to an integer, or EOF\r
+if an error occurred.\r
+\r
+Reading a string of data is achieved with fgets(). fgets() attempts to\r
+read a string terminated by a newline character and of no more than a\r
+specified number of bytes from the file stream. It has the prototype;\r
+\r
+\r
+     char *fgets(char s, int n, FILE *fp);\r
+\r
+A successful call to fgets() results in a string being stored in `s' that\r
+is either terminated by a newline character, or that is `n' - 1\r
+characters long, which ever came first. The newline character is retained\r
+by fgets(), and a null bytes is appended to the string. If the call fails\r
+a NULL pointer is returned.\r
+\r
+\r
+Strings may be written to a stream using fputs() that has the prototype;\r
+\r
+     int fputs(const char *s,FILE *fp);\r
+\r
+fputs() writes all the characters in the string `s' to the stream `fp'\r
+except the null terminating byte. On success, fputs() returns the last\r
+character written, on failure it returns EOF.\r
+\r
+To write a single character to a stream use fputc() that has the\r
+prototype;\r
+\r
+     int fputc(int c,FILE *fp);\r
+\r
+If successful, fputc() returns the character written, otherwise it\r
+returns EOF.\r
+\r
+To read a large block of data, or a record from a stream you can use\r
+fread() that has the prototype;\r
+\r
+     size_t fread(void *ptr,size_t size, size_t n, FILE *fp);\r
+\r
+fread() attempts to read 'n' items, each of length 'size' from the file\r
+stream 'fp' into the block of memory pointed to by 'ptr'. To check the\r
+success or failure status of fread() use ferror().\r
+\r
+The sister function to fread() is fwrite() that has the prototype;\r
+\r
+     size_t fwrite(const void *ptr,size_t size, size_t n,FILE *fp);\r
+\r
+that writes 'n' items each of length 'size' from the memory area pointed\r
+to by 'ptr' to the specified stream 'fp'.\r
+\r
+Formatted input from a stream is achieved with fscanf() that has the\r
+prototype;\r
+\r
+     int fscanf(FILE *fp, const char *format[,address ...]);\r
+\r
+fscanf() returns the number of fields successfully stored, and EOF on end\r
+of file. This short example shows how fscanf() is quite useful for\r
+reading numbers from a stream, but hopeless when it comes to strings!\r
+\r
+     #include <stdio.h>\r
+     \r
+     void main()\r
+     {\r
+          FILE *fp;\r
+          int a;\r
+          int b;\r
+          int c;\r
+          int d;\r
+          int e;\r
+          char text[100];\r
+     \r
+          fp = fopen("data.txt","w+");\r
+     \r
+          if(!fp)\r
+          {\r
+               perror("Unable to create file");\r
+               exit(0);\r
+          }\r
+     \r
+          fprintf(fp,"1 2 3 4 5 \"A line of numbers\"");\r
+     \r
+          fflush(fp);\r
+     \r
+          if (ferror(fp))\r
+          {\r
+               fputs("Error flushing stream",stderr);\r
+               exit(1);\r
+          }\r
+     \r
+          rewind(fp);\r
+          if (ferror(fp))\r
+          {\r
+               fputs("Error rewind stream",stderr);\r
+               exit(1);\r
+          }\r
+     \r
+          fscanf(fp,"%d %d %d %d %d %s",&a,&b,&c,&d,&e,text);\r
+          if (ferror(fp))\r
+          {\r
+               fputs("Error reading from stream",stderr);\r
+               exit(1);\r
+          }\r
+     \r
+          printf("\nfscanf() returned %d %d %d %d %d %s",a,b,c,d,e,text);\r
+     }\r
+     \r
+As you can see from the example, fprintf() can be used to write formatted\r
+data to a stream.\r
+\r
+If you wish to store the position of a file pointer on a stream, and then\r
+later restore it to the same position you can use the functions fgetpos()\r
+and fsetpos(). fgetpos() reads the current location of the file pointer\r
+and has the prototype;\r
+\r
+     int fgetpos(FILE *fp, fpos_t *pos);\r
+\r
+fsetpos() repositions the file pointer and has the prototype;\r
+\r
+     int fsetpos(FILE *fp, const fpos_t *fpos);\r
+\r
+fpos_t is defined in stdio.h.\r
+\r
+These functions are more convenient than doing an ftell() followed by an\r
+fseek().\r
+\r
+An open stream can have a new file associated with it in place of the\r
+existing file by using the function freopen() that has the prototype;\r
+\r
+     FILE *freopen(const char *name,const char *mode,FILE *fp);\r
+\r
+freopen() closes the existing stream and then attempts to reopens it with\r
+the specified file name. This is useful for redirecting the predefined\r
+streams stdin, stdout and stderr to a file or device.\r
+\r
+For example; if you wish to redirect all output intended to stdout\r
+(usually the host computer's display device) to a printer you might use;\r
+\r
+     freopen("LPT1","w",stdout);\r
+\r
+where LPT1 is the name of the printer device (on a PC host, LPT1 is the\r
+name of the parallel port).\r
+\r
+\r
+Predefined I/O Streams\r
+\r
+There are three predefined I/O streams; stdin, stdout, and stderr. The\r
+streams stdin and stdout default to the keyboard and display\r
+respectively, but can be redirected on some hardware platforms, such as\r
+the PC and under UNIX. The stream stderr defaults to the display and is\r
+not usually redirected by the operator. Thus it can be used for the\r
+display of error messages even when program output has been redirected,\r
+such as with;\r
+\r
+      fputs("Error message",stderr);\r
+\r
+The functions printf() and puts(), output data to the stream stdout, and\r
+can therefore be redirected by the operator of the program. scanf() and\r
+gets() accept input from the stream stdin.\r
+\r
+As an example of file i/o with the PC consider the following short\r
+program that does a hex dump of a specified file to the predefined stream\r
+stdout, which may be redirected to a file using;\r
+\r
+          dump filename.ext > target.ext\r
+\r
+     #include <stdio.h>\r
+     #include <fcntl.h>\r
+     #include <io.h>\r
+     #include <string.h>\r
+     \r
+     main(int argc, char *argv[])\r
+     {\r
+          unsigned counter;\r
+          unsigned char v1[20];\r
+          int f1;\r
+          int x;\r
+          int n;\r
+     \r
+          if (argc != 2)\r
+          {\r
+               fputs("\nERROR: Syntax is dump f1\n",stderr);\r
+               return(1);\r
+          }\r
+     \r
+          f1 = open(argv[1],O_RDONLY);\r
+     \r
+          if (f1 == -1)\r
+          {\r
+               fprintf(stderr,"\nERROR: Unable to open %s\n",argv[1]);\r
+               return(1);\r
+          }\r
+     \r
+          fprintf(stdout,"\nDUMP OF FILE %s\n\n",strupr(argv[1]));\r
+     \r
+          counter = 0;\r
+     \r
+          while(1)\r
+          {\r
+               /* Set buffer to zero bytes */\r
+               memset(v1,0,20);\r
+     \r
+               /* Read buffer from file */\r
+               x = _read(f1,&v1,16);\r
+     \r
+               /* x will be 0 on EOF or -1 on error */\r
+               if (x < 1)\r
+                    break;\r
+     \r
+               /* Print file offset to stdout */\r
+               fprintf(stdout,"%06d(%05x) ",counter,counter);\r
+     \r
+               counter += 16;\r
+     \r
+               /* print hex values of buffer to stdout */\r
+               for(n = 0; n < 16; n++)\r
+                    fprintf(stdout,"%02x ",v1[n]);\r
+     \r
+               /* Print ascii values of buffer to stdout */\r
+               for(n = 0; n < 16; n++)\r
+               {\r
+                    if ((v1[n] > 31) && (v1[n] < 128))\r
+                         fprintf(stdout,"%c",v1[n]);\r
+                    else\r
+                         fputs(".",stdout);\r
+               }\r
+     \r
+               /* Finish the line with a new line */\r
+               fputs("\n",stdout);\r
+          }\r
+     \r
+          /* successful termination */\r
+          return(0);\r
+     }\r
+                                    \r
+                                 STRINGS\r
+\r
+The C language has one of the most powerful string handling capabilities\r
+of any general purpose computer language.\r
+\r
+A string is a single dimension array of characters terminated by a zero\r
+byte.\r
+\r
+Strings may be initialised in two ways. Either in the source code where\r
+they may be assigned a constant value, as in;\r
+\r
+     int main()\r
+     {\r
+          char *p = "System 5";\r
+          char name[] = "Test Program" ;\r
+     }\r
+\r
+or at run time by the function strcpy() that has the function prototype;\r
+\r
+     char *strcpy(char *destination, char *source);\r
+\r
+strcpy() copies the string pointed to by source into the location pointed\r
+to by destination as in the following example;\r
+\r
+\r
+     #include<stdio.h>\r
+     \r
+     int main()\r
+     {\r
+          char name[50];\r
+     \r
+          strcpy(name,"Servile Software");\r
+     \r
+          printf("\nName equals %s",name);\r
+     }\r
+\r
+C also allows direct access to each individual byte of the string, so the\r
+following is quite permissible;\r
+\r
+     #include<stdio.h>\r
+     \r
+     int main()\r
+     {\r
+          char name[50];\r
+     \r
+          strcpy(name,"Servile Software");\r
+     \r
+          printf("\nName equals %s",name);\r
+     \r
+          /* Replace first byte with lower case 's' */\r
+          name[0] = 's';\r
+     \r
+          printf("\nName equals %s",name);\r
+     }\r
+     \r
+The ANSI standard on the C programming language defines the following\r
+functions for use with strings;\r
+\r
+char *strcat(char *dest, char *source)            Appends string source\r
+to the end of string destination.\r
+\r
+char *strchr(char *s, int c)            Returns a pointer to the first\r
+occurence of character 'c' within s.\r
+\r
+int strcmp(char *s1, char *s2)               Compares strings s1 and s2\r
+returning        < 0 if s1 is less than s2\r
+                                                       == 0 if s1 and s2\r
+are the same\r
+                                                       > 0 if s1 is\r
+greater than s2\r
+\r
+int strcoll(char *s1, char *s2)              Compares strings s1 and s2\r
+                    according to the collating sequence set by\r
+                              setlocale() returning    < 0 if s1 is less\r
+than s2\r
+                                             == 0 if s1 and s2 are the\r
+same\r
+                                             > 0 if s1 is greater than s2\r
+\r
+char *strcpy(char *dest, char *src)          Copies string src into\r
+string dest.\r
+\r
+unsigned strcspn(char *s1, char *s2)         Returns the length of string\r
+s1 that consists entirely of characters not in\r
+string s2.\r
+\r
+unsigned strlen(char *s)                Returns the length of string s.\r
+\r
+char *strncat(char *dest, char *src, unsigned len)     Copies at most\r
+'len' characters from string src into string dest.\r
+\r
+int strncmp(char *s1, char *s2, unsigned len)     Compares at most 'len'\r
+characters from\r
+                              string s1 with string s2 returning      < 0\r
+if s1 is less than s2\r
+                                                  == 0 if s1 and s2 are\r
+the same\r
+                                                  > 0 if s1 is greater\r
+than s2\r
+\r
+char *strncpy(char *dest, char *src, unsigned len)     Copies 'len'\r
+characters from string  src into string dest, truncating or\r
+                              padding with zero bytes as required.\r
+\r
+char *strpbrk(char *s1, char *s2)            Returns a pointer to the\r
+first character in string s1 that occurs in\r
+                              string s2.\r
+\r
+char *strrchr(char *s, int c)           Returns a pointer to the last\r
+occurence of 'c' within string s.\r
+\r
+unsigned strspn(char *s1, char *s2)          Returns the length of the\r
+initial segment of string s1 that consists\r
+                              entirely of characters in string s2.\r
+\r
+char *strstr(char *s1, char *s2)             Returns a pointer to the\r
+first occurence of string s2 within string\r
+                              s1, or NULL if string s2 is not found in\r
+string s1.\r
+\r
+char *strtok(char *s1, char *s2)             Returns a pointer to the\r
+token found in string s1 that is defined by\r
+                              delimiters in string s2. Returns NULLif no\r
+tokens are found.\r
+\r
+The ANSI standard also defines various functions for converting strings\r
+into numbers and numbers into strings.\r
+\r
+Some C compilers include functions to convert strings to upper and lower\r
+case, but these functions are not defined in the ANSI standard. However,\r
+the ANSI standard does define the functions; toupper() and tolower() that\r
+return an\r
+\r
+integer parameter converted to upper and lowercase respectively. By using\r
+these functions we can create our own ANSI compatible versions;\r
+\r
+\r
+     #include<stdio.h>\r
+     \r
+     void strupr(char *source)\r
+     {\r
+          char *p;\r
+     \r
+          p = source;\r
+          while(*p)\r
+          {\r
+               *p = toupper(*p);\r
+               p++;\r
+          }\r
+     }\r
+     \r
+     void strlwr(char *source)\r
+     {\r
+          char *p;\r
+     \r
+          p = source;\r
+          while(*p)\r
+          {\r
+               *p = tolower(*p);\r
+               p++;\r
+          }\r
+     }\r
+\r
+\r
+     int main()\r
+     {\r
+          char name[50];\r
+     \r
+          strcpy(name,"Servile Software");\r
+     \r
+          printf("\nName equals %s",name);\r
+     \r
+          strupr(name);\r
+     \r
+          printf("\nName equals %s",name);\r
+     \r
+          strlwr(name);\r
+     \r
+          printf("\nName equals %s",name);\r
+     }\r
+\r
+C does not impose a maximum length that a string may be, unlike other\r
+computer languages. However, some CPUs impose restrictions on the maximum\r
+size a block of memory can be. For example, the 8088 family of CPUs, as\r
+used by the IBM PC, impose a limit of 64K bytes on a segment of memory.\r
+\r
+An example program to reverse all the characters in a string.\r
+\r
+     #include <stdio.h>\r
+     #include <string.h>\r
+     \r
+     char *strrev(char *s)\r
+     {\r
+          /* Reverses the order of all characters in a string except the\r
+     null */\r
+          /* terminating byte */\r
+     \r
+          char *start;\r
+          char *end;\r
+          char tmp;\r
+     \r
+          /* Set pointer 'end' to last character in string */\r
+          end = s + strlen(s) - 1;\r
+     \r
+          /* Preserve pointer to start of string */\r
+          start = s;\r
+     \r
+          /* Swop characters */\r
+          while(end >= s)\r
+          {\r
+               tmp = *end;\r
+               *end = *s;\r
+               *s = tmp;\r
+               end--;\r
+               s++;\r
+          }\r
+          return(start);\r
+     }\r
+     \r
+     \r
+     main()\r
+     {\r
+          char text[100];\r
+          char *p;\r
+     \r
+          strcpy(text,"This is a string of data");\r
+     \r
+          p = strrev(text);\r
+     \r
+          printf("\n%s",p);\r
+     }\r
+     \r
+\r
+Strtok()\r
+\r
+The function strtok() is a very powerful standard C feature for\r
+extracting substrings from within a single string. It is used where the\r
+substrings are separated by known delimiters, such as commas in the\r
+following example;\r
+\r
+     #include <stdio.h>\r
+     #include <string.h>\r
+     \r
+     main()\r
+     {\r
+          char data[50];\r
+          char *p;\r
+     \r
+          strcpy(data,"RED,ORANGE,YELLOW,GREEN,BLUE,INDIGO,VIOLET");\r
+     \r
+          p = strtok(data,",");\r
+          while(p)\r
+          {\r
+               puts(p);\r
+               p = strtok(NULL,",");\r
+          };\r
+     }\r
+     \r
+Or this program can be written with a for() loop thus;\r
+\r
+     #include <stdio.h>\r
+     #include <string.h>\r
+     \r
+     main()\r
+     {\r
+          char data[50];\r
+          char *p;\r
+     \r
+          strcpy(data,"RED,ORANGE,YELLOW,GREEN,BLUE,INDIGO,VIOLET");\r
+     \r
+          for(strtok(data,","); p; p = strtok(NULL,","))\r
+          {\r
+               puts(p);\r
+          };\r
+     }\r
+     \r
+They both compile to the same code but follow different programming\r
+styles.\r
+\r
+Initially, you call strtok() with the name of the string variable to be\r
+parsed, and a second string that contains the known delimiters. Strtok()\r
+then returns a pointer to the start of the first substring and replaces\r
+the first token with a zero delimiter. Subsequent calls to strtok() can\r
+be made in a loop passing NULL as the string to be parsed, and strtok()\r
+will return the subsequent substrings.\r
+\r
+\r
+Since strtok() can accept many delimiter characters in the second\r
+parameter string we can use it as the basis of a simple word counting\r
+program;\r
+\r
+     #include <stdio.h>\r
+     #include <stdlib.h>\r
+     #include <string.h>\r
+     \r
+     void main(int argc, char *argv[])\r
+     {\r
+          FILE *fp;\r
+          char buffer[256];\r
+          char *p;\r
+          long count;\r
+     \r
+          if (argc != 2)\r
+          {\r
+               fputs("\nERROR: Usage is wordcnt <file>\n",stderr);\r
+               exit(0);\r
+          }\r
+     \r
+          /* Open file for reading */\r
+          fp = fopen(argv[1],"r");\r
+     \r
+          /* Check the open was okay */\r
+          if (!fp)\r
+          {\r
+               fputs("\nERROR: Cannot open source file\n",stderr);\r
+               exit(0);\r
+          }\r
+     \r
+          /* Initialise word count */\r
+          count = 0;\r
+     \r
+          do\r
+          {\r
+               /* Read a line of data from the file */\r
+               fgets(buffer,255,fp);\r
+     \r
+               /* check for an error in the read or EOF */\r
+               if (ferror(fp) || feof(fp))\r
+                    continue;\r
+     \r
+               /* count words in received line */\r
+               /* Words are defined as separated by the characters */\r
+               /* \t(tab) \n(newline) , ; : . ! ? ( ) - and [space] */\r
+               p = strtok(buffer,"\t\n,;:.!?()- ");\r
+               while(p)\r
+               {\r
+                    count++;\r
+                    p = strtok(NULL,"\t\n,;:.!?()- ");\r
+               }\r
+          }\r
+          while(!ferror(fp) && !feof(fp));\r
+     \r
+          /* Finished reading. Was it due to an error? */\r
+          if (ferror(fp))\r
+          {\r
+               fputs("\nERROR: Reading source file\n",stderr);\r
+               fclose(fp);\r
+               exit(0);\r
+          }\r
+     \r
+          /* Reading finished due to EOF, quite valid so print count */\r
+          printf("\nFile %s contains %ld words\n",argv[1],count);\r
+          fclose(fp);\r
+     }\r
+     \r
+\r
+\r
+\r
+Converting Numbers To And From Strings\r
+\r
+All C compilers provide a facility for converting numbers to strings.\r
+This being sprintf(). However, as happens sprintf() is a multi-purpose\r
+function that is therefore large and slow. The following function ITOS()\r
+accepts two parameters, the first being a signed integer and the second\r
+being a pointer to a character string. It then copies the integer into\r
+the memory pointed to by the character pointer. As with sprintf() ITOS()\r
+does not check that the target string is long enough to accept the result\r
+of the conversion. You should then ensure that the target string is long\r
+enough.\r
+\r
+Example function for copying a signed integer into a string;\r
+\r
+     void ITOS(long x, char *ptr)\r
+     {\r
+          /* Convert a signed decimal integer to a string */\r
+     \r
+          long pt[9] = { 100000000, 10000000, 1000000, 100000, 10000,\r
+     1000, 100, 10, 1 };\r
+          int n;\r
+     \r
+          /* Check sign */\r
+          if (x < 0)\r
+          {\r
+               *ptr++ = '-';\r
+               /* Convert x to absolute */\r
+               x = 0 - x;\r
+          }\r
+     \r
+          for(n = 0; n < 9; n++)\r
+          {\r
+               if (x > pt[n])\r
+               {\r
+                    *ptr++ = '0' + x / pt[n];\r
+                    x %= pt[n];\r
+               }\r
+          }\r
+          return;\r
+     }\r
+     \r
+To convert a string to a floating point number, C provides two functions;\r
+atof() and strtod(). atof() has the prototype;\r
+\r
+     double atof(const char *s);\r
+\r
+strtod has the prototype;\r
+\r
+     double strtod(const char *s,char **endptr);\r
+\r
+Both functions scan the string and convert it as far as they can, until\r
+they come across a character they don't understand. The difference\r
+between the two functions is that if strtod() is passed a character\r
+pointer for parameter `endptr', it sets that pointer to the first\r
+character in the string that terminated the conversion. Because of its\r
+better error reporting, by way of endptr, strtod() is often preferred to\r
+atof().\r
+\r
+To convert a string to an integer use atoi() that has the prototype;\r
+\r
+\r
+     int atoi(const char *s);\r
+\r
+atoi() does not check for an overflow, and the results are undefined!\r
+\r
+atol() is a similar function but returns a long. Alternatively, you can\r
+use strtol() and stroul() instead that have better error checking.\r
+\r
+                                    \r
+                              TEXT HANDLING\r
+\r
+Human languages write information down as `text'. This is comprised of\r
+words, figures and punctuation. The words being made up of upper case and\r
+lower case letters. Processing text with a computer is a commonly\r
+required task, and yet quite a difficult one.\r
+\r
+The ANSI C definitions include string processing functions that are by\r
+their nature sensitive to case. That is the letter `A' is seen as\r
+distinct from the letter `a'. This is the first problem that must be\r
+overcome by the programmer. Fortunately both Borland's Turbo C compilers\r
+and Microsoft's C compilers include case insensitive forms of the string\r
+functions.\r
+\r
+stricmp() for example is the case insensitive form of strcmp(), and\r
+strnicmp() is the case insensitive form of strncmp().\r
+\r
+If you are concerned about writing portable code, then you must restrict\r
+yourself to the ANSI C functions, and write your own case insensitive\r
+functions using the tools provided.\r
+\r
+Here is a simple implementation of a case insensitive version of\r
+strstr().  The function simply makes a copy of the parameter strings,\r
+converts those copies both to upper case and then does a standard\r
+strstr() on the copies.  The offset of the target string within the\r
+source string will be the same for the copy as the original, and so it\r
+can be returned relative to the parameter string.\r
+\r
+\r
+     char *stristr(char *s1, char *s2)\r
+     {\r
+          char c1[1000];\r
+          char c2[1000];\r
+          char *p;\r
+     \r
+          strcpy(c1,s1);\r
+          strcpy(c2,s2);\r
+     \r
+          strupr(c1);\r
+          strupr(c2);\r
+     \r
+          p = strstr(c1,c2);\r
+          if (p)\r
+               return s1 + (p - c1);\r
+          return NULL;\r
+     }\r
+     \r
+This function scans a string, s1 looking for the word held in s2. The\r
+word must be a complete word, not simply a character pattern, for the\r
+function to return true. It makes use of the stristr() function described\r
+previously.\r
+\r
+     int word_in(char *s1,char *s2)\r
+     {\r
+          /* return non-zero if s2 occurs as a word in s1 */\r
+          char *p;\r
+          char *q;\r
+          int ok;\r
+     \r
+          ok = 0;\r
+          q = s1;\r
+     \r
+          do\r
+          {\r
+               /* Locate character occurence s2 in s1 */\r
+               p = stristr(q,s2);\r
+               if (p)\r
+               {\r
+                    /* Found */\r
+                    ok = 1;\r
+     \r
+                    if (p > s1)\r
+                    {\r
+                         /* Check previous character */\r
+                         if (*(p - 1) >= 'A' && *(p - 1) <= 'z')\r
+                              ok = 0;\r
+                    }\r
+     \r
+                    /* Move p to end of character set */\r
+                    p += strlen(s2);\r
+                    if (*p)\r
+                    {\r
+                         /* Check character following */\r
+                         if (*p >= 'A' && *p <= 'z')\r
+                              ok = 0;\r
+                    }\r
+               }\r
+               q = p;\r
+          }\r
+          while(p && !ok);\r
+          return ok;\r
+     }\r
+\r
+\r
+Some more useful functions for dealing with text are truncstr() that\r
+truncates a string;\r
+\r
+     void truncstr(char *p,int num)\r
+     {\r
+          /* Truncate string by losing last num characters */\r
+          if (num < strlen(p))\r
+               p[strlen(p) - num] = 0;\r
+     }\r
+     \r
+trim() that removes trailing spaces from the end of a string;\r
+\r
+     void trim(char *text)\r
+     {\r
+          /* remove trailing spaces */\r
+          char *p;\r
+     \r
+          p = &text[strlen(text) - 1];\r
+          while(*p == 32 && p >= text)\r
+               *p-- = 0;\r
+     }\r
+     \r
+strlench() that changes the length of a string by adding or deleting\r
+characters;\r
+\r
+     void strlench(char *p,int num)\r
+     {\r
+          /* Change length of string by adding or deleting characters */\r
+     \r
+          if (num > 0)\r
+               memmove(p + num,p,strlen(p) + 1);\r
+          else\r
+          {\r
+               num = 0 - num;\r
+               memmove(p,p + num,strlen(p) + 1);\r
+          }\r
+     }\r
+     \r
+strins() that inserts a string into another string;\r
+\r
+     void strins(char *p, char *q)\r
+     {\r
+          /* Insert string q into p */\r
+          strlench(p,strlen(q));\r
+          strncpy(p,q,strlen(q));\r
+     }\r
+     \r
+strchg() that replaces all occurences of one sub-string with another\r
+within a target string;\r
+\r
+     void strchg(char *data, char *s1, char *s2)\r
+     {\r
+          /* Replace all occurences of s1 with s2 */\r
+          char *p;\r
+          char changed;\r
+     \r
+          do\r
+          {\r
+               changed = 0;\r
+               p = strstr(data,s1);\r
+               if (p)\r
+               {\r
+                    /* Delete original string */\r
+                    strlench(p,0 - strlen(s1));\r
+     \r
+                    /* Insert replacement string */\r
+                    strins(p,s2);\r
+                    changed = 1;\r
+               }\r
+          }\r
+          while(changed);\r
+     }\r
+                                    \r
+                                  TIME\r
+\r
+C provides a function, time(), which reads the computer's system clock to\r
+return the system time as a number of seconds since midnight on January\r
+the first, 1970. However, this value can be converted to a useful string\r
+by the function ctime() as illustrated in the following example;\r
+\r
+     #include <stdio.h>\r
+     #include <time.h>\r
+     \r
+     int main()\r
+     {\r
+          /* Structure to hold time, as defined in time.h  */\r
+          time_t t;\r
+     \r
+          /* Get system date and time from computer */\r
+          t = time(NULL);\r
+          printf("Today's date and time: %s\n",ctime(&t));\r
+     }\r
+     \r
+The string returned by ctime() is comprised of seven fields;\r
+\r
+     Day of the week,\r
+     Month of the year,\r
+     Date of the day of the month,\r
+     hour,\r
+     minutes,\r
+     seconds,\r
+     century of the year\r
+\r
+terminated by a newline character and null terminating byte. Since the\r
+fields always occupy the same width, slicing operations can be carried\r
+out on the string with ease. The following program defines a structure\r
+`time' and a function gettime() that extracts the hours, minutes and\r
+seconds of the current time and places them in the structure;\r
+\r
+\r
+     #include <stdio.h>\r
+     #include <time.h>\r
+     \r
+     struct time\r
+     {\r
+          int ti_min;         /* Minutes */\r
+          int ti_hour;        /* Hours */\r
+          int ti_sec;         /* Seconds */\r
+     };\r
+     \r
+     void gettime(struct time *now)\r
+     {\r
+          time_t t;\r
+          char temp[26];\r
+          char *ts;\r
+     \r
+          /* Get system date and time from computer */\r
+          t = time(NULL);\r
+     \r
+          /* Translate dat and time into a string */\r
+          strcpy(temp,ctime(&t));\r
+     \r
+          /* Copy out just time part of string */\r
+          temp[19] = 0;\r
+          ts = &temp[11];\r
+     \r
+          /* Scan time string and copy into time structure */\r
+          sscanf(ts,"%2d:%2d:%2d",&now->ti_hour,&now->ti_min,&now-\r
+     >ti_sec);\r
+     }\r
+     \r
+     int main()\r
+     {\r
+          struct time now;\r
+     \r
+          gettime(&now);\r
+     \r
+          printf("\nThe time is\r
+     %02d:%02d:%02d",now.ti_hour,now.ti_min,now.ti_sec);\r
+     \r
+     }\r
+\r
+The ANSI standard on C does actually provide a function ready made to\r
+convert the value returned by time() into a structure;\r
+\r
+     #include <stdio.h>\r
+     #include <time.h>\r
+     \r
+     int main()\r
+     {\r
+          time_t t;\r
+          struct tm *tb;\r
+     \r
+          /* Get time into t */\r
+          t = time(NULL);\r
+     \r
+          /* Convert time value t into structure pointed to by tb */\r
+          tb = localtime(&t);\r
+     \r
+          printf("\nTime is %02d:%02d:%02d",tb->tm_hour,tb->tm_min,tb-\r
+     >tm_sec);\r
+     }\r
+     \r
+The structure 'tm' is defined in time.h as;\r
+\r
+     struct tm\r
+     {\r
+          int tm_sec;\r
+          int tm_min;\r
+          int tm_hour;\r
+          int tm_mday;\r
+          int tm_mon;\r
+          int tm_year;\r
+          int tm_wday;\r
+          int tm_yday;\r
+          int tm_isdst;\r
+     };\r
+\r
+\r
+Timers\r
+Often a program must determine the date and time from the host computer's\r
+non-volatile RAM. There are several time functions provided by the ANSI\r
+standard on C that allow a program to retrieve, from the host computer,\r
+the current date and time;\r
+\r
+time() returns the number of seconds that have elapsed since midnight on\r
+January the 1st 1970. It has the prototype;\r
+\r
+     time_t time(time_t *timer);\r
+\r
+time() fills in the time_t variable sent as a parameter and returns the\r
+same value. You can call time() with a NULL parameter and just collect\r
+the return value thus;\r
+\r
+     #include <time.h>\r
+     \r
+     void main()\r
+     {\r
+          time_t now;\r
+     \r
+          now = time(NULL);\r
+     }\r
+     \r
+asctime() converts a time block to a twenty six character string of the\r
+format;\r
+\r
+                Wed Oct 14 10:23:45 1992\n\0\r
+\r
+asctime() has the prototype;\r
+\r
+               char *asctime(const struct tm *tblock);\r
+\r
+ctime() converts a time value (as returned by time()) into a twenty six\r
+chracter string of the same format as asctime(). For example;\r
+\r
+     #include <stdio.h>\r
+     #include <time.h>\r
+     \r
+     void main()\r
+     {\r
+          time_t now;\r
+          char date[30];\r
+     \r
+          now = time(NULL);\r
+          strcpy(date,ctime(&now));\r
+     }\r
+     \r
+difftime() returns the difference, in seconds, between two values (as\r
+returned by time()). This can be useful for testing the elapsed time\r
+between two events, the time a function takes to execute, and for\r
+creating consistent delays that are irrelevant of the host computer.\r
+\r
+An example delay program;\r
+\r
+     #include <stdio.h>\r
+     #include <time.h>\r
+     \r
+     \r
+     void DELAY(int period)\r
+     {\r
+          time_t start;\r
+     \r
+          start = time(NULL);\r
+          while(time(NULL) < start + period)\r
+               ;\r
+     }\r
+     \r
+     void main()\r
+     {\r
+          printf("\nStarting delay now....(please wait 5 seconds)");\r
+     \r
+          DELAY(5);\r
+     \r
+          puts("\nOkay, I've finished!");\r
+     }\r
+\r
+gmtime() converts a local time value (as returned by time()) to the GMT\r
+time and stores it in a time block. This function depends upon the global\r
+variable timezone being set.\r
+\r
+\r
+The time block is a predefined structure (declared in time.h) as follows;\r
+\r
+     struct tm\r
+     {\r
+          int tm_sec;\r
+          int tm_min;\r
+          int tm_hour;\r
+          int tm_mday;\r
+          int tm_mon;\r
+          int tm_year;\r
+          int tm_wday;\r
+          int tm_yday;\r
+          int tm_isdst;\r
+     };\r
+\r
+tm_mday records the day of the month, ranging from 1 to 31; tm_wday is\r
+the day of the week with Sunday being represented by 0; the year is\r
+recorded less 1900; tm_isdst is a flag to show whether daylight saving\r
+time is in effect. The actual names of the structure and its elements may\r
+vary from compiler to compiler, but the structure should be the same in\r
+essence.\r
+\r
+mktime() converts a time block to a calendar format. It follows the\r
+prototype;\r
+\r
+                time_t mktime(struct tm *t);\r
+\r
+The following example allows entry of a date, and uses mktime() to\r
+calculate the day of the week appropriate to that date. Only dates from\r
+the first of January 1970 are recognisable by the time functions.\r
+\r
+     #include <stdio.h>\r
+     #include <time.h>\r
+     #include <string.h>\r
+     \r
+     void main()\r
+     {\r
+          struct tm tsruct;\r
+          int okay;\r
+          char data[100];\r
+          char *p;\r
+          char *wday[] = {"Sunday", "Monday", "Tuesday", "Wednesday",\r
+     "Thursday", "Friday", "Saturday" ,\r
+                    "prior to 1970, thus not known" };\r
+          do\r
+          {\r
+               okay = 0;\r
+               printf("\nEnter a date as dd/mm/yy ");\r
+               p = fgets(data,8,stdin);\r
+               p = strtok(data,"/");\r
+     \r
+               if (p != NULL)\r
+                    tsruct.tm_mday = atoi(p);\r
+               else\r
+                    continue;\r
+     \r
+               p = strtok(NULL,"/");\r
+               if (p != NULL)\r
+                    tsruct.tm_mon = atoi(p);\r
+               else\r
+                    continue;\r
+     \r
+               p = strtok(NULL,"/");\r
+     \r
+               if (p != NULL)\r
+                    tsruct.tm_year = atoi(p);\r
+               else\r
+                    continue;\r
+               okay = 1;\r
+          }\r
+          while(!okay);\r
+     \r
+          tsruct.tm_hour = 0;\r
+          tsruct.tm_min = 0;\r
+          tsruct.tm_sec = 1;\r
+          tsruct.tm_isdst = -1;\r
+     \r
+          /* Now get day of the week */\r
+          if (mktime(&tsruct) == -1)\r
+          tsruct.tm_wday = 7;\r
+     \r
+          printf("That was %s\n",wday[tsruct.tm_wday]);\r
+     }\r
+     \r
+mktime() also makes the neccessary adjustments for values out of range,\r
+this can be utilised for discovering what the date will be in n number of\r
+days time thus;\r
+\r
+\r
+     #include <stdio.h>\r
+     #include <time.h>\r
+     #include <string.h>\r
+     \r
+     void main()\r
+     {\r
+          struct tm *tsruct;\r
+          time_t today;\r
+     \r
+          today = time(NULL);\r
+          tsruct = localtime(&today);\r
+     \r
+          tsruct->tm_mday += 10;\r
+          mktime(tsruct);\r
+     \r
+          printf("In ten days it will be %02d/%02d/%2d\n", tsruct-\r
+     >tm_mday,tsruct->tm_mon + 1,tsruct->tm_year);\r
+     \r
+     }\r
+     \r
+\r
+This program uses Julian Dates to decide any day of the week since the\r
+1st of October 1582 when the Gregorian calendar was introduced.\r
+\r
+     char *WDAY(int day, int month, int year)\r
+     {\r
+          /* Returns a pointer to a string representing the day of the\r
+     week */\r
+     \r
+          static char *cday[] = { "Saturday","Sunday","Monday","Tuesday",\r
+     "Wednesday","Thursday","Friday" };\r
+          double yy;\r
+          double yt;\r
+          double j;\r
+          int y1;\r
+          int y4;\r
+          int x;\r
+     \r
+          yy = year / 100;\r
+          y1 = (int)(yy);\r
+          yt = year / 400;\r
+          y4 = (int)(yt);\r
+          x = 0;\r
+     \r
+          if (month < 3)\r
+          {\r
+               year--;\r
+               x = 12;\r
+          }\r
+     \r
+          j = day + (int)(365.25*year);\r
+     \r
+          j += (int)(30.6001 * (month + 1 + x)) - y1 + y4;\r
+     \r
+          if (yy == y1 && yt != y4 && month < 3)\r
+               j++;\r
+     \r
+          j = 1 + j - 7 * (int)(j/7);\r
+     \r
+          if (j > 6)\r
+               j -= 7;\r
+     \r
+          return(cday[j]);\r
+     }\r
+     \r
+     \r
+With time() and difftime() we can create a timer for testing the\r
+execution times of functions thus;\r
+\r
+     #include <stdio.h>\r
+     #include <time.h>\r
+     \r
+     main()\r
+     {\r
+          time_t now;\r
+          time_t then;\r
+          double elapsed;\r
+     \r
+          int n;\r
+     \r
+          now = time(NULL);\r
+     \r
+          /* This loop is adjustable for multiple passes */\r
+          for(n = 0; n < 5000; n++)\r
+               /* Call the function to test */\r
+               func();\r
+     \r
+          then = time(NULL);\r
+     \r
+          elapsed = difftime(then,now);\r
+          printf("\nElapsed seconds==%lf\n",elapsed);\r
+     }\r
+     \r
+By way of time() and ctime() the current system date and time can be\r
+retrieved from the host computer thus;\r
+\r
+     #include <stdio.h>\r
+     #include <time.h>\r
+     \r
+     main()\r
+     {\r
+          time_t now;\r
+          char *date;\r
+          int n;\r
+     \r
+          /* Get system time */\r
+          now = time(NULL);\r
+     \r
+          /* Convert system time to a string */\r
+          date = ctime(&now);\r
+     \r
+          /*Display system time */\r
+          printf("\nIt is %s",date);\r
+     }\r
+     \r
+time_t is a type defined in time.h as the type of variable returned by\r
+time(). This type may vary from compiler to compiler, and therefore is\r
+represented by the type "time_t".\r
+                                    \r
+                              HEADER FILES\r
+\r
+Function prototypes for library functions supplied with the C compiler,\r
+and standard macros are declared in header files.\r
+\r
+The ANSI standard on the C programming language lists the following\r
+header files;\r
+\r
+Header file    Description\r
+               \r
+assert.h       Defines the assert debugging macro\r
+ctype.h        Character classification and\r
+               conversion macros\r
+errno.h        Constant mnemonics for error codes\r
+float.h        Defines implementation specific\r
+               macros for dealing with floating\r
+               point mathematics\r
+limits.h       Defines implementation specific\r
+               limits on type values\r
+locale.h       Country specific parameters\r
+math.h         Prototypes for mathematics functions\r
+setjmp.h       Defines typedef and functions for\r
+               setjmp/longjmp\r
+signal.h       Constants and declarations for use by\r
+               signal() and raise()\r
+stdarg.h       Macros for dealing with argument\r
+               lists\r
+stddef.h       Common data types and macros\r
+stdio.h        Types and macros required for\r
+               standard I/O\r
+stdlib.h       Prototypes of commonly used functions\r
+               and miscellany\r
+string.h       String manipulation function\r
+               prototypes\r
+time.h         Structures for time conversion\r
+               routines\r
+                                    \r
+                                DEBUGGING\r
+\r
+The ANSI standard on C includes a macro function for debugging called\r
+assert(). This expands to an if() statement, which if it returns true\r
+terminates the program and outputs to the standard error stream a message\r
+comprised of:\r
+\r
+\r
+     Assertion failed: <test>, file <module>, line <line number>\r
+     Abnormal program termination\r
+For example, the following program accidentally assigns a zero value to a\r
+pointer!\r
+\r
+     #include <stdio.h>\r
+     #include <assert.h>\r
+     \r
+     main()\r
+     {\r
+          /* Demonstration of assert */\r
+     \r
+          int *ptr;\r
+          int x;\r
+     \r
+          x = 0;\r
+     \r
+          /* Whoops! error in this line! */\r
+          ptr = x;\r
+     \r
+          assert(ptr != NULL);\r
+     }\r
+     \r
+When run, this program terminates with the message:\r
+\r
+     Assertion failed: ptr != 0, file TEST.C, line 16\r
+     Abnormal program termination\r
+\r
+When a program is running okay, the assert() functions can be removed\r
+from the compiled program by simply adding the line;\r
+\r
+     #define NDEBUG\r
+\r
+before the #include <assert.h> line. Effectively the assert functions are\r
+commented out in the preprocessed source before compilation, this means\r
+that the assert expressions are not evaluated, and thus cannot cause any\r
+side effects.\r
+                                    \r
+                               FLOAT ERRORS\r
+\r
+Floating point numbers are decimal fractions, decimal fractions do not\r
+accurately equate to normal fractions as not every number will divide\r
+precisely by ten. This creates the potential for rounding errors in\r
+calculations that use floating point numbers. The following program\r
+illustrates one such example of rounding error problems;\r
+\r
+\r
+     #include <stdio.h>\r
+     \r
+     void main()\r
+     {\r
+          float number;\r
+     \r
+          for(number = 1; number > 0.4; number -= 0.01)\r
+               printf("\n%f",number);\r
+     }\r
+     \r
+At about 0.47 (depending upon the host computer and compiler) the program\r
+starts to store an inaccurate value for 'number'.\r
+\r
+This problem can be minimised by using longer floating point numbers,\r
+doubles or long doubles that have larger storage space allocated to them.\r
+For really accurate work though, you should use integers and only convert\r
+to a floating point number for display. You also should notice that most\r
+C compilers default floating point numbers to `doubles' and when using\r
+`float' types have to convert the double down to a float!\r
+\r
+\r
+                                    \r
+                             ERROR HANDLING\r
+\r
+When a system error occurs within a program, for example when an attempt\r
+to open a file fails, it is helpful to the program's user to display a\r
+message reporting the failure. Equally it is useful to the program's\r
+developer to know why the error occurred, or at least as much about it as\r
+possible. To this end the ANSI standard on C describes a function,\r
+perror(), which has the prototype;\r
+\r
+\r
+     void perror(const char *s);\r
+\r
+and is used to display an error message. The program's own prefixed error\r
+message is passed to perror() as the string parameter. This error message\r
+is displayed by perror() followed by the host's system error separated by\r
+a colon. The following example illustrates a use for perror();\r
+\r
+\r
+     #include <stdio.h>\r
+     \r
+     void main()\r
+     {\r
+          FILE *fp;\r
+          char fname[] = "none.xyz";\r
+     \r
+          fp = fopen(fname,"r");\r
+     \r
+          if(!fp)\r
+               perror(fname);\r
+          return;\r
+     }\r
+     \r
+If the fopen() operation fails, a message similar to;\r
+\r
+     none.xyz: No such file or directory\r
+\r
+is displayed.\r
+\r
+You should note, perror() sends its output to the predefined stream\r
+`stderr', which is usually the host computer's display unit.\r
+\r
+\r
+perror() finds its message from the host computer via the global variable\r
+'errno' that is set by most, but not all system functions.\r
+\r
+Unpleasant errors might justify the use of abort(). abort() is a function\r
+that terminates the running program with a message;\r
+\r
+     "Abnormal program termination"\r
+\r
+and returns an exit code of 3 to the parent process or operating system.\r
+\r
+\r
+\r
+Critical Error Handling With The IBM PC AND DOS\r
+\r
+The IBM PC DOS operating system provides a user amendable critical error\r
+handling function. This function is usually discovered by attempting to\r
+write to a disk drive that does not have a disk in it, in which case the\r
+familiar;\r
+\r
+     Not ready error writing drive A\r
+     Abort Retry Ignore?\r
+\r
+Message is displayed on the screen. Fine when it occurs from within a DOS\r
+program, not so fine from within your own program!\r
+\r
+The following example program shows how to redirect the DOS critical\r
+error interrupt to your own function;\r
+\r
+\r
+     /* DOS critical error handler test */\r
+     \r
+     #include <stdio.h>\r
+     #include <dos.h>\r
+     \r
+     void interrupt new_int();\r
+     void interrupt (*old_int)();\r
+     \r
+     char status;\r
+     \r
+     main()\r
+     {\r
+          FILE *fp;\r
+     \r
+          old_int = getvect(0x24);\r
+     \r
+          /* Set critical error handler to my function */\r
+          setvect(0x24,new_int);\r
+     \r
+          /* Generate an error by not having a disc in drive A */\r
+          fp = fopen("a:\\data.txt","w+");\r
+     \r
+          /* Display error status returned */\r
+          printf("\nStatus ==  %d",status);\r
+     \r
+     }\r
+     \r
+     void interrupt new_int()\r
+     {\r
+          /* set global error code */\r
+          status = _DI;\r
+     \r
+          /* ignore error and return */\r
+          _AL = 0;\r
+     }\r
+     \r
+When the DOS critical error interrupt is called, a status message is\r
+passed in the low byte of the DI register. This message is one of;\r
+\r
+Code                     Meaning\r
+                         \r
+00                       Write-protect error\r
+01                       Unknown unit\r
+02                       Drive not ready\r
+03                       Unknown command\r
+04                       Data error, bad CRC\r
+05                       Bad request structure\r
+                         length\r
+06                       Seek error\r
+07                       Unknown media type\r
+08                       Sector not found\r
+09                       Printer out of paper\r
+0A                       Write error\r
+0B                       Read error\r
+0C                       General failure\r
+\r
+Your critical error interrupt handler can transfer this status message\r
+into a global variable, and then set the result message held in register\r
+AL to one of;\r
+\r
+\r
+Code                     Action\r
+                         \r
+00                       Ignore error\r
+01                       Retry\r
+02                       Terminate program\r
+03                       Fail (Available with\r
+                         DOS 3.3 and above)\r
+\r
+\r
+If you choose to set AL to 02, terminate program, you should ensure ALL\r
+files are closed first since DOS will terminate the program abruptly,\r
+leaving files open and memory allocated, not a pretty state to be in!\r
+\r
+\r
+The example program shown returns an ignore status from the critical\r
+error interrupt, and leaves the checking of any errors to the program\r
+itself. So, in this example after the call to fopen() we could check the\r
+return value in fp, and if it reveals an error (NULL in this case) we\r
+could then check the global variable status and act accordingly, perhaps\r
+displaying a polite message to the user to put a disk in the floppy drive\r
+and ensure that the door is closed.\r
+\r
+The following is a practical function for checking whether a specified\r
+disc drive can be accessed. It should be used with the earlier critical\r
+error handler and global variable `status'.\r
+\r
+     int DISCOK(int drive)\r
+     {\r
+          /* Checks for whether a disc can be read */\r
+          /* Returns false (zero) on error */\r
+          /* Thus if(!DISCOK(drive)) */\r
+          /*          error();  */\r
+     \r
+          unsigned char buffer[25];\r
+     \r
+          /* Assume okay */\r
+          status = 0;\r
+     \r
+          /* If already logged to disc, return okay */\r
+          if ('A' + drive == diry[0])\r
+               return(1);\r
+     \r
+          /* Attempt to read disc */\r
+          memset(buffer,0,20);\r
+          sprintf(buffer,"%c:$$$.$$$",'A'+drive);\r
+     \r
+          _open(buffer,O_RDONLY);\r
+     \r
+          /* Check critical error handler status */\r
+          if (status == 0)\r
+               return(1);\r
+     \r
+          /* Disc cannot be read */\r
+          return(0);\r
+     }\r
+                                    \r
+                                  CAST\r
+\r
+\r
+Casting tells the compiler what a data type is, and can be used to change\r
+a data type. For example, consider the following;\r
+\r
+     #include <stdio.h>\r
+     \r
+     void main()\r
+     {\r
+          int x;\r
+          int y;\r
+     \r
+          x = 10;\r
+          y = 3;\r
+     \r
+          printf("\n%lf",x / y);\r
+     }\r
+\r
+The printf() function has been told to expect a double. However, the\r
+compiler sees the variables `x' and `y' as integers, and an error occurs!\r
+To make this example work we must tell the compiler that the result of\r
+the expression x / y is a double, this is done with a cast thus;\r
+\r
+\r
+     #include <stdio.h>\r
+     \r
+     void main()\r
+     {\r
+          int x;\r
+          int y;\r
+     \r
+          x = 10;\r
+          y = 3;\r
+     \r
+          printf("\n%lf",(double)(x / y));\r
+     }\r
+\r
+Notice the data type `double' is enclosed by parenthesis, and so is the\r
+expression to convert. But now, the compiler knows that the result of the\r
+expression is a double, but it still knows that the variables `x' and `y'\r
+are integers and so an integer division will be carried out. We have to\r
+cast the constants thus;\r
+\r
+     #include <stdio.h>\r
+     \r
+     void main()\r
+     {\r
+          int x;\r
+          int y;\r
+     \r
+          x = 10;\r
+          y = 3;\r
+     \r
+          printf("\n%lf",(double)(x) / (double)(y));\r
+     }\r
+\r
+Because both of the constants are doubles, the compiler knows that the\r
+outcome of the expression will also be a double.\r
+\r
+\r
+\r
+                                    \r
+                      THE IMPORTANCE OF PROTOTYPING\r
+\r
+Prototyping a function involves letting the compiler know in advance what\r
+type of values a function will receive and return. For example, lets look\r
+at strtok(). This has the prototype;\r
+\r
+\r
+     char *strtok(char *s1, const char *s2);\r
+\r
+This prototype tells the compiler that strtok() will return a character\r
+pointer, the first received parameter will be a pointer to a character\r
+string, and that string can be changed by strtok(), and the last\r
+parameter will be a pointer to a character string that strtok() cannot\r
+change.\r
+\r
+The compiler knows how much space to allocate for the return parameter,\r
+sizeof(char *), but without a prototype for the function the compiler\r
+will assume that the return value of strtok() is an integer, and will\r
+allocate space for a return type of int, that is sizeof(int). If an\r
+integer and a character pointer occupy the same number of bytes on the\r
+host computer no major problems will occur, but if a character pointer\r
+occupies more space than an integer, then the compiler wont have\r
+allocated enough space for the return value and the return from a call to\r
+strtok() will overwrite some other bit of memory. If, as so often happens\r
+the return value is returned via the stack, the results of confusing the\r
+compiler can be disastrous!\r
+\r
+Thankfully most C compilers will warn the programmer if a call to a\r
+function has been made without a prototype, so that you can add the\r
+required function prototypes.\r
+\r
+Consider the following example that will not compile on most modern C\r
+compilers due to the nasty error in it;\r
+\r
+\r
+     #include <stdio.h>\r
+     \r
+     int FUNCA(int x, int y)\r
+     {\r
+          return(MULT(x,y));\r
+     }\r
+     \r
+     double MULT(double x, double y)\r
+     {\r
+          return(x * y);\r
+     }\r
+     \r
+     \r
+     main()\r
+     {\r
+          printf("\n%d",FUNCA(5,5));\r
+     }\r
+     \r
+When the compiler first encounters the function MULT() it is as a call\r
+from within FUNCA(). In the absence of any prototype for MULT() the\r
+compiler assumes that MULT() returns an integer. When the compiler finds\r
+the definition for function MULT() it sees that a return of type double\r
+has been declared. The compiler then reports an error in the compilation\r
+saying something like;\r
+\r
+\r
+     "Type mismatch in redclaration of function 'MULT'"\r
+\r
+What the compiler is really trying to say is, prototype your functions\r
+before using them! If this example did compile, and was then run it\r
+probably would crash the computer's stack and cause a system hang.\r
+\r
+                                    \r
+                          POINTERS TO FUNCTIONS\r
+\r
+C allows a pointer to point to the address of a function, and this\r
+pointer to be called rather than specifying the function. This is used by\r
+interrupt changing functions and may be used for indexing functions\r
+rather than using switch statements. For example;\r
+\r
+     #include <stdio.h>\r
+     #include <math.h>\r
+     \r
+     double (*fp[7])(double x);\r
+     \r
+     void main()\r
+     {\r
+          double x;\r
+          int p;\r
+     \r
+          fp[0] = sin;\r
+          fp[1] = cos;\r
+          fp[2] = acos;\r
+          fp[3] = asin;\r
+          fp[4] = tan;\r
+          fp[5] = atan;\r
+          fp[6] = ceil;\r
+     \r
+          p = 4;\r
+     \r
+          x = fp[p](1.5);\r
+          printf("\nResult %lf",x);\r
+     }\r
+\r
+This example program defines an array of pointers to functions, (*fp[])()\r
+that are then called dependant upon the value in the indexing variable p.\r
+This program could also be written;\r
+\r
+     #include <stdio.h>\r
+     #include <math.h>\r
+     \r
+     void main()\r
+     {\r
+          double x;\r
+          int p;\r
+     \r
+          p = 4;\r
+     \r
+          switch(p)\r
+          {\r
+               case 0 :  x = sin(1.5);\r
+                     break;\r
+               case 1 :  x = cos(1.5);\r
+                     break;\r
+               case 2 :  x = acos(1.5);\r
+                     break;\r
+               case 3 :  x = asin(1.5);\r
+                     break;\r
+               case 4 :  x = tan(1.5);\r
+                     break;\r
+               case 5 :  x = atan(1.5);\r
+                     break;\r
+               case 6 :  x = ceil(1.5);\r
+                     break;\r
+          }\r
+          puts("\nResult %lf",x);\r
+     }\r
+\r
+The first example, using pointers to the functions, compiles into much\r
+smaller code and executes faster than the second example.\r
+\r
+The table of pointers to functions is a useful facility when writing\r
+language interpreters, the program compares an entered instruction\r
+against a table of key words that results in an index variable being set\r
+and then the program simply needs to call the function pointer indexed by\r
+the variable, rather than wading through a lengthy switch() statement.\r
+\r
+                                    \r
+                            DANGEROUS PITFALLS\r
+\r
+One of the most dangerous pitfalls can occur with the use of gets(). This\r
+function accepts input from the stream stdin until it receives a newline\r
+character, which it does not pass to the program. All the data it\r
+receives is stored in memory starting at the address of the specified\r
+string, and quite happily overflowing into other variables! This danger\r
+can be avoided by using fgets() that allows a maximum number of\r
+characters to be specified, so you can avoid overflow problems. Notice\r
+though that fgets() does retain the newline character scanf() is another\r
+function best avoided. It accepts input from stdin and stores the\r
+received data at the addresses provided to it. If those addresses are not\r
+really addresses where the data ends up is anybodys guess!\r
+\r
+This example is okay, since scanf() has been told to store the data at\r
+the addresses occupied by the two variables `x' and `y'.\r
+\r
+\r
+     void main()\r
+     {\r
+          int x;\r
+          int y;\r
+     \r
+          scanf("%d%d",&x,&y);\r
+     }\r
+     \r
+But in this example scanf() has been told to store the data at the\r
+addresses suggested by the current values of `x' and `y'! An easy and\r
+common mistake to make, and yet one that can have very peculiar effects.\r
+\r
+\r
+     void main()\r
+     {\r
+          int x;\r
+          int y;\r
+     \r
+          scanf("%d%d",x,y);\r
+     }\r
+     \r
+The answer is, don't use scanf(), use fgets() and parse your string\r
+manually using the standard library functions strtod(), strtol() and\r
+strtoul().\r
+\r
+Here is the basis of a simple input string parser that returns the\r
+individual input fields from an entered string;\r
+\r
+     #include <stdio.h>\r
+     #include <string.h>\r
+     \r
+     void main()\r
+     {\r
+          char input[80];\r
+          char *p;\r
+     \r
+          puts("\nEnter a string ");\r
+          fgets(input,79,stdin);\r
+     \r
+          /* now parse string for input fields */\r
+          puts("The fields entered are:");\r
+          p = strtok(input,", ");\r
+          while(p)\r
+          {\r
+               puts(p);\r
+               p = strtok(NULL,", ");\r
+          }\r
+     }\r
+     \r
+                                    \r
+                                 SIZEOF\r
+\r
+A preprocessor instruction, `sizeof', returns the size of an item, be it\r
+a structure, pointer, string or whatever. However! take care when using\r
+`sizeof'. Consider the following program;\r
+\r
+\r
+     #include <stdio.h>\r
+     #include <mem.h>\r
+     \r
+     char string1[80]; char *text = "This is a string of data" ;\r
+     \r
+     void main()\r
+     {\r
+          /* Initialise string1 correctly */\r
+          memset(string1,0,sizeof(string1));\r
+     \r
+          /* Copy some text into string1 ? */\r
+          memcpy(string1,text,sizeof(text));\r
+     \r
+          /* Display string1 */\r
+          printf("\nString 1 = %s\n",string1);\r
+     }\r
+     \r
+What it is meant to do is initialise all 80 elements of string1 to\r
+zeroes, which it does alright, and then copy the constant string `text'\r
+into the variable `string1'. However, variable text is a pointer, and so\r
+the sizeof(text) instruction returns the size of the character pointer\r
+(perhaps two bytes) rather than the length of the string pointed to by\r
+the pointer!  If the length of the string pointed to by `text' happened\r
+to be the same as the size of a character pointer then no error would be\r
+noticed.\r
+\r
+                                    \r
+                               INTERRUPTS\r
+\r
+The IBM PC BIOS and DOS contain functions that may be called by a program\r
+by way of the function's interrupt number. The address of the function\r
+assigned to each interrupt is recorded in a table in RAM called the\r
+interrupt vector table. By changing the address of an interrupt vector a\r
+program can effectively disable the original interrupt function and\r
+divert any calls to it to its own function. This was done by the critical\r
+error handler described in the section on error handling.\r
+\r
+Borland's Turbo C provides two library functions for reading and changing\r
+an interrupt vector. These are: setvect() and getvect(). The\r
+corresponding Microsoft C library functions are: _dos_getvect() and\r
+_dos_setvect().\r
+\r
+getvect() has the function prototype;\r
+\r
+     void interrupt(*getvect(int interrupt_no))();\r
+\r
+setvect() has the prototype;\r
+\r
+     void setvect(int interrupt_no, void interrupt(*func)());\r
+\r
+To read and save the address of an existing interrupt a program uses\r
+getvect() thus;\r
+\r
+     /* Declare variable to record old interrupt */\r
+     void interrupt(*old)(void);\r
+     \r
+     main()\r
+     {\r
+          /* get old interrupt vector */\r
+          old = getvect(0x1C);\r
+          .\r
+          .\r
+          .\r
+     }\r
+     \r
+Where 0x1C is the interrupt vector to be retrieved.\r
+\r
+To then set the interrupt vector to a new address, our own function, we\r
+use setvect() thus;\r
+\r
+     void interrupt new(void)\r
+     {\r
+          .\r
+          .\r
+          /* New interrupt function */\r
+          .\r
+          .\r
+          .\r
+     }\r
+     \r
+     main()\r
+     {\r
+          .\r
+          .\r
+          .\r
+          setvect(0x1C,new);\r
+          .\r
+          .\r
+          .\r
+          .\r
+     }\r
+     \r
+There are two important points to note about interrupts;\r
+\r
+First, if the interrupt is called by external events then before changing\r
+the vector you MUST disable the interrupt callers using disable() and\r
+then re-enable the interrupts after the vector has been changed using\r
+enable().  If a call is made to the interrupt while the vector is being\r
+changed ANYTHING could happen!\r
+\r
+Secondly, before your program terminates and returns to DOS you must\r
+reset any changed interrupt vectors! The exception to this is the\r
+critical error handler interrupt vector that is restored automatically by\r
+DOS, so your program needn't bother restoring it.\r
+\r
+This example program hooks the PC clock timer interrupt to provide a\r
+background clock process while the rest of the program continues to run.\r
+If included with your own program that requires a constantly displayed\r
+clock on screen, you need only amend the display coordinates in the call\r
+to puttext(). Sincle the closk display code is called by a hardware\r
+issued interrupt, your program can start the clock and forget it until it\r
+terminates.\r
+\r
+     \r
+     /* Compile in LARGE memory model */\r
+     \r
+     #include <stdio.h>\r
+     #include <dos.h>\r
+     #include <time.h>\r
+     #include <conio.h>\r
+     #include <stdlib.h>\r
+     \r
+     enum { FALSE, TRUE };\r
+     \r
+     #define COLOUR (BLUE << 4) | YELLOW\r
+     \r
+     #define BIOS_TIMER  0x1C\r
+     \r
+     static unsigned installed = FALSE;\r
+     static void interrupt (*old_tick) (void);\r
+     \r
+     static void interrupt tick (void)\r
+     {\r
+          int i;\r
+          struct tm *now;\r
+          time_t this_time;\r
+          char time_buf[9];\r
+          static time_t last_time = 0L;\r
+          static char video_buf[20] =\r
+          {\r
+               ' ', COLOUR, '0', COLOUR, '0', COLOUR, ':', COLOUR, '0',\r
+     COLOUR,\r
+               '0', COLOUR, ':', COLOUR, '0', COLOUR, '0', COLOUR, ' ',\r
+     COLOUR\r
+          };\r
+     \r
+          enable ();\r
+     \r
+          if (time (&this_time) != last_time)\r
+          {\r
+               last_time = this_time;\r
+     \r
+               now = localtime(&this_time);\r
+     \r
+               sprintf(time_buf, "%02d:%02d.%02d",now->tm_hour,now-\r
+     >tm_min,now->tm_sec);\r
+     \r
+               for (i = 0; i < 8; i++)\r
+               {\r
+                    video_buf[(i + 1) << 1] = time_buf[i];\r
+               }\r
+     \r
+               puttext (71, 1, 80, 1, video_buf);\r
+          }\r
+     \r
+          old_tick ();\r
+     }\r
+     \r
+     void stop_clock (void)\r
+     {\r
+          if (installed)\r
+          {\r
+               setvect (BIOS_TIMER, old_tick);\r
+               installed = FALSE;\r
+          }\r
+     }\r
+     \r
+     void start_clock (void)\r
+     {\r
+          static unsigned first_time = TRUE;\r
+     \r
+          if (!installed)\r
+          {\r
+               if (first_time)\r
+               {\r
+                    atexit (stop_clock);\r
+                    first_time = FALSE;\r
+               }\r
+     \r
+               old_tick = getvect (BIOS_TIMER);\r
+               setvect (BIOS_TIMER, tick);\r
+               installed = TRUE;\r
+          }\r
+     }\r
+                                    \r
+                                 SIGNAL\r
+\r
+Interrupts raised by the host computer can be trapped and diverted in\r
+several ways. A simple method is to use signal().\r
+\r
+Signal() takes two parameters in the form;\r
+\r
+     void (*signal (int sig, void (*func) (int))) (int);\r
+\r
+The first parameter, `sig' is the signal to be caught. These are often\r
+predefined by the header file `signal.h'.\r
+\r
+The second parameter is a pointer to a function to be called when the\r
+signal is raised. This can either be a user function, or a macro defined\r
+in the header file `signal.h' to do some arbitrary task, such as ignore\r
+the signal for example.\r
+\r
+On a PC platform, it is often useful to disable the `ctrl-break' key\r
+combination that is used to terminate a running program by the user. The\r
+following PC signal() call replaces the predefined signal `SIGINT', which\r
+equates to the ctrl-break interrupt request, with the predefined macro\r
+`SIG-IGN', ignore the request;\r
+\r
+\r
+     signal(SIGINT,SIG_IGN);\r
+\r
+This example catches floating point errors on a PC, and zero divisions!\r
+\r
+     #include <stdio.h>\r
+     #include <signal.h>\r
+     \r
+     void (*old_sig)();\r
+     \r
+     void catch(int sig)\r
+     {\r
+          printf("Catch was called with: %d\n",sig);\r
+     }\r
+     \r
+     \r
+     void main()\r
+     {\r
+          int a;\r
+          int b;\r
+     \r
+          old_sig = signal(SIGFPE,catch);\r
+     \r
+          a = 0;\r
+          b = 10 / a;\r
+     \r
+          /* Restore original handler before exiting! */\r
+          signal(SIGFPE,old_sig);\r
+     }\r
+     \r
+                                    \r
+                          SORTING AND SEARCHING\r
+\r
+The ANSI C standard defines qsort(), a function for sorting a table of\r
+data. The function follows the format;\r
+\r
+     qsort(void *base,size_t elements,size_t width,int (*cmp)(void *,\r
+void *));\r
+\r
+The following short program illustrates the use of qsort() with a\r
+character array.\r
+\r
+     #include <string.h>\r
+     \r
+     main()\r
+     {\r
+          int n;\r
+          char data[10][20];\r
+     \r
+          /* Initialise some arbirary data */\r
+     \r
+          strcpy(data[0],"RED");\r
+          strcpy(data[1],"BLUE");\r
+          strcpy(data[2],"GREEN");\r
+          strcpy(data[3],"YELLOW");\r
+          strcpy(data[4],"INDIGO");\r
+          strcpy(data[5],"BROWN");\r
+          strcpy(data[6],"BLACK");\r
+          strcpy(data[7],"ORANGE");\r
+          strcpy(data[8],"PINK");\r
+          strcpy(data[9],"CYAN");\r
+     \r
+          /* Sort the data table */\r
+          qsort(data[0],10,20,strcmp);\r
+     \r
+          /* Print the data table */\r
+          for(n = 0; n < 10; n++)\r
+               puts(data[n]);\r
+     }\r
+     \r
+\r
+Here is a program that implements the shell sort algorithm (this one is\r
+based on the routine in K & R), which sorts arrays of pointers based upon\r
+the data pointed to by the pointers;\r
+\r
+     #include <stdio.h>\r
+     #include <stdlib.h>\r
+     #include <string.h>\r
+     \r
+     #define LINELEN     80\r
+     #define MAXLINES    2000\r
+     \r
+     char *lines[MAXLINES];\r
+     int lastone;\r
+     \r
+     void SHELL(void);\r
+     \r
+     void SHELL()\r
+     {\r
+          /* SHELL Sort Courtesy of K & R */\r
+     \r
+          int gap;\r
+          int i;\r
+          int j;\r
+          char temp[LINELEN];\r
+     \r
+          for(gap = lastone / 2; gap > 0; gap /= 2)\r
+          for(i = gap; i < lastone; i++)\r
+               for(j = i - gap; j >= 0 && strcmp(lines[j] , lines[j +\r
+     gap]) >\r
+                  0; j -= gap)\r
+               {\r
+                    strcpy(temp,lines[j]);\r
+                    strcpy(lines[j] , lines[j + gap]);\r
+                    strcpy(lines[j + gap] , temp);\r
+     \r
+               }\r
+     }\r
+     \r
+     void main(int argc, char *argv[])\r
+     {\r
+          FILE *fp;\r
+          char buff[100];\r
+          int n;\r
+\r
+          /* Check command line parameter has been given */\r
+          if (argc != 2)\r
+          {\r
+               printf("\nError: Usage is SERVSORT file");\r
+               exit(0);\r
+          }\r
+     \r
+          /* Attempt to open file for updating */\r
+          fp = fopen(argv[1],"r+");\r
+          if (fp == NULL)\r
+          {\r
+               printf("\nError: Unable to open %s",argv[1]);\r
+               exit(0);\r
+          }\r
+     \r
+          /* Initialise element counter to zero */\r
+          lastone = 0;\r
+     \r
+          /* Read file to be sorted */\r
+          while((fgets(buff,100,fp)) != NULL)\r
+          {\r
+               /* Allocate memory block*/\r
+               lines[lastone] = malloc(LINELEN);\r
+               if (lines[lastone] == NULL)\r
+               {\r
+                    printf("\nError: Unable to allocate memory");\r
+                    fclose(fp);\r
+                    exit(0);\r
+               }\r
+               strcpy(lines[lastone],buff);\r
+               lastone++;\r
+     \r
+               if (lastone > MAXLINES)\r
+               {\r
+                    printf("\nError: Too many lines in source file");\r
+                    exit(0);\r
+               }\r
+          }\r
+          /* Call sort function */\r
+          SHELL();\r
+     \r
+          /* Close file */\r
+          fclose(fp);\r
+     \r
+          /* Reopen file in create mode */\r
+          fp = fopen(argv[1],"w+");\r
+     \r
+          /* Copy sorted data from memory to disk */\r
+          for(n = 0; n < lastone; n++)\r
+               fputs(lines[n],fp);\r
+     \r
+          /* Close file finally */\r
+          fclose(fp);\r
+     \r
+          /* Return to calling program */\r
+          return(1);\r
+     }\r
+     \r
+\r
+If we want to use qsort() with a table of pointers we have to be a bit\r
+more clever than usual.\r
+\r
+This example uses the colours again, but this time they are stored in\r
+main memory and indexed by a table of pointers. Because we have a table\r
+of pointers to sort there are two differences between this program's\r
+qsort() and the previous one;\r
+\r
+First we can't use strcmp() as the qsort() comparison function, secondly\r
+the width of the table being sorted is sizeof(char *), that is the size\r
+of a character pointer.\r
+\r
+Notice the comparison function cmp() that receives two parameters, both\r
+are pointers to a pointer. qsort() sends to this function the values held\r
+in data[], which are in turn pointers to the data. So we need to use this\r
+indirection to locate the data, otherwise we would be comparing the\r
+addresses at which the data is held rather than the data itself!\r
+\r
+     #include <alloc.h>\r
+     #include <string.h>\r
+     \r
+     /* Function prototype for comparison function */\r
+     int (cmp)(char **,char **);\r
+     \r
+     int cmp(char **s1, char **s2)\r
+     {\r
+          /* comparison function using pointers to pointers */\r
+          return(strcmp(*s1,*s2));\r
+     }\r
+     \r
+     main()\r
+     {\r
+          int n;\r
+          char *data[10];\r
+     \r
+          for(n = 0; n < 10; n++)\r
+               data[n] = malloc(20);\r
+     \r
+          strcpy(data[0],"RED");\r
+          strcpy(data[1],"BLUE");\r
+          strcpy(data[2],"GREEN");\r
+          strcpy(data[3],"YELLOW");\r
+          strcpy(data[4],"INDIGO");\r
+          strcpy(data[5],"BROWN");\r
+          strcpy(data[6],"BLACK");\r
+          strcpy(data[7],"ORANGE");\r
+          strcpy(data[8],"PINK");\r
+          strcpy(data[9],"CYAN");\r
+     \r
+          /* The data table is comprised of pointers */\r
+          /* so the call to qsort() must reflect this */\r
+          qsort(data,10,sizeof(char *),cmp);\r
+     \r
+          for(n = 0; n < 10; n++)\r
+               puts(data[n]);\r
+     }\r
+     \r
+The quick sort is a fast sorting algorithm that works by subdividing the\r
+data table into two sub-tables and then subdividing the sub-tables. As it\r
+subdivides the table, so it compares the elements in the table and swaps\r
+them as required.\r
+\r
+The following program implements the quick sort algorithm, which is\r
+usually already used by qsort();\r
+\r
+\r
+     #include <string.h>\r
+     \r
+     #define MAXELE  2000\r
+     \r
+     char data[10][20];\r
+     int lastone;\r
+     \r
+     void QKSORT()\r
+     {\r
+          /* Implementation of QUICKSORT algorithm */\r
+     \r
+          int i;\r
+          int j;\r
+          int l;\r
+          int p;\r
+          int r;\r
+          int s;\r
+          char temp[100];\r
+          static int sl[MAXELE][2];\r
+     \r
+          /* sl[] is an index to the sub-table */\r
+     \r
+          l = 0;\r
+          r = lastone;\r
+          p = 0;\r
+     \r
+          do\r
+          {\r
+               while(l < r)\r
+               {\r
+                    i = l;\r
+                    j = r;\r
+                    s = -1;\r
+     \r
+                    while(i < j)\r
+                    {\r
+                         if (strcmp(data[i],data[j]) > 0)\r
+                         {\r
+                              strcpy(temp,data[i]);\r
+                              strcpy(data[i],data[j]);\r
+                              strcpy(data[j],temp);\r
+                              s = 0 - s;\r
+                         }\r
+     \r
+                         if (s == 1)\r
+                              i++;\r
+                         else\r
+                              j--;\r
+                    }\r
+     \r
+                    if (i + 1 < r)\r
+                    {\r
+                         p++;\r
+                         sl[p][0] = i + 1;\r
+                         sl[p][1] = r;\r
+                    }\r
+                    r = i - 1;\r
+               }\r
+               if (p != 0)\r
+               {\r
+                    l = sl[p][0];\r
+                    r = sl[p][1];\r
+                    p--;\r
+               }\r
+          }\r
+          while(p > 0);\r
+     }\r
+     \r
+     main()\r
+     {\r
+          int n;\r
+     \r
+          /* Initialise arbitrary data */\r
+          strcpy(data[0],"RED");\r
+          strcpy(data[1],"BLUE");\r
+          strcpy(data[2],"GREEN");\r
+          strcpy(data[3],"YELLOW");\r
+          strcpy(data[4],"INDIGO");\r
+          strcpy(data[5],"BROWN");\r
+          strcpy(data[6],"BLACK");\r
+          strcpy(data[7],"ORANGE");\r
+          strcpy(data[8],"PINK");\r
+          strcpy(data[9],"CYAN");\r
+     \r
+          /* Set last element indicator */\r
+          lastone = 9;\r
+     \r
+          /* Call quick sort function */\r
+          QKSORT();\r
+     \r
+          /* Display sorted list */\r
+          for(n = 0; n < 10; n++)\r
+               puts(data[n]);\r
+     \r
+     }\r
+     \r
+A table sorted into ascending order can be searched with bsearch(), this\r
+takes the format;\r
+\r
+     bsearch(key,base,num_elements,width,int (*cmp)(void *, void *));\r
+\r
+bsearch() returns a pointer to the first element in the table that\r
+matches the key, or zero if no match is found.\r
+\r
+Or you can write your own binary search function thus;\r
+\r
+     int BSRCH(char *key, void *data, int numele, int width)\r
+     {\r
+          /* A binary search function returning one if found */\r
+          /* Zero if not found */\r
+     \r
+          int bp;\r
+          int tp;\r
+          int mp;\r
+          int result;\r
+          char *p;\r
+     \r
+          bp = 0;\r
+          tp = numele;\r
+          mp = (tp + bp) / 2;\r
+     \r
+          /* Locate element mp in table by assigning pointer to start */\r
+          /* and incrementing it by width * mp */\r
+          p = data;\r
+          p += width * mp;\r
+     \r
+          while((result = strcmp(p,key)) != 0)\r
+          {\r
+               if (mp >= tp)\r
+                    /* Not found! */\r
+                    return(0);\r
+               if (result < 0)\r
+                    bp = mp + 1;\r
+               else\r
+                    tp = mp - 1;\r
+     \r
+               mp = (bp + tp) / 2;\r
+               p = data;\r
+               p += width * mp;\r
+          }\r
+          return(1);\r
+     }\r
+     \r
+     void main()\r
+     {\r
+          int result;\r
+          char data[10][20];\r
+     \r
+          /* Initialise some arbirary data */\r
+     \r
+          strcpy(data[0],"RED");\r
+          strcpy(data[1],"BLUE");\r
+          strcpy(data[2],"GREEN");\r
+          strcpy(data[3],"YELLOW");\r
+          strcpy(data[4],"INDIGO");\r
+          strcpy(data[5],"BROWN");\r
+          strcpy(data[6],"BLACK");\r
+          strcpy(data[7],"ORANGE");\r
+          strcpy(data[8],"PINK");\r
+          strcpy(data[9],"CYAN");\r
+     \r
+          /* Sort the data table */\r
+          qsort(data[0],10,20,strcmp);\r
+     \r
+          result = BSRCH("CYAN",data[0],10,20);\r
+     \r
+          printf("\n%s\n",(result == 0) ? "Not found" : "Located okay");\r
+     }\r
+     \r
+There are other sorting algorithms as well. This program incorporates the\r
+QUICK SORT, BUBBLE SORT, FAST BUBBLE SORT, INSERTION SORT and SHELL SORT\r
+for comparing how each performs on a random 1000 item string list;\r
+\r
+     #include <stdio.h>\r
+     #include <stdlib.h>\r
+     #include <string.h>\r
+     \r
+     char data[1000][4];\r
+     char save[1000][4];\r
+     \r
+     int lastone;\r
+     \r
+     void INITDATA(void);\r
+     void QKSORT(void);\r
+     void SHELL(void);\r
+     void BUBBLE(void);\r
+     void FBUBBLE(void);\r
+     void INSERTION(void);\r
+     void MKDATA(void);\r
+     \r
+     void QKSORT()\r
+     {\r
+          /* Implementation of QUICKSORT algorithm */\r
+     \r
+          int i;\r
+          int j;\r
+          int l;\r
+          int p;\r
+          int r;\r
+          int s;\r
+          char temp[20];\r
+          static int sl[1000][2];\r
+     \r
+          l = 0;\r
+          r = lastone;\r
+          p = 0;\r
+     \r
+          do\r
+          {\r
+               while(l < r)\r
+               {\r
+                    i = l;\r
+                    j = r;\r
+                    s = -1;\r
+     \r
+                    while(i < j)\r
+                    {\r
+                         if (strcmp(data[i],data[j]) > 0)\r
+                         {\r
+                              strcpy(temp,data[i]);\r
+                              strcpy(data[i],data[j]);\r
+                              strcpy(data[j],temp);\r
+                              s = 0 - s;\r
+                         }\r
+     \r
+                         if (s == 1)\r
+                              i++;\r
+                         else\r
+                              j--;\r
+                    }\r
+     \r
+                    if (i + 1 < r)\r
+                    {\r
+                         p++;\r
+                         sl[p][0] = i + 1;\r
+                         sl[p][1] = r;\r
+                    }\r
+                    r = i - 1;\r
+               }\r
+               if (p != 0)\r
+               {\r
+                    l = sl[p][0];\r
+                    r = sl[p][1];\r
+                    p--;\r
+               }\r
+          }\r
+          while(p > 0);\r
+     }\r
+     \r
+     void SHELL()\r
+     {\r
+          /* SHELL Sort Courtesy of K & R */\r
+     \r
+          int gap;\r
+          int i;\r
+          int j;\r
+          char temp[20];\r
+     \r
+          for(gap = lastone / 2; gap > 0; gap /= 2)\r
+          for(i = gap; i < lastone; i++)\r
+               for(j = i - gap; j >= 0 && strcmp(data[j] , data[j + gap])\r
+     > 0;\r
+                      j -= gap)\r
+               {\r
+                    strcpy(temp,data[j]);\r
+                    strcpy(data[j] , data[j + gap]);\r
+                    strcpy(data[j + gap] , temp);\r
+               }\r
+     }\r
+     \r
+     void BUBBLE()\r
+     {\r
+          int a;\r
+          int b;\r
+          char temp[20];\r
+     \r
+          for(a = lastone; a >= 0; a--)\r
+          {\r
+               for(b = 0; b < a; b++)\r
+               {\r
+                    if(strcmp(data[b],data[b + 1]) > 0)\r
+                    {\r
+                         strcpy(temp,data[b]);\r
+                         strcpy(data[b] , data[b + 1]);\r
+                         strcpy(data[b + 1] , temp);\r
+                    }\r
+               }\r
+          }\r
+     }\r
+     \r
+     void FBUBBLE()\r
+     {\r
+          /* bubble sort with swap flag*/\r
+     \r
+          int a;\r
+          int b;\r
+          int s;\r
+          char temp[20];\r
+     \r
+          s = 1;\r
+     \r
+          for(a = lastone; a >= 0 && s == 1; a--)\r
+          {\r
+               s = 0;\r
+               for(b = 0; b < a; b++)\r
+               {\r
+                    if(strcmp(data[b],data[b + 1]) > 0)\r
+                    {\r
+                         strcpy(temp,data[b]);\r
+                         strcpy(data[b] , data[b + 1]);\r
+                         strcpy(data[b + 1] , temp);\r
+                         s = 1;\r
+                    }\r
+               }\r
+          }\r
+     }\r
+     \r
+     void INSERTION()\r
+     {\r
+          int a;\r
+          int b;\r
+          char temp[20];\r
+     \r
+          for(a = 0; a < lastone; a++)\r
+          {\r
+               b = a;\r
+               strcpy(temp,data[a + 1]);\r
+               while(b >= 0)\r
+               {\r
+                    if (strcmp(temp,data[b]) < 0)\r
+                    {\r
+                         strcpy(data[b+1],data[b]);\r
+                         b--;\r
+                    }\r
+                    else\r
+                         break;\r
+               }\r
+               strcpy(data[b+1],temp);\r
+          }\r
+     }\r
+     \r
+     void MKDATA()\r
+     {\r
+          /* Initialise arbitrary data */\r
+          /* Uses random(), which is not ANSI C */\r
+          /* Returns a random number between 0 and n - 1 */\r
+     \r
+          int n;\r
+          for(n = 0; n < 1000; n++)\r
+               sprintf(save[n],"%d",random(1000));\r
+     }\r
+     \r
+     void INITDATA()\r
+     {\r
+          int n;\r
+     \r
+          for(n = 0 ; n < 1000; n++)\r
+               strcpy(data[n],save[n]);\r
+     }\r
+     \r
+     void main()\r
+     {\r
+          MKDATA();\r
+     \r
+          /* Initialise arbitrary data */\r
+          INITDATA();\r
+     \r
+          /* Set last element indicator */\r
+          lastone = 999;\r
+     \r
+          /* Call quick sort function */\r
+          QKSORT();\r
+     \r
+     \r
+          /* Initialise arbitrary data */\r
+          INITDATA();\r
+     \r
+          /* Set last element indicator */\r
+          lastone = 1000;\r
+     \r
+          /* Call shell sort function */\r
+          SHELL();\r
+     \r
+          /* Initialise arbitrary data */\r
+          INITDATA();\r
+     \r
+          /* Set last element indicator */\r
+          lastone = 999;\r
+     \r
+          /* Call bubble sort function */\r
+          BUBBLE();\r
+     \r
+          /* Initialise arbitrary data */\r
+          INITDATA();\r
+     \r
+          /* Set last element indicator */\r
+          lastone = 999;\r
+     \r
+          /* Call bubble sort with swap flag function */\r
+          FBUBBLE();\r
+     \r
+          /* Initialise arbitrary data */\r
+          INITDATA();\r
+     \r
+          /* Set last element indicator */\r
+          lastone = 999;\r
+     \r
+          /* Call insertion sort function */\r
+          INSERTION();\r
+     }\r
+\r
+Here are the profiler results of the above test program run on 1000 and\r
+5000 random items;\r
+\r
+STRING SORT - 1000 RANDOM ITEMS\r
+\r
+FBUBBLE        26.436 sec  41%\r
+|********************************************\r
+BUBBLE         26.315 sec  41%\r
+|*******************************************\r
+INSERTION      10.210 sec  15% |***************\r
+SHELL               0.8050 sec   1%  |*\r
+QKSORT         0.3252 sec  <1% |\r
+\r
+STRING SORT - 5000 RANDOM ITEMS\r
+\r
+FBUBBLE        563.70 sec  41%\r
+|********************************************\r
+BUBBLE         558.01 sec  41%\r
+|********************************************\r
+INSERTION      220.61 sec  16% |***************\r
+SHELL               5.2531 sec  <1% |\r
+QKSORT         0.8379 sec  <1% |\r
+\r
+Here is the same test program amended for sorting tables of integers;\r
+\r
+     /* Integer sort test program */\r
+     \r
+     #include <stdio.h>\r
+     #include <stdlib.h>\r
+     \r
+     void INITDATA(void);\r
+     void QKSORT(void);\r
+     void SHELL(void);\r
+     void BUBBLE(void);\r
+     void FBUBBLE(void);\r
+     void INSERTION(void);\r
+     void MKDATA(void);\r
+     \r
+     int data[1000];\r
+     int save[1000];\r
+     \r
+     int lastone;\r
+     \r
+     void QKSORT()\r
+     {\r
+          /* Implementation of QUICKSORT algorithm */\r
+     \r
+          int i;\r
+          int j;\r
+          int l;\r
+          int p;\r
+          int r;\r
+          int s;\r
+          int temp;\r
+          static int sl[1000][2];\r
+     \r
+          l = 0;\r
+          r = lastone;\r
+          p = 0;\r
+     \r
+          do\r
+          {\r
+               while(l < r)\r
+               {\r
+                    i = l;\r
+                    j = r;\r
+                    s = -1;\r
+     \r
+                    while(i < j)\r
+                    {\r
+                         if (data[i] > data[j])\r
+                         {\r
+                              temp = data[i];\r
+                              data[i] = data[j];\r
+                              data[j] = temp;\r
+                              s = 0 - s;\r
+                         }\r
+     \r
+                         if (s == 1)\r
+                              i++;\r
+                         else\r
+                              j--;\r
+                    }\r
+     \r
+                    if (i + 1 < r)\r
+                    {\r
+                         p++;\r
+                         sl[p][0] = i + 1;\r
+                         sl[p][1] = r;\r
+                    }\r
+                    r = i - 1;\r
+               }\r
+               if (p != 0)\r
+               {\r
+                    l = sl[p][0];\r
+                    r = sl[p][1];\r
+                    p--;\r
+               }\r
+          }\r
+          while(p > 0);\r
+     }\r
+     \r
+     void SHELL()\r
+     {\r
+          /* SHELL Sort Courtesy of K & R */\r
+     \r
+          int gap;\r
+          int i;\r
+          int j;\r
+          int temp;\r
+     \r
+          for(gap = lastone / 2; gap > 0; gap /= 2)\r
+          for(i = gap; i < lastone; i++)\r
+               for(j = i - gap; j >= 0 && data[j] > data[j + gap];\r
+                      j -= gap)\r
+               {\r
+                    temp = data[j];\r
+                    data[j] = data[j + gap];\r
+                    data[j + gap] = temp;\r
+               }\r
+     }\r
+     \r
+     void BUBBLE()\r
+     {\r
+          int a;\r
+          int b;\r
+          int temp;\r
+     \r
+          for(a = lastone; a >= 0; a--)\r
+          {\r
+               for(b = 0; b < a; b++)\r
+               {\r
+                    if(data[b] > data[b + 1])\r
+                    {\r
+                         temp = data[b];\r
+                         data[b] = data[b + 1];\r
+                         data[b + 1] = temp;\r
+                    }\r
+               }\r
+          }\r
+     }\r
+     \r
+     void FBUBBLE()\r
+     {\r
+          /* bubble sort with swap flag */\r
+     \r
+          int a;\r
+          int b;\r
+          int s;\r
+          int temp;\r
+     \r
+          s = 1;\r
+     \r
+          for(a = lastone; a >= 0 && s == 1; a--)\r
+          {\r
+               s = 0;\r
+               for(b = 0; b < lastone - a; b++)\r
+               {\r
+                    if(data[b] > data[b + 1])\r
+                    {\r
+                         temp = data[b];\r
+                         data[b] = data[b + 1];\r
+                         data[b + 1] = temp;\r
+                         s = 1;\r
+                    }\r
+               }\r
+          }\r
+     }\r
+     \r
+     void INSERTION()\r
+     {\r
+          int a;\r
+          int b;\r
+          int temp;\r
+     \r
+          for(a = 0; a < lastone; a++)\r
+          {\r
+               b = a;\r
+               temp = data[a + 1];\r
+               while(b >= 0)\r
+               {\r
+                    if (temp < data[b])\r
+                    {\r
+                         data[b+1] = data[b];\r
+                         b--;\r
+                    }\r
+                    else\r
+                         break;\r
+               }\r
+               data[b+1] = temp;\r
+          }\r
+     }\r
+     \r
+     void MKDATA()\r
+     {\r
+          int n;\r
+     \r
+          for(n = 0; n < 1000; n++)\r
+               save[n] = random(1000);\r
+     }\r
+     \r
+     void INITDATA()\r
+     {\r
+          int n;\r
+     \r
+          for(n = 0; n < 1000; n++)\r
+          data[n] = save[n];\r
+     }\r
+     \r
+     void main()\r
+     {\r
+          int n;\r
+     \r
+          /* Create 1000 random elements */\r
+          MKDATA();\r
+     \r
+          /* Initialise arbitrary data */\r
+          INITDATA();\r
+     \r
+          /* Set last element indicator */\r
+          lastone = 999;\r
+     \r
+          /* Call quick sort function */\r
+          QKSORT();\r
+     \r
+          /* Initialise arbitrary data */\r
+          INITDATA();\r
+     \r
+          /* Set last element indicator */\r
+          lastone = 1000;\r
+     \r
+          /* Call shell sort function */\r
+          SHELL();\r
+     \r
+          /* Initialise arbitrary data */\r
+          INITDATA();\r
+     \r
+          /* Set last element indicator */\r
+          lastone = 999;\r
+     \r
+          /* Call bubble sort function */\r
+          BUBBLE();\r
+     \r
+          /* Initialise arbitrary data */\r
+          INITDATA();\r
+     \r
+          /* Set last element indicator */\r
+          lastone = 999;\r
+     \r
+          /* Call bubble sort with swap flag function */\r
+          FBUBBLE();\r
+     \r
+          /* Initialise arbitrary data */\r
+          INITDATA();\r
+     \r
+          /* Set last element indicator */\r
+          lastone = 999;\r
+     \r
+          /* Call insertion sort function */\r
+          INSERTION();\r
+     }\r
+\r
+And here are the profiler results for this program;\r
+\r
+INTEGER SORTS - 1000 RANDOM NUMBERS (0 - 999)\r
+\r
+FBUBBLE        3.7197 sec  41%\r
+|********************************************\r
+BUBBLE         3.5981 sec  39%\r
+|******************************************\r
+INSERTION      1.4258 sec  15% |***************\r
+SHELL               0.1207 sec   1%  |*\r
+QKSORT         0.0081 sec  <1% |\r
+\r
+INTEGER SORTS - 5000 RANDOM NUMBERS (0 - 999)\r
+\r
+FBUBBLE        92.749 sec  42%\r
+|********************************************\r
+BUBBLE         89.731 sec  41%\r
+|********************************************\r
+INSERTION      35.201 sec  16% |***************\r
+SHELL               0.9838 sec  <1% |\r
+QKSORT         0.0420 sec  <1% |\r
+\r
+INTEGER SORTS - 5000 RANDOM NUMBERS (0 - 99)\r
+\r
+FBUBBLE        92.594 sec  42% |*****************************************\r
+BUBBLE         89.595 sec  40% |****************************************\r
+INSERTION      35.026 sec  16% |***************\r
+SHELL               0.7563 sec  <1% |\r
+QKSORT         0.6018 sec  <1% |\r
+\r
+INTEGER SORTS - 5000 RANDOM NUMBERS (0 - 9)\r
+\r
+FBUBBLE        89.003 sec  41%\r
+|*******************************************\r
+BUBBLE         86.921 sec  40%\r
+|*******************************************\r
+INSERTION      31.544 sec  14% |***************\r
+QKSORT         6.0358 sec   2%  |**\r
+SHELL               0.5424 sec  <1% |\r
+\r
+INTEGER SORTS - 5000 DESCENDING ORDERED NUMBERS\r
+\r
+FBUBBLE        122.99 sec  39%\r
+|******************************************\r
+BUBBLE         117.22 sec  37% |****************************************\r
+INSERTION      70.595 sec  22% |**********************\r
+SHELL               0.6438 sec  <1% |\r
+QKSORT         0.0741 sec  <1% |\r
+\r
+INTEGER SORTS - 5000 ORDERED NUMBERS\r
+\r
+BUBBLE         62.908 sec  99%\r
+|******************************************\r
+SHELL               0.3971 sec  <1% |\r
+INSERTION      0.0510 sec  <1% |\r
+QKSORT         0.0382 sec  <1% |\r
+FBUBBLE        0.0251 sec  <1% |\r
+\r
+INTEGER SORTS - 10000 RANDOM NUMBERS (0 - 999)\r
+\r
+FBUBBLE        371.18 sec  42% |****************************************\r
+BUBBLE         359.06 sec  41% |***************************************\r
+INSERTION      140.88 sec  16% |**************\r
+SHELL               2.0423 sec  <1% |\r
+QKSORT         0.6183 sec  <1% |\r
+\r
+Theory has it that the performance of a sorting algorithm is dependant\r
+upon;\r
+\r
+     a) the number of items to be sorted and\r
+     b) how unsorted the list is to start with.\r
+\r
+With this in mind it is worth testing the various sorting routines\r
+described here to determine which one will best suit your particular\r
+application. If you examine the above profiler results you will see that:\r
+\r
+     1) With an already sorted list FBUBBLE() executes fastest\r
+     \r
+     2) With a random list of small variations between the values SHELL()\r
+         executes fastest\r
+     \r
+     3) With a random list of large variations between the values\r
+     QKSORT()\r
+          executes the fastest\r
+\r
+What the profiler does not highlight is that when the comparison aspect\r
+of a sort function takes a disproportionately long time to execute in\r
+relation to the rest of the sort function, then the bubble sort with a\r
+swap flag will execute faster than the bubble sort with out a swap flag.\r
+\r
+When considering a sort routine take into consideration memory\r
+constraints and the type of data to be sorted as well as the relative\r
+performances of the sort functions. Generally, the faster a sort\r
+operates, the more memory it requires. Compare the simple bubble sort\r
+with the quick sort, and you will see that the quick sort requires far\r
+more memory than the bubble sort.\r
+\r
+                                    \r
+                        DYNAMIC MEMORY ALLOCATION\r
+\r
+\r
+If a program needs a table of data, but the size of the table is\r
+variable, perhaps for a list of all file names in the current directory,\r
+it is inefficient to waste memory by declaring a data table of the\r
+maximum possible size. Rather it is better to dynamically allocate the\r
+table as required.\r
+\r
+Turbo C allocates RAM as being available for dynamic allocation into an\r
+area called the "heap". The size of the heap varies with memory model.\r
+The tiny memory model defaults to occupy 64K of RAM. The small memory\r
+model allocates upto 64K for the program/code and heap with a far heap\r
+being available within the remainder of conventional memory. The other\r
+memory models make all conventional memory available to the heap. This is\r
+significant when programming in the tiny memory model when you want to\r
+reduce the memory overhead of your program to a minimum. The way to do\r
+this is to reduce the heap to a minimum size. The smallest is 1 byte.\r
+\r
+C provides a function malloc() which allocates a block of free memory of\r
+a specified size and returns a pointer to the start of the block; it also\r
+provides free() which deallocates a block of memory previously allocated\r
+by malloc(). Notice, however, that the IBM PC doesnot properly free\r
+blocks of memory, and contiuous use of malloc() and free() will\r
+fragmentise memory, eventually causing no memory to be available untill\r
+the program terminates.\r
+\r
+This program searches a specified file for a specified string (with case\r
+sensitivity). It uses malloc() to allocate just enough memory for the\r
+file to be read into memory.\r
+\r
+     #include <stdio.h>\r
+     #include <stdlib.h>\r
+     \r
+     char *buffer;\r
+     \r
+     void main(int argc, char *argv[])\r
+     {\r
+          FILE *fp;\r
+          long flen;\r
+     \r
+          /* Check number of parameters */\r
+          if (argc != 3)\r
+          {\r
+               fputs("Usage is sgrep <text> <file spec>",stderr);\r
+               exit(0);\r
+          }\r
+     \r
+          /* Open stream fp to file */\r
+          fp = fopen(argv[2],"r");\r
+          if (!fp)\r
+          {\r
+               perror("Unable to open source file");\r
+               exit(0);\r
+          }\r
+     \r
+          /* Locate file end */\r
+          if(fseek(fp,0L,SEEK_END))\r
+          {\r
+               fputs("Unable to determine file length",stderr);\r
+               fclose(fp);\r
+               exit(0);\r
+          }\r
+     \r
+          /* Determine file length */\r
+          flen = ftell(fp);\r
+     \r
+          /* Check for error */\r
+          if (flen == -1L)\r
+          {\r
+               fputs("Unable to determine file length",stderr);\r
+               fclose(fp);\r
+               exit(0);\r
+          }\r
+     \r
+          /* Set file pointer to start of file */\r
+          rewind(fp);\r
+     \r
+          /* Allocate memory buffer */\r
+          buffer = malloc(flen);\r
+     \r
+          if (!buffer)\r
+          {\r
+               fputs("Unable to allocate memory",stderr);\r
+               fclose(fp);\r
+               exit(0);\r
+          }\r
+     \r
+          /* Read file into buffer */\r
+          fread(buffer,flen,1,fp);\r
+     \r
+          /* Check for read error */\r
+          if(ferror(fp))\r
+          {\r
+               fputs("Unable to read file",stderr);\r
+     \r
+               /* Deallocate memory block */\r
+               free(buffer);\r
+     \r
+               fclose(fp);\r
+               exit(0);\r
+          }\r
+     \r
+          printf("%s %s in %s",argv[1],(strstr(buffer,argv[1])) ? "was\r
+     found" : "was not found",argv[2]);\r
+     \r
+          /* Deallocate memory block before exiting */\r
+          free(buffer);\r
+          fclose(fp);\r
+     }\r
+                                    \r
+                         VARIABLE ARGUMENT LISTS\r
+\r
+\r
+Some functions, such as printf(), accept a variable number and type of\r
+arguments. C provides a mechanism to write your own functions which can\r
+accept a variable argument list. This mechanism is the va_ family defined\r
+in the header file `stdarg.h'.\r
+\r
+There are three macros which allow implementation of variable argument\r
+lists; va_start(), va_arg() and va_end() and a variable type va_list\r
+which defines an array which holds the information required by the\r
+macros.\r
+\r
+va_start() takes two parameters, the first is the va_list variable and\r
+the second is the last fixed parameter sent to the function. va_start()\r
+must be called before attempting to access the variable argument list as\r
+it sets up pointers required by the other macros.\r
+\r
+va_arg() returns the next variable from the argument list. It is called\r
+with two parameters, the first is the va_list variable and the second is\r
+the type of the argument to be extracted. So, if the next variable in the\r
+argument list is an integer, it can be extracted with;\r
+\r
+\r
+     <int> = va_arg(<va_list>,int);\r
+\r
+va_end() is called after extracting all required variables from the\r
+argument list, and simply tidies up the internal stack if appropriate.\r
+va_end() accepts a single parameter, the va_list variable.\r
+\r
+The following simple example program illustrates the basis for a printf()\r
+type implementation where the types of the arguments is not known, but\r
+can be determined from the fixed parameter. This example only caters for\r
+integer, string and character types, but could easily by extended to\r
+cater for other variable types as well by following the method\r
+illustrated;\r
+\r
+     #include <stdarg.h>\r
+     \r
+     char *ITOS(long x, char *ptr)\r
+     {\r
+          /* Convert a signed decimal integer to a string */\r
+     \r
+          long pt[9] = { 100000000, 10000000, 1000000, 100000, 10000,\r
+     1000, 100, 10, 1 };\r
+          int n;\r
+     \r
+          /* Check sign */\r
+          if (x < 0)\r
+          {\r
+               *ptr++ = '-';\r
+               /* Convert x to absolute */\r
+               x = 0 - x;\r
+          }\r
+     \r
+          for(n = 0; n < 9; n++)\r
+          {\r
+               if (x > pt[n])\r
+               {\r
+                    *ptr++ = 48 + x / pt[n];\r
+                    x %= pt[n];\r
+               }\r
+          }\r
+          return(ptr);\r
+     }\r
+     \r
+     void varfunc(char *format, ...)\r
+     {\r
+          va_list arg_ptr;\r
+          char output[1000];\r
+          char *ptr;\r
+          int bytes;\r
+          int x;\r
+          char *y;\r
+          char z;\r
+     \r
+          /* Initialise pointer to argument list */\r
+          va_start(arg_ptr, format);\r
+     \r
+          /* loop format string */\r
+          ptr = output;\r
+          bytes = 0;\r
+          while(*format)\r
+          {\r
+               /* locate next argument */\r
+               while(*format != '%')\r
+               {\r
+                    *ptr++ = *format++;\r
+                    bytes++;\r
+               }\r
+               /* A % has been located */\r
+               format++;\r
+               switch(*format)\r
+               {\r
+                    case '%' :  *ptr++ = '%';\r
+                             break;\r
+     \r
+                    case 'd' : /* integer expression follows */\r
+                            x = va_arg(arg_ptr,int);\r
+                            ptr = ITOS(x,ptr);\r
+                            *ptr = 0;\r
+                            format++;\r
+                            bytes += strlen(output) - bytes;\r
+                            break;\r
+     \r
+                    case 's' : /* String expression follows */\r
+                            y = va_arg(arg_ptr,char *);\r
+                            strcat(output,y);\r
+                            x = strlen(y);\r
+                            format++;\r
+                            ptr += x;\r
+                            bytes += x;\r
+                            break;\r
+     \r
+                    case 'c' : /* Char expression follows */\r
+                            z = va_arg(arg_ptr,char);\r
+                            *ptr++ = z;\r
+                            format++;\r
+                            bytes++;\r
+                            break;\r
+               }\r
+     \r
+          }\r
+     \r
+          /* Clean stack just in case! */\r
+          va_end(arg_ptr);\r
+     \r
+          /* Null terminate output string */\r
+          *ptr = 0;\r
+     \r
+          /* Display what we got */\r
+          printf("\nOUTPUT==%s",output);\r
+     }\r
+     \r
+     void main()\r
+     {\r
+          varfunc("%d %s %c",5,"hello world",49);\r
+     }\r
+     \r
+\r
+A simpler variation is to use vsprintf() rather than implementing our own\r
+variable argument list access. However, it is beneficial to understand\r
+how variable argument lists behave. The following is a simplification of\r
+the same program, but leaving the dirty work to the compiler;\r
+\r
+     #include <stdio.h>\r
+     #include <stdarg.h>\r
+     \r
+     void varfunc(char *format, ...)\r
+     {\r
+          va_list arg_ptr;\r
+          char output[1000];\r
+     \r
+          va_start(arg_ptr, format);\r
+     \r
+          vsprintf(output,format,arg_ptr);\r
+     \r
+          va_end(arg_ptr);\r
+     \r
+          /* Display what we got */\r
+          printf("\nOUTPUT==%s",output);\r
+     }\r
+     \r
+     void main()\r
+     {\r
+          varfunc("%d %s %c",5,"hello world",49);\r
+     }\r
+                                    \r
+                         TRIGONOMETRY FUNCTIONS\r
+\r
+The ANSI standard on C defines a number of trigonometry functions, all of\r
+which accept an angle parameter in radians;\r
+\r
+\r
+FUNCTION  PROTOTYPE                DESCRIPTION\r
+acos      double acos(double x)              arc cosine of x\r
+asin      double asin(double x)              arc sine of x\r
+atan      double atan(double x)              arc tangent of x\r
+atan2          double atan2(double x, double y)        arc tangent of y/x\r
+cos       double cos(double x)               cosine of x\r
+cosh      double cosh(double x)              hyperbolic cosine of x\r
+sin       double sin(double x)               sine of x\r
+sinh      double sinh(double x)              hyperbolic sine of x\r
+tan       double tan(double x)               tangent of x\r
+tanh      double tanh(double x)              hyperbolic tangent of x\r
+\r
+There are 2PI radians in a circle, therefore 1 radian is equal to 360/2PI\r
+degrees or approximately 57 degrees in 1 radian. The calculation of any\r
+of the above functions requires large floating point numbers to be used\r
+which is a very slow process. If you are going to use calls to a trig'\r
+function, it is a good idea to use a lookup table of values rather than\r
+keep on calling the function. This approach is used in the discussion on\r
+circle drawing later in this book.\r
+\r
+                                    \r
+                                 ATEXIT\r
+\r
+\r
+When ever a program terminates, it should close any open files (this is\r
+done for you by the C compiler's startup/termination code which it\r
+surrounds your program with), and restore the host computer to some\r
+semblance of order.  Within a large program where exit may occur from a\r
+number of locations it is a pain to have to keep on writing calls to the\r
+cleanup routine. Fortunately we don't have to!\r
+\r
+The ANSI standard on C describes a function, atexit(), which registers\r
+the specified function, supplied as a parameter to atexit(), as a\r
+function which is called immediately before terminating the program. This\r
+function is called automatically, so the following program calls\r
+`leave()' whether an error occurs or not;\r
+\r
+     #include <stdio.h>\r
+     \r
+     void leave()\r
+     {\r
+          puts("\nBye Bye!");\r
+     }\r
+     \r
+     void main()\r
+     {\r
+          FILE *fp;\r
+          int a;\r
+          int b;\r
+          int c;\r
+          int d;\r
+          int e;\r
+          char text[100];\r
+     \r
+          atexit(leave);\r
+     \r
+          fp = fopen("data.txt","w");\r
+     \r
+          if(!fp)\r
+          {\r
+               perror("Unable to create file");\r
+               exit(0);\r
+          }\r
+     \r
+          fprintf(fp,"1 2 3 4 5 \"A line of numbers\"");\r
+     \r
+          fflush(fp);\r
+     \r
+          if (ferror(fp))\r
+          {\r
+               fputs("Error flushing stream",stderr);\r
+               exit(1);\r
+          }\r
+     \r
+          rewind(fp);\r
+          if (ferror(fp))\r
+          {\r
+               fputs("Error rewind stream",stderr);\r
+               exit(1);\r
+          }\r
+     \r
+          fscanf(fp,"%d %d %d %d %d %s",&a,&b,&c,&d,&e,text);\r
+          if (ferror(fp))\r
+          {\r
+               /* Unless you noticed the deliberate bug earlier */\r
+               /* The program terminates here */\r
+               fputs("Error reading from stream",stderr);\r
+               exit(1);\r
+          }\r
+     \r
+          printf("\nfscanf() returned %d %d %d %d %d %s",a,b,c,d,e,text);\r
+     }\r
+                                    \r
+                            INCREASING SPEED\r
+\r
+\r
+In order to reduce the time your program spends executing it is essential\r
+to know your host computer. Most computers are very slow at displaying\r
+information on the screen. And the IBM PC is no exception to this. C\r
+offers various functions for displaying data, printf() being one of the\r
+most commonly used and also the slowest. Whenever possible try to use\r
+puts(varname) in place of printf("%s\n",varname). Remembering that puts()\r
+appends a newline to the string sent to the screen.\r
+\r
+When multiplying a variable by a constant which is a factor of 2 many C\r
+compilers will recognise that a left shift is all that is required in the\r
+assembler code to carry out the multiplication rapidly. When multiplying\r
+by other values it is often faster to do a multiple addition instead, so;\r
+\r
+\r
+     'x * 3' becomes 'x + x + x'\r
+\r
+Don't try this with variable multipliers in a loop because it becomes\r
+very slow! But, where the multiplier is a constant it can be faster.\r
+(Sometimes!) Another way to speed up multiplication and division is with\r
+the shift commands, << and >>.\r
+\r
+The instruction x /= 2 can equally well be written x >>= 1, shift the\r
+bits of x right one place. Many compilers actually convert integer\r
+divisions by 2 into a shift right instruction. You can use the shifts for\r
+multiplying and dividing by 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024 &c.\r
+If you have difficulty understanding the shift commands consider the\r
+binary form of a number;\r
+\r
+\r
+     01001101  equal to   77\r
+\r
+shifted right one place it becomes;\r
+\r
+     00100110  equal to   38\r
+\r
+Try to use integers rather than floating point numbers where ever\r
+possible.  Sometimes you can use integers where you didn't think you\r
+could! For example, to convert a fraction to a decimal one would normally\r
+say;\r
+\r
+     percentage = x / y * 100\r
+\r
+This requires floating point variables. However, it can also be written\r
+as;\r
+\r
+     z = x * 100;\r
+     percentage = z / y\r
+\r
+Which works fine with integers, so long as you don't mind the percentage\r
+being truncated. eg;\r
+\r
+     5 / 7 * 100 is equal to 71.43 with floating point\r
+\r
+but with integers;\r
+\r
+     5 * 100 / 7 is equal to 71\r
+\r
+(Assuming left to right expression evaluation. You may need to force the\r
+multiplication to be done first as with `z = x * 100').\r
+\r
+Here is a test program using this idea;\r
+\r
+     float funca(double x, double y)\r
+     {\r
+          return(x / y * 100);\r
+     }\r
+     \r
+     int funcb(int x,int y)\r
+     {\r
+          return(x * 100 / y);\r
+     }\r
+     \r
+     void main()\r
+     {\r
+          int n;\r
+          double x;\r
+          int y;\r
+     \r
+          for(n = 0; n < 5000; n++)\r
+          {\r
+               x = funca(5,7);\r
+               y = funcb(5,7);\r
+          }\r
+     }\r
+     \r
+And here is the results of the test program fed through a profiler;\r
+\r
+funca            1.9169 sec  96%\r
+|**********************************************\r
+funcb            0.0753 sec   3%  |*\r
+\r
+You can clearly see that the floating point function is 25 times slower\r
+than the integer equivalent!\r
+\r
+NB: Although it is normal practice for expressions to be evaluated left\r
+to right, the ANSI standard on C does not specify an order of preference\r
+for expression evaluation, and as such you should check your compiler\r
+manual.\r
+\r
+Another way of increasing speed is to use pointers rather than array\r
+indexing. When you access an array through an index, for example with;\r
+\r
+     x = data[i];\r
+\r
+the compiler has to calculate the offset of data[i] from the beginning of\r
+the array. A slow process. Using pointers can often improve things as the\r
+following two bubble sorts, one with array indexing and one with pointers\r
+illustrates;\r
+\r
+     void BUBBLE()\r
+     {\r
+          /* Bubble sort using array indexing */\r
+     \r
+          int a;\r
+          int b;\r
+          int temp;\r
+     \r
+          for(a = lastone; a >= 0; a--)\r
+          {\r
+               for(b = 0; b < a; b++)\r
+               {\r
+                    if(data[b] > data[b + 1])\r
+                    {\r
+                         temp = data[b];\r
+                         data[b] = data[b + 1];\r
+                         data[b + 1] = temp;\r
+                    }\r
+               }\r
+          }\r
+     }\r
+     \r
+     void PTRBUBBLE()\r
+     {\r
+          /* Bubble sort using pointers */\r
+     \r
+          int temp;\r
+          int *ptr;\r
+          int *ptr2;\r
+     \r
+          for(ptr = &data[lastone]; ptr >= data; ptr--)\r
+          {\r
+               for(ptr2 = data; ptr2 < ptr; ptr2++)\r
+               {\r
+                    if(*ptr2 > *(ptr2 + 1))\r
+                    {\r
+                         temp = *ptr2;\r
+                         *ptr2 = *(ptr2 + 1);\r
+                         *(ptr2 + 1) = temp;\r
+                    }\r
+               }\r
+          }\r
+     }\r
+     \r
+Here are the profiler results for the two versions of the same bubble\r
+sort operating on the same 1000 item, randomly sorted list;\r
+\r
+BUBBLE       3.1307 sec  59% |******************************************\r
+PTRBUBBLE    2.1686 sec  40% |***************************\r
+\r
+\r
+Here is another example of how to initialise an array using first the\r
+common indexing approach, and secondly the pointer approach;\r
+\r
+     /* Index array initialisation */\r
+     int n;\r
+     \r
+     for(n = 0; n < 1000; n++)\r
+          data[n] = random(1000);\r
+     \r
+     \r
+     /* Pointer array initialisation */\r
+     int *n;\r
+     \r
+     for(n = data; n < &data[1000]; n++)\r
+          *n = random(1000);\r
+     \r
+\r
+Needless to say, the pointer approach is faster than the index. The\r
+pointer approach is only really of benefit when an array is going to be\r
+traversed, as in the above examples. In the case of say a binary search\r
+where a different and non-adjacent element is going to be tested each\r
+pass then the pointer approach is no better than using array indexing.\r
+\r
+The exception to this rule of using pointers rather than indexed access,\r
+comes with pointer to pointers. Say your program has declared a table of\r
+static data, such as:\r
+\r
+static char *colours[] = { "Black", "Blue", "Green", "Yellow", "Red",\r
+"White" };\r
+\r
+It is faster to access the table with colours[n] than it is with a\r
+pointer, since each element in the table colours[], is a pointer. If you\r
+need to scan a string table for a value you can use this very fast\r
+approach instead;\r
+\r
+First the table is changed into a single string, with some delimiter\r
+between the elements.\r
+\r
+     static char *colours = "Black/Blue/Green/Yellow/Red/White";\r
+\r
+Then to confirm that a value is held in the table you can use strstr();\r
+\r
+     result = strstr(colours,"Cyan");\r
+\r
+Using in-line assembler code can provide the greatest speed increase.\r
+Care must be taken however not to interfere with the compiled C code. It\r
+is usually safe to write a complete function with in-line assembler code,\r
+but mixing in-line assembler with C code can be hazardous. As a rule of\r
+thumb, get your program working without assembler code, and then if you\r
+want to use in-line assembler, convert small portions of the code at a\r
+time, testing the program at each stage. Video I/O is a very slow process\r
+with C, and usually benefits from in-line assembler, and we have used\r
+this principle quite widely in the example programs which follow later.\r
+\r
+                                    \r
+                               PC GRAPHICS\r
+\r
+When programming graphics you should bear in mind that they are a machine\r
+dependant subject. Code to produce graphics on an IBM PC will not port to\r
+an Amiga, or VAX or any other type of computer.\r
+\r
+\r
+\r
+Introduction To PC Graphics\r
+\r
+The IBM PC and compatible range of computers display information on a\r
+visual display unit (VDU). To enable the computer to send information to\r
+the VDU a component is included within the computer called a "display\r
+card". There are various display cards and VDUs which have been produced\r
+for the IBM PC range of computers; monochrome display adapter (MDA),\r
+colour graphics adapter (CGA), Hercules graphics card (HGC), Enhanced\r
+graphics adapter (EGA), video graphics array (VGA), super video graphics\r
+array (SVGA), memory controller gate array (MCGA), 8514/A and the Txas\r
+Instruments Graphics Architecture (TIGA). For simplicity, this section\r
+will concern itself only with the three more common types of display;\r
+\r
+     CGA, EGA and VGA\r
+\r
+Information about the VGA display is also relevant to the SVGA display\r
+which in simple terms can do the same and more. This section will not\r
+concern itself with monochrome displays since they are of limited use in\r
+graphics work.\r
+\r
+\r
+Display Modes\r
+\r
+When an IBM PC computer is first switched on is set to display 80 columns\r
+by 25 rows of writing. This measurement, 80 x 25 is called the\r
+"resolution". A display mode which is intended for displaying writing is\r
+called a "text" mode.  Where as a display mode which is intended for\r
+displaying pictures is called a "graphics" mode.\r
+\r
+If you look closely at the display you will see that each displayed\r
+character is comprised of dots. In reality the entire display is\r
+comprised of dots, called "pixels", which may be set to different\r
+colours. In text display modes these pixels are not very relevant,\r
+however, in graphics display modes each pixel may be selected and set by\r
+a program. The size of the pixels varies with the display resolution. In\r
+80 x 25 text mode the pixels are half the width they are in 40 x 25 text\r
+display mode.\r
+\r
+Depending upon the display card installed in the computer, there are a\r
+number of display modes which may be used;\r
+\r
+\r
+MODE   TYPE               RESOLUTION         COLOURS\r
+                                             \r
+ 0       Text             40 x 25            4 (CGA), 16 (EGA,\r
+                                             VGA) Shades of\r
+                                             grey\r
+ 1       Text             40 x 25            4 (CGA), 16 (EGA,\r
+                                             VGA)\r
+ 2       Text             80 x 25            4 (CGA), 16 (EGA,\r
+                                             VGA) Shades of\r
+                                             grey\r
+ 3       Text             80 x 25            4 (CGA), 16 (EGA,\r
+                                             VGA)\r
+ 4       Graphics         320 x 200          4\r
+ 5       Graphics         320 x 200          4 (grey on CGA\r
+                                             and EGA)\r
+ 6       Graphics         640 x 200          2\r
+ 7       Text             80 x 25            Mono (EGA, VGA)\r
+13       Graphics         320 x 200          16 (EGA, VGA)\r
+14       Graphics         640 x 200          16 (EGA, VGA)\r
+15       Graphics         640 x 350          Mono (EGA, VGA)\r
+16       Graphics         640 x 350          16 (EGA, VGA)\r
+17       Graphics         640 x 480          2 (VGA)\r
+18       Graphics         640 x 480          16 (VGA)\r
+19       Graphics         320 x 200          256 (VGA)\r
+\r
+The term resolution in graphics modes refers to the number of pixels\r
+across and down the VDU. The larger the number of pixels, the smaller\r
+each is and the sharper any displayed image appears. As you can see from\r
+the table, the VGA display can produce a higher resolution than the other\r
+display cards, resulting in sharper images being produced.\r
+\r
+\r
+The CGA display card can produce a maximum resolution of 320 x 200\r
+pixels, where as the VGA display card can produce a resolution of 640 x\r
+480 pixels.  This is why writing on a VGA VDU looks so much sharper than\r
+the writing displayed on a CGA VDU.\r
+\r
+\r
+\r
+Accessing The Display\r
+\r
+Inside the IBM PC computer is a silicon chip called the BIOS ROM, this\r
+chip contains functions which may be called by an external computer\r
+program to access the display card, which in turn passes the information\r
+on to the VDU.  The BIOS display functions are all accessed by generating\r
+interrupt 10 calls, with the number of the appropriate function stored in\r
+the assembly language AH register.\r
+\r
+A programmer interested in creating graphics displays must first be able\r
+to switch the display card to an appropriate graphics mode. This is\r
+achieved by calling the BIOS display function 0, with th number of the\r
+desired display mode from the table stored in the assembly language AL\r
+register thus the following assembly language code segment will switch\r
+the display card to CGA graphics mode 4, assuming that is that the\r
+display card is capable of this mode;\r
+\r
+          mov   ah , 00\r
+          mov   al , 04\r
+          int   10h\r
+\r
+\r
+A C function for selecting video display modes can be written;\r
+\r
+     #include <dos.h>\r
+     \r
+     void setmode(unsigned char mode)\r
+     {\r
+          /* Sets the video display mode */\r
+     \r
+          union REGS inregs outreg;\r
+     \r
+          inreg.h.ah = 0;\r
+          inreg.h.al = mode;\r
+          int86(0x10,&inreg,&outregs);\r
+     }\r
+     \r
+Any graphics are created by setting different pixels to different\r
+colours, this is termed "plotting", and is achieved by calling BIOS\r
+display function 12 with the pixel's horizontal coordinate in the\r
+assembly language CX register and it's vertical coordinate in the\r
+assembly language DX register and the required colour in the assembly\r
+language AL register thus;\r
+\r
+\r
+          mov   ah, 12\r
+          mov   al, colour\r
+          mov   bh, 0\r
+          mov   cx, x_coord\r
+          mov   dx, y_coord\r
+          int   10h\r
+\r
+The corresponding C function is;\r
+\r
+     #include <dos.h>\r
+     \r
+     void plot(int x_coord, int y_coord, unsigned char colour)\r
+     {\r
+          /* Sets the colour of a pixel */\r
+     \r
+          union REGS regs;\r
+     \r
+          regs.h.ah = 12;\r
+          regs.h.al = colour;\r
+          regs.h.bh = 0;\r
+          regs.x.cx = x_coord;\r
+          regs.x.dx = y_coord;\r
+          int86(0x10,&regs,&regs);\r
+     }\r
+\r
+The inverse function of plot is to read the existing colour setting of a\r
+pixel. This is done by calling BIOS ROM display function 13, again with\r
+the pixel's horizontal coordinate in the assembly language CX register\r
+and it's vertical coordinate in the assembly language DX register. This\r
+function then returns the pixel's colour in the assembly language AL\r
+register;\r
+\r
+\r
+     #include <dos.h>\r
+     \r
+     unsigned char get_pixel(int x_coord, int y_coord)\r
+     {\r
+          /* Reads the colour of a pixel */\r
+     \r
+          union REGS inreg, outreg;\r
+     \r
+          inreg.h.ah = 13;\r
+          inreg.h.bh = 0;\r
+          inreg.x.cx = x_coord;\r
+          inreg.x.dx = y_coord;\r
+          int86(0x10,&inreg,&outreg);\r
+          return(outreg.h.al);\r
+     }\r
+\r
+\r
+Colour And The CGA\r
+\r
+The CGA display card can display a maximum of 4 colours simultaneously at\r
+any time. However, the display card can generate a total of 8 colours.\r
+There are two sets of colours, called "palettes". The first palette\r
+contains the colours;\r
+\r
+     background, cyan, magenta and white.\r
+\r
+the second palette contains the colours;\r
+\r
+       background, green, red and yellow.\r
+\r
+Colour 0 is always the same as the background colour.\r
+\r
+The pixels displayed on the VDU take their colours from the currently\r
+active palette, and are continuously being refreshed. So, if the active\r
+palette changes, so to do the colours of the displayed pixels on the VDU.\r
+\r
+Selection of the active CGA palette is achieved by calling the BIOS\r
+display function 11 with the number of the desired palette (either 0 or\r
+1) in the assembly language BH register;\r
+\r
+\r
+          mov   ah, 11\r
+          mov   bh, palette\r
+          int   10h\r
+\r
+The C function for selecting the CGA palette is;\r
+\r
+     void palette(unsigned char palette)\r
+     {\r
+          union REGS inreg, outreg;\r
+     \r
+          inreg.h.ah = 11;\r
+          inreg.h.bh = palette;\r
+          int86(0x10,&inreg,&outreg);\r
+     }\r
+     \r
+The background colour may be selected independantly from any of the eight\r
+available colours by calling the same BIOS display function with a value\r
+of 0 stored in the assembly language BH register and the desired\r
+background colour in the assembly language BL register;\r
+\r
+\r
+     mov   ah, 11\r
+     mov   bh, 0\r
+     mov   bl, colour\r
+     int   10h\r
+\r
+In C this function can be written;\r
+\r
+     void background(unsigned char colour)\r
+     {\r
+          union REGS inreg, outreg;\r
+     \r
+          inreg.h.ah = 11;\r
+          inreg.h.bh = 0;\r
+          inreg.h.bl = colour;\r
+          int86(0x10,&inreg,&outreg);\r
+     }\r
+     \r
+The background colours available are;\r
+\r
+     0       Black\r
+     1       Blue\r
+     2       Green\r
+     3       Cyan\r
+     4       Red\r
+     5       Magenta\r
+     6       Yellow\r
+     7       White\r
+\r
+\r
+Colour And The EGA\r
+\r
+The EGA display card can display a maximum of 16 colours simultaneously\r
+at any time. The colour of all pixels is continuously refreshed by the\r
+display card by reading the colour from the EGA palette. Unlike the CGA\r
+display card, the EGA display card allows you to redefine any or all of\r
+the colours in the palette. Unfortunately only the first 8 colours may be\r
+loaded into other palette colours.\r
+\r
+The colours are;\r
+\r
+     0    Black\r
+     1    Blue\r
+     2    Green\r
+     3    Cyan\r
+     4    Red\r
+     5    Magenta\r
+     6    Brown\r
+     7    Light grey\r
+     8    Dark grey\r
+     9    Light blue\r
+     10        Light green\r
+     11   Light cyan\r
+     12        Light red\r
+     13        Light magenta\r
+     14        Yellow\r
+     15        White\r
+\r
+Changing a palette colour is achieved by calling the BIOS display\r
+function 16 with a value of 0 in the assembly language AL register, the\r
+colour value (0 to 7) in the assembly language BH register and the number\r
+of the palette colour (0 to 15) in the assembly language BL register\r
+thus;\r
+\r
+\r
+     mov   ah,16\r
+     mov   al,0\r
+     mov   bl,palette\r
+     mov   bh,colour\r
+     int   10h\r
+\r
+In C this function may be written;\r
+\r
+     void ega_palette(unsigned char colour, unsigned char palette)\r
+     {\r
+          union REGS inreg,outreg;\r
+     \r
+          inreg.h.ah = 16;\r
+          inreg.h.al = 0;\r
+          inreg.h.bl = palette;\r
+          inreg.h.bh = colour;\r
+     \r
+          int86(0x10,&inreg,&outreg);\r
+     }\r
+\r
+\r
+\r
+Colour And The VGA\r
+\r
+The VGA display card can display a maximum of 256 colours on the VDU at\r
+any one time, these colours are defined by information held in 256\r
+special registers called "DAC" registers. As with the CGA and EGA\r
+displays, the colour of displayed pixels is continuously being refreshed,\r
+and as such any change to a DAC register is immediately visible. Each DAC\r
+register has three component data parts which record the amount of green,\r
+blue and red colours which make up the displayed colour. Each of these\r
+seperate data components can hold a value between 0 and 63 giving the VGA\r
+display card the ability to display 262,144 colours! Although only a\r
+small subset of them can be displayed at any one time.\r
+\r
+Setting the value of a DAC register is achieved by calling the BIOS\r
+display function 16 with a value of 16 stored in the assembly language AL\r
+register, the green value stored in the assembly language CH register,\r
+the blue value stored in the assembly language CL register and the red\r
+value stored in the assembly language DH register and the number of the\r
+DAC register to be set stored in the assembly language BX register;\r
+\r
+\r
+          mov   ah,16\r
+          mov   al,16\r
+          mov   ch,green\r
+          mov   cl,blue\r
+          mov   dh,red\r
+          mov   bx,dac\r
+          int   10h\r
+\r
+\r
+The C function to set a DAC register looks lik this;\r
+\r
+     void set_dac(int dac, unsigned char green, unsigned char blue,\r
+     unsigned char red)\r
+     {\r
+          union REGS regs;\r
+     \r
+          regs.h.ah = 16;\r
+          regs.h.al = 16;\r
+          regs.x.bx = dac;\r
+          regs.h.ch = green;\r
+          regs.h.cl = blue;\r
+          regs.h.dh = red;\r
+          int86(0x10,&regs,&regs);\r
+     }\r
+     \r
+\r
+\r
+Displaying Text\r
+\r
+The BIOS ROM provides three functions for displaying a single character.\r
+The first function to consider is the one used extensively by DOS for\r
+displaying messages, this is function 14 called "write text in teletype\r
+mode". This function interprets some control characters; bell (ascii 7),\r
+backspace (ascii 8), carriage return (ascii 10) and line feed (ascii 13)\r
+but all other ascii codes are displayed, and the current cursor position\r
+updated accordingly, moving down a row when a character is displayed in\r
+the far right column. To call this function the assembly language\r
+register AL holds the ascii code of the character to be displayed and\r
+assembly language register BL holds the foreground colour for the\r
+character to be displayed in if a graphics mode is active;\r
+\r
+\r
+          mov   ah,14\r
+          mov   al,character\r
+          mov   bh,0\r
+          mov   bl,foreground\r
+          int   10h\r
+\r
+\r
+A C function for accessing the write text in teletype mode may be written\r
+like this;\r
+\r
+     #include <dos.h>\r
+     \r
+     void teletype(unsigned char character, unsigned char foreground)\r
+     {\r
+          union REGS inreg, outreg;\r
+     \r
+          inreg.h.ah = 14;\r
+          inreg.h.al = character;\r
+          inreg.h.bh = 0;\r
+          inreg.h.bl = foreground;\r
+          int86(0x10,&inrg,&outreg);\r
+     }\r
+     \r
+The second BIOS ROM display function for displaying a character allows\r
+the foreground and background colours of the displayed character to be\r
+defined. It also allows multiple copies of the character to be displayed\r
+one after another automatically displaying subsequent characters at the\r
+next display position, although the current cursor position is not\r
+changed by this function.\r
+\r
+This function is called "write character and attribute", and is BIOS ROM\r
+display function number 9. It is called with the ascii code of the\r
+character to be displayed in the assembly language AL register, the\r
+display page in assembly language register BH, the foreground colour in\r
+the first four bits of the assembly language register BL and the\r
+background colour in the last four bits of the assembly language register\r
+BL, the number of times the character is to be displayed is stored in the\r
+assembly language CX register thus;\r
+\r
+\r
+          mov   ah,9\r
+          mov   al,character\r
+          mov   bh,0\r
+          mov   bl,foreground + 16 * background\r
+          mov   cx,number\r
+          int   10h\r
+\r
+And in C;\r
+\r
+     #include <dos.h>\r
+     \r
+     void char_attrib(unsigned char character, unsigned char foreground,\r
+     unsigned char background, int number)\r
+     {\r
+          union REGS inreg,outreg;\r
+     \r
+          inreg.h.ah = 9;\r
+          inreg.h.al = character;\r
+          inreg.h.bh = 0;\r
+          inreg.h.bl = (background << 4) + foreground;\r
+          inreg.x.cx = number;\r
+          int86(0x10,&inreg,&outreg);\r
+     }\r
+\r
+The last BIOS ROM display function for displaying a character retains the\r
+foreground and background colours of the display position.\r
+\r
+This function is called "write character", and is BIOS ROM display\r
+function number 10. It is identical to BIOS ROM display function 9 except\r
+that the colours of the displayed character are those which are prevalent\r
+at the display position, except in graphics modes when the foreground\r
+colour of the character is determined by the value in the assembly\r
+language BL register.  Its use is as follows;\r
+\r
+\r
+          mov   ah,10\r
+          mov   al,character\r
+          mov   bh,0\r
+          mov   bl,foreground   ; For graphics modes ONLY\r
+          mov   cx,number\r
+          int   10h\r
+\r
+And in C;\r
+\r
+     #include <dos.h>\r
+     \r
+     void char_attrib(unsigned char character, unsigned char foreground,\r
+     int number)\r
+     {\r
+          union REGS inreg,outreg;\r
+     \r
+          inreg.h.ah = 10;\r
+          inreg.h.al = character;\r
+          inreg.h.bh = 0;\r
+          inreg.h.bl = foreground; /* For graphics modes ONLY */\r
+          inreg.x.cx = number;\r
+          int86(0x10,&inreg,&outreg);\r
+     }\r
+     \r
+\r
+Positioning of the text cursor is provided for by the ROM BIOS display\r
+function number 2. It is called with the row number in the assembly\r
+language register DH and the column number in the assembly language\r
+register DL;\r
+\r
+          mov   ah,2\r
+          mov   bh,0\r
+          mov   dh,row\r
+          mov   dl,column\r
+          int   10h\r
+\r
+The corresponding function in C looks like this;\r
+\r
+     #include <dos.h>\r
+     \r
+     void at(unsigned char row, unsigned char column)\r
+     {\r
+          union REGS regs;\r
+     \r
+          regs.h.ah = 2;\r
+          regs.h.bh = 0;\r
+          regs.h.dh = row;\r
+          regs.h.dl = column;\r
+          int86(0x10,&regs,&regs);\r
+     }\r
+\r
+From these basic functions a more useful replacement for the C language's\r
+"printf()" function can be written which allows data to be displayed at\r
+the current cursor position, previously set by a call to "at()", with\r
+prescribed attributes;\r
+\r
+\r
+     #include <dos.h>\r
+     #include <stdarg.h>\r
+     \r
+     void at(unsigned char row, unsigned char column)\r
+     {\r
+          union REGS regs;\r
+     \r
+          regs.h.ah = 2;\r
+          regs.h.bh = 0;\r
+          regs.h.dh = row;\r
+          regs.h.dl = column;\r
+          int86(0x10,&regs,&regs);\r
+     }\r
+     \r
+     void xprintf(unsigned char foreground, unsigned char background,\r
+     char *format,...)\r
+     {\r
+          union REGS inreg,outreg;\r
+     \r
+          va_list arg_ptr;\r
+          static char output[1000];\r
+          unsigned char col;\r
+          unsigned char row;\r
+          unsigned char n;\r
+          unsigned char p;\r
+          unsigned char text;\r
+          unsigned char attr;\r
+     \r
+          /* Convert foreground and background colours into a single\r
+     attribute */\r
+          attr = (background << 4) + foreground;\r
+     \r
+          /* Copy data into a single string */\r
+          va_start(arg_ptr, format);\r
+          vsprintf(output, format, arg_ptr);\r
+     \r
+          /* Determine number of display columns */\r
+          inreg.h.ah = 15;\r
+          int86(0x10,&inreg,&outreg);\r
+          n = outreg.h.ah;\r
+     \r
+          /* Determine current cursor position */\r
+          inreg.h.ah = 3;\r
+          inreg.h.bh = 0;\r
+          int86(0x10,&inreg,&outreg);\r
+          row = outreg.h.dh;\r
+          col = outreg.h.dl;\r
+     \r
+          /* Now display data */\r
+          p = 0;\r
+          while (output[p])\r
+          {\r
+               /* Display this character */\r
+               inreg.h.bh = 0;\r
+               inreg.h.bl = attr;\r
+               inreg.x.cx = 01;\r
+               inreg.h.ah = 9;\r
+               inreg.h.al = output[p++];\r
+               int86(0x10,&inreg,&outreg);\r
+     \r
+               /* Update cursor position */\r
+               /* moving down a row if required */\r
+               col++;\r
+               if (col < (n - 1))\r
+                    at(row, col);\r
+               else\r
+               {\r
+                    col = 0;\r
+                    at(++row, col);\r
+               }\r
+          }\r
+     }\r
+     \r
+This function, "xprintf()" illustrates two more functions of the BIOS\r
+ROM. The first is the call to function 15 which returns the number of\r
+text display columns for the currently active display mode.\r
+\r
+The other function illustrated, but not yet discussed, is BIOS ROM\r
+function 3 which returns information about the cursor. The cursor's row\r
+is returned in the assembly language register DH, and it's column in the\r
+assembly language register DL.\r
+\r
+\r
+                                    \r
+                     ADVANCED GRAPHICS ON THE IBM PC\r
+\r
+This section aims to reveal more about the graphics facilities offered by\r
+the IBM PC, in particular topics which are of a more complex nature than\r
+those addressed in the previous section.\r
+\r
+\r
+\r
+Display Pages\r
+\r
+The information for display by the display card is stored in an area of\r
+memory called the "video RAM". The size of the video RAM varies from one\r
+display card to another, and the amount of video RAM required for a\r
+display varies with the selected display mode. Display modes which do not\r
+require all of the video RAM use the remaining video RAM for additional\r
+display pages.\r
+\r
+\r
+MODE               PAGES\r
+ 0                 8\r
+ 1                 8\r
+ 2                 4 (CGA) 8 (EGA, VGA)\r
+ 3                 4 (CGA) 8 (EGA, VGA)\r
+ 4                 1\r
+ 5                 1\r
+ 6                 1\r
+ 7                 8 (EGA, VGA)\r
+13                 8 (EGA, VGA)\r
+14                 4 (EGA, VGA)\r
+15                 2 (EGA, VGA)\r
+16                 2 (EGA, VGA)\r
+17                 1 (VGA)\r
+18                 1 (VGA)\r
+19                 1 (VGA)\r
+\r
+Many of the BIOS ROM display functions allow selection of the display\r
+page to be written to, regardless of which page is currently being\r
+displayed.\r
+\r
+The display card continuously updates the VDU from the information in the\r
+active display page. Changing the active display page instantaneously\r
+changes the display.\r
+\r
+This provides the graphics programmer with the means to build a display\r
+on an undisplayed page, and to then change the active display page so\r
+that the viewer does not see the display being drawn.\r
+\r
+Selection of the active display page is achieved by calling BIOS ROM\r
+display function 5 with the number of the required display page stored in\r
+the assembly language register AL;\r
+\r
+          mov   ah,5\r
+          mov   al,page\r
+          int   10h\r
+\r
+Or, from C this function becomes;\r
+\r
+     #include <dos.h>\r
+     \r
+     void set_page(unsigned char page)\r
+     {\r
+          union REGS inreg, outreg;\r
+     \r
+          inreg.h.ah = 5;\r
+          inreg.h.al = page;\r
+          int86(0x10,&inreg,&outreg);\r
+     }\r
+     \r
+The display page to which BIOS ROM display functions write is decided by\r
+the value stored in the assembly language BH register. The functions for\r
+setting a pixel's colour may be amended thus;\r
+\r
+          mov   ah, 12\r
+          mov   al, colour\r
+          mov   bh, page\r
+          mov   cx, x_coord\r
+          mov   dx, y_coord\r
+          int   10h\r
+\r
+And the corresponding C function becomes;\r
+\r
+     #include <dos.h>\r
+     \r
+     void plot(int x_coord, int y_coord, unsigned char colour, unsigned\r
+     char page)\r
+     {\r
+          /* Sets the colour of a pixel */\r
+     \r
+          union REGS inreg, outreg;\r
+     \r
+          inreg.h.ah = 12;\r
+          inreg.h.al = colour;\r
+          inreg.h.bh = page;\r
+          inreg.x.cx = x_coord;\r
+          inreg.x.dx = y_coord;\r
+          int86(0x10,&inreg,&outreg);\r
+     }\r
+\r
+The currently active display page can be determined by calling BIOS ROM\r
+display function 15. This function returns the active display page in the\r
+assembly language register BH;\r
+\r
+          mov   ah,15\r
+          int   10h\r
+                              ; BH now holds active page number\r
+\r
+                                    \r
+                         Advanced Text Routines\r
+\r
+When the IBM PC display is in a text mode a blinking cursor is displayed\r
+at the current cursor position. This cursor is formed of a rectangle\r
+which is one complete character width, but it's top and bottom pixel\r
+lines are definable within the limits of the character height by calling\r
+BIOS ROM display function 1. A CGA display has a character height of 8\r
+pixel lines, an EGA display has a character height of 14 lines and a VGA\r
+display has a character height of 16 lines.\r
+\r
+BIOS ROM function 1 is called with the top pixel line number of the\r
+desired cursor shape in assembly language register CH and the bottom\r
+pixel line number in assembly language register CL;\r
+\r
+\r
+          mov   ah,1\r
+          mov   ch,top\r
+          mov   cl,bottom\r
+          int   10h\r
+\r
+A C function to set the cursor shape may be be written thus;\r
+\r
+     #include <dos.h>\r
+     \r
+     void setcursor(unsigned char top, unsigned char bottom)\r
+     {\r
+          union REGS inreg, outreg;\r
+     \r
+          inreg.h.ch = start;\r
+          inreg.h.cl = end;\r
+          inreg.h.ah = 1;\r
+          int86(0x10, &inreg, &outreg);\r
+     }\r
+     \r
+If the top pixel line is defined as larger than the bottom line, then the\r
+cursor will appear as a pair of parallel rectangles.\r
+\r
+The cursor may be removed from view by calling BIOS ROM display function\r
+1 with a value of 32 in the assembly language CH register.\r
+\r
+The current shape of the cursor can be determined by calling BIOS ROM\r
+function 3, which returns the top pixel line number of the cursor shape\r
+in the assembly language CH register, and the bottom line number in the\r
+assembly language register CL.\r
+\r
+Two functions are provided by the BIOS ROM for scrolling of the currently\r
+active display page. These are function 6, which scrolls the display up\r
+and function 7 which scrolls the display down.\r
+\r
+Both functions accept the same parameters, these being the number of\r
+lines to scroll in the assembly language register AL, the colour\r
+attribute for the resulting blank line in the assembly language BH\r
+register, the top row of the area to be scrolled in the assembly language\r
+CH register, the left column of the area to be scrolled in the assembly\r
+language CL register, the bottom row to be scrolled in the assembly\r
+language DH register and the right most column to be scrolled in the\r
+assembly language DL register.\r
+\r
+If the number of lines being scrolled is greater than the number of lines\r
+in the specified area, then the result is to clear the specified area,\r
+filling it with spaces in the attribute specified in the assembly\r
+language BH register.\r
+\r
+\r
+Scrolling\r
+\r
+A C function to scroll the entire screen down one line can be written\r
+thus;\r
+\r
+     #include <dos.h>\r
+     \r
+     void scroll_down(unsigned char attr)\r
+     {\r
+          union REGS inreg, outreg;\r
+     \r
+          inreg.h.al = 1;\r
+          inreg.h.cl = 0;\r
+          inreg.h.ch = 0;\r
+          inreg.h.dl = 79;\r
+          inreg.h.dh = 24;    /* Assuming a 25 line display */\r
+          inreg.h.bh = attr;\r
+          inreg.h.ah = 7;\r
+          int86(0x10, &inreg, &outreg);\r
+     }\r
+\r
+\r
+Clear Screen\r
+A simple clear screen function can be written in C based upon the\r
+"scroll_down()" function simply by changing the value assigned to\r
+inreg.h.al to 0;\r
+\r
+     #include <dos.h>\r
+     \r
+     void cls(unsigned char attr)\r
+     {\r
+          union REGS inreg, outreg;\r
+     \r
+          inreg.h.al = 0;\r
+          inreg.h.cl = 0;\r
+          inreg.h.ch = 0;\r
+          inreg.h.dl = 79;\r
+          inreg.h.dh = 24;    /* Assuming a 25 line display */\r
+          inreg.h.bh = attr;\r
+          inreg.h.ah = 7;\r
+          int86(0x10, &inreg, &outreg);\r
+     }\r
+\r
+\r
+\r
+Windowing\r
+Windowing functions need to preserve the display they overwrite, and\r
+restore it when the window is removed from display. The BIOS ROM provides\r
+a display function which enables this to be done.\r
+\r
+Function 8 requires the appropriate display page number to be stored in\r
+assembly language register BH, and then when called it returns the ascii\r
+code of the character at the current cursor position of that display page\r
+in the assembly language AL register, and the display attribute of the\r
+character in the assembly language AH register.\r
+\r
+The following C functions allow an area of the display to be preserved,\r
+and later restored;\r
+\r
+     #include <dos.h>\r
+     \r
+     void at(unsigned char row, unsigned char column, unsigned char page)\r
+     {\r
+          /* Position the cursor */\r
+     \r
+          union REGS inreg,outreg;\r
+     \r
+          inreg.h.ah = 2;\r
+          inreg.h.bh = page;\r
+          inreg.h.dh = row;\r
+          inreg.h.dl = column;\r
+          int86(0x10,&inreg,&outreg);\r
+     }\r
+     \r
+     void get_win(unsigned char left, unsigned char top, unsigned char\r
+     right,unsigned char bottom, unsigned char page,        char *buffer)\r
+     {\r
+          /* Read a text window into a variable */\r
+     \r
+          union REGS inreg,outreg;\r
+     \r
+          unsigned char old_left;\r
+          unsigned char old_row;\r
+          unsigned char old_col;\r
+     \r
+          /* save current cursor position */\r
+          inreg.h.ah = 3;\r
+          inreg.h.bh = page;\r
+          int86(0x10,&inreg,&outreg);\r
+          old_row = outreg.h.dh;\r
+          old_col = outreg.h.dl;\r
+     \r
+          while(top <= bottom)\r
+          {\r
+               old_left = left;\r
+               while(left <= right)\r
+               {\r
+                    at(top,left,page);\r
+                    inreg.h.bh = page;\r
+                    inreg.h.ah = 8;\r
+                    int86(0x10,&inreg,&outreg);\r
+                    *buffer++ = outreg.h.al;\r
+                    *buffer++ = outreg.h.ah;\r
+                    left++;\r
+               }\r
+     \r
+               left = old_left;\r
+               top++;\r
+          }\r
+     \r
+          /* Restore cursor to original location */\r
+          at(old_row,old_col,page);\r
+     }\r
+     \r
+     void put_win(unsigned char left, unsigned char top, unsigned char\r
+     right,  unsigned char bottom, unsigned char            page, char\r
+     *buffer)\r
+     {\r
+          /* Display a text window from a variable */\r
+     \r
+          union REGS inreg,outreg;\r
+     \r
+          unsigned char old_left;\r
+          unsigned char chr;\r
+          unsigned char attr;\r
+          unsigned char old_row;\r
+          unsigned char old_col;\r
+     \r
+          /* save current cursor position */\r
+          inreg.h.ah = 3;\r
+          inreg.h.bh = page;\r
+          int86(0x10,&inreg,&outreg);\r
+          old_row = outreg.h.dh;\r
+          old_col = outreg.h.dl;\r
+     \r
+          while(top <= bottom)\r
+          {\r
+               old_left = left;\r
+               while(left <= right)\r
+               {\r
+                    at(top,left,page);\r
+                    chr = *buffer++;\r
+                    attr = *buffer++;\r
+                    inreg.h.bh = page;\r
+                    inreg.h.ah = 9;\r
+                    inreg.h.al = chr;\r
+                    inreg.h.bl = attr;\r
+                    inreg.x.cx = 1;\r
+                    int86(0x10,&inreg,&outreg);\r
+                    left++;\r
+               }\r
+               left = old_left;\r
+               top++;\r
+          }\r
+     \r
+          /* Restore cursor to original location */\r
+          at(old_row,old_col,page);\r
+     }\r
+                                    \r
+                   DIRECT VIDEO ACCESS WITH THE IBM PC\r
+\r
+Accessing video RAM directly is much faster than using the BIOS ROM\r
+display functions. There are problems however. Different video modes\r
+arrange their use of video RAM in different ways so a number of functions\r
+are required for plotting using direct video access, where as only one\r
+function is required if use is made of the BIOS ROM display function.\r
+\r
+The following C function will set a pixel in CGA display modes 4 and 5\r
+directly;\r
+\r
+\r
+     void dplot4(int y, int x, int colour)\r
+     {\r
+          /* Direct plotting in modes 4 & 5 ONLY! */\r
+     \r
+          union mask\r
+          {\r
+               char c[2];\r
+               int i;\r
+          }bit_mask;\r
+     \r
+          int index;\r
+          int bit_position;\r
+     \r
+          unsigned char t;\r
+          char xor;\r
+     \r
+          char far *ptr = (char far *) 0xB8000000;\r
+     \r
+          bit_mask.i = 0xFF3F;\r
+     \r
+          if ( y < 0 || y > 319 || x < 0 || x > 199)\r
+               return;\r
+     \r
+          xor = colour & 128;\r
+     \r
+          colour = colour & 127;\r
+     \r
+          bit_position = y % 4;\r
+     \r
+          colour <<= 2 * (3 - bit_position);\r
+     \r
+          bit_mask.i >>= 2 * bit_position;\r
+     \r
+          index = x * 40 + (y / 4);\r
+     \r
+          if (x % 2)\r
+               index += 8152;\r
+     \r
+     \r
+     \r
+     \r
+          if (!xor)\r
+          {\r
+               t = *(ptr + index) & bit_mask.c[0];\r
+               *(ptr + index) = t | colour;\r
+          }\r
+          else\r
+          {\r
+               t = *(ptr + index) | (char)0;\r
+               *(ptr + index) = t ^ colour;\r
+          }\r
+     }\r
+     \r
+\r
+Direct plotting in VGA mode 19 is very much simpler;\r
+\r
+     void dplot19(int x, int y, unsigned char colour)\r
+     {\r
+          /* Direct plot in mode 19 ONLY */\r
+          char far *video;\r
+     \r
+          video = MK_FP(0xA000,0);\r
+          video[x + y * 320] = colour;\r
+}\r
+                                    \r
+              ADVANCED GRAPHICS TECHNIQUES WITH THE IBM PC\r
+\r
+\r
+\r
+\r
+Increasing Colours\r
+\r
+The EGA display is limited displaying a maximum of 16 colours, however in\r
+high resolution graphics modes (such as mode 16) the small physical size\r
+of the pixels allows blending of adjacent colours to produce additional\r
+shades.\r
+\r
+If a line is drawn straight across a black background display in blue,\r
+and then a subsequent line is drawn beneath it also in blue but only\r
+plotting alternate pixels, the second line will appear in a darker shade\r
+of the same colour.\r
+\r
+The following C program illustrates this idea;\r
+\r
+\r
+     #include <dos.h>\r
+     \r
+     union REGS inreg, outreg;\r
+     \r
+     void setvideo(unsigned char mode)\r
+     {\r
+          /* Sets the video display mode */\r
+     \r
+          inreg.h.al = mode;\r
+          inreg.h.ah = 0;\r
+          int86(0x10, &inreg, &outreg);\r
+     }\r
+     \r
+     void plot(int x, int y, unsigned char colour)\r
+     {\r
+          /* Sets a pixel at the specified coordinates */\r
+     \r
+          inreg.h.al = colour;\r
+          inreg.h.bh = 0;\r
+          inreg.x.cx = x;\r
+          inreg.x.dx = y;\r
+          inreg.h.ah = 0x0C;\r
+          int86(0x10, &inreg, &outreg);\r
+     }\r
+     \r
+     void line(int a, int b, int c, int d, unsigned char colour)\r
+     {\r
+          /* Draws a straight line from (a,b) to (c,d) */\r
+     \r
+          int u;\r
+          int v;\r
+          int d1x;\r
+          int d1y;\r
+          int d2x;\r
+          int d2y;\r
+          int m;\r
+          int n;\r
+          int s;\r
+          int i;\r
+     \r
+          u = c - a;\r
+          v = d - b;\r
+          if (u == 0)\r
+          {\r
+               d1x = 0;\r
+               m = 0;\r
+          }\r
+          else\r
+          {\r
+               m = abs(u);\r
+               if (u < 0)\r
+                    d1x = -1;\r
+               else\r
+               if (u > 0)\r
+                    d1x = 1;\r
+          }\r
+          if ( v == 0)\r
+          {\r
+               d1y = 0;\r
+               n = 0;\r
+          }\r
+          else\r
+          {\r
+               n = abs(v);\r
+               if (v < 0)\r
+                    d1y = -1;\r
+               else\r
+               if (v > 0)\r
+                    d1y = 1;\r
+          }\r
+          if (m > n)\r
+          {\r
+               d2x = d1x;\r
+               d2y = 0;\r
+          }\r
+          else\r
+          {\r
+               d2x = 0;\r
+               d2y = d1y;\r
+               m = n;\r
+               n = abs(u);\r
+          }\r
+          s = m / 2;\r
+     \r
+          for (i = 0; i <= m; i++)\r
+          {\r
+               plot(a,b,colour);\r
+               s += n;\r
+               if (s >= m)\r
+               {\r
+                    s -= m;\r
+                    a += d1x;\r
+                    b += d1y;\r
+               }\r
+               else\r
+               {\r
+                    a += d2x;\r
+                    b += d2y;\r
+               }\r
+          }\r
+     }\r
+     \r
+     void dot_line(int a, int b, int c, int d, int colour)\r
+     {\r
+          /* Draws a dotted straight line from (a,b) to (c,d) */\r
+     \r
+          int u;\r
+          int v;\r
+          int d1x;\r
+          int d1y;\r
+          int d2x;\r
+          int d2y;\r
+          int m;\r
+          int n;\r
+          int s;\r
+          int i;\r
+     \r
+          u = c - a;\r
+          v = d - b;\r
+          if (u == 0)\r
+          {\r
+               d1x = 0;\r
+               m = 0;\r
+          }\r
+          else\r
+          {\r
+               m = abs(u);\r
+               if (u < 0)\r
+                    d1x = -2;\r
+               else\r
+               if (u > 0)\r
+                    d1x = 2;\r
+          }\r
+          if (v == 0)\r
+          {\r
+               d1y = 0;\r
+               n = 0;\r
+          }\r
+          else\r
+          {\r
+               n = abs(v);\r
+               if (v < 0)\r
+                    d1y = -2;\r
+               else\r
+               if (v > 0)\r
+                    d1y = 2;\r
+          }\r
+          if (m > n)\r
+          {\r
+               d2x = d1x;\r
+               d2y = 0;\r
+          }\r
+          else\r
+          {\r
+               d2x = 0;\r
+               d2y = d1y;\r
+               m = n;\r
+               n = abs(u);\r
+          }\r
+          s = m / 2;\r
+     \r
+          for (i = 0; i <= m; i+=2)\r
+          {\r
+               plot(a,b,colour);\r
+               s += n;\r
+               if (s >= m)\r
+               {\r
+                    s -= m;\r
+                    a += d1x;\r
+                    b += d1y;\r
+               }\r
+               else\r
+               {\r
+                    a += d2x;\r
+                    b += d2y;\r
+               }\r
+          }\r
+     }\r
+     \r
+     \r
+     void main(void)\r
+     {\r
+          int n;\r
+     \r
+          /* Display different colour bands */\r
+     \r
+          setvideo(16);\r
+     \r
+          for(n = 1; n < 16; n++)\r
+          {\r
+               line(0,n * 20,639,n * 20,n);\r
+               line(0,1 + n * 20,639,1 + n * 20,n);\r
+               line(0,2 + n * 20,639,2 + n * 20,n);\r
+     \r
+               dot_line(0,4 + n * 20,639,4 + n * 20,n);\r
+               dot_line(1,5 + n * 20,639,5 + n * 20,n);\r
+               dot_line(0,6 + n * 20,639,6 + n * 20,n);\r
+     \r
+               dot_line(0,8 + n * 20,639,8 + n * 20,n);\r
+               dot_line(1,9 + n * 20,639,9 + n * 20,n);\r
+               dot_line(0,10 + n * 20,639,10 + n * 20,n);\r
+     \r
+               dot_line(1,8 + n * 20,639,8 + n * 20,7);\r
+               dot_line(0,9 + n * 20,639,9 + n * 20,7);\r
+               dot_line(1,10 + n * 20,639,10 + n * 20,7);\r
+     \r
+               dot_line(1,12 + n * 20,639,12 + n * 20,n);\r
+               dot_line(0,13 + n * 20,639,13 + n * 20,n);\r
+               dot_line(1,14 + n * 20,639,14 + n * 20,n);\r
+     \r
+               dot_line(0,12 + n * 20,639,12 + n * 20,14);\r
+               dot_line(1,13 + n * 20,639,13 + n * 20,14);\r
+               dot_line(0,14 + n * 20,639,14 + n * 20,14);\r
+          }\r
+     }\r
+\r
+This technique can be put to good use for drawing three dimensional\r
+boxes;\r
+\r
+     void box3d(int xa,int ya, int xb, int yb, int col)\r
+     {\r
+          /* Draws a box for use in 3d histogram graphs etc */\r
+     \r
+          int xc;\r
+          int xd;\r
+     \r
+          int n;\r
+     \r
+          xd = (xb - xa) / 2;\r
+          xc = xa + xd;\r
+     \r
+          /* First draw the solid face */\r
+          for(n = xa; n < xb; n++)\r
+               line(n,ya,n,yb,col);\r
+     \r
+          /* Now "shaded" top and side */\r
+          for(n = 0; n < xd; n++)\r
+          {\r
+               dotline(xa + n,yb - n ,xc + n,yb - n,col);\r
+               dotline(xa + xd + n,yb - n ,xc + xd + n,yb - n,col);\r
+               dotline(xb +n ,ya - n ,xb + n,yb - n,col);\r
+          }\r
+     }\r
+     \r
+\r
+\r
+Displaying Text At Pixel Coordinates\r
+\r
+When using graphics display modes it is useful to be able to display text\r
+not at the fixed character boundaries, but at pixel coordinates. This can\r
+be achieved by implementing a print function which reads the character\r
+definition data from the BIOS ROM and uses it to plot pixels to create\r
+the shapes of the desired text. The following C function, "gr_print()"\r
+illustrates this idea using the ROM CGA (8x8) character set;\r
+\r
+     void gr_print(char *output, int x, int y, unsigned char colour)\r
+     {\r
+          unsigned char far *ptr;\r
+          unsigned char chr;\r
+          unsigned char bmask;\r
+          int i;\r
+          int k;\r
+          int oldy;\r
+          int p;\r
+          int height;\r
+     \r
+          /* The height of the characters in the font being accessed */\r
+          height = 8;\r
+     \r
+          /* Set pointer to start of font definition in the ROM */\r
+          ptr = getfontptr(3);\r
+     \r
+          oldy = y;\r
+          p = 0;\r
+     \r
+          /* Loop output string */\r
+          while(output[p])\r
+          {\r
+               /* Get first character to be displayed */\r
+               chr = output[p];\r
+     \r
+               /* Loop pixel lines in character definition */\r
+               for(i = 0; i < height; i++)\r
+               {\r
+                    /* Get pixel line definition from the ROM */\r
+                    bmask = *(ptr + (chr * height) + i);\r
+     \r
+                    /* Loop pixel columns */\r
+                    for (k = 0; k < 8; ++k, bmask <<= 1)\r
+                    {\r
+                         /* Test for a set bit */\r
+                         if(bmask & 128)\r
+                              /* Plot a pixel if appropriate */\r
+                              plot(x,y,colour);\r
+                         else\r
+                              plot(x,y,0);\r
+                         x++;\r
+                    }\r
+                    /* Down to next row and left to start of character */\r
+                    y++;\r
+                    x -= 8;\r
+               }\r
+               /* Next character to be displayed */\r
+               p++;\r
+     \r
+               /* Back to top row of the display position */\r
+               y = oldy;\r
+     \r
+               /* Right to next character display position */\r
+               x += 8;\r
+          }\r
+     }\r
+\r
+The following assembly language support function is required to retrieve\r
+the address of the ROM font by calling the BIOS ROM display function\r
+which will return the address;\r
+\r
+\r
+     ; GET FONT POINTER\r
+     ; Small memory model\r
+     ; compile with tasm /mx\r
+     \r
+     _TEXT     segment   byte public 'CODE'\r
+                     assume   cs:_TEXT,ds:NOTHING\r
+     \r
+     _getfontptr    proc near\r
+               push bp\r
+               mov   bp,sp\r
+               mov   ax,1130h\r
+               mov   bh, [bp+4]         ; Number for font to be retrieved\r
+               int   10h\r
+               mov   dx,es\r
+               mov   ax,bp\r
+               pop   bp\r
+               ret\r
+     _getfontptr    endp\r
+     \r
+     _TEXT     ends\r
+     \r
+          public    _getfontptr\r
+          end\r
+     \r
+The font number supplied to "getfontptr()" can be one of;\r
+\r
+     2    ROM EGA 8 x 14 font\r
+     3    ROM CGA 8 x 8 font\r
+     6    ROM VGA 8 x 16 font\r
+\r
+A Graphics Function Library For Turbo C\r
+\r
+     /* Graphics library for 'Turbo C'  (V2.01) */\r
+     \r
+     /* (C)1992 Copyright Servile Software */\r
+     \r
+     #include <stdio.h>\r
+     #include <dos.h>\r
+     #include <stdlib.h>\r
+     #include <stdarg.h>\r
+     #include <string.h>\r
+     #include <math.h>\r
+     \r
+     /* Global variables */\r
+     static unsigned char attribute;\r
+     int _dmode;\r
+     int _lastx;\r
+     int _lasty;\r
+     \r
+     /* Maximum coordinates for graphics screen */\r
+     static int maxx;\r
+     static int maxy;\r
+     \r
+     /* Sprite structure */\r
+     struct SP\r
+     {\r
+          int x;\r
+          int y;\r
+          char data[256];\r
+          char save[256];\r
+     };\r
+     typedef struct SP SPRITE;\r
+     \r
+     \r
+     /* Sine and cosine tables for trig' operations */\r
+     \r
+     static double sintable[360] =\r
+     {0.000000000000000001,0.017452406437283512,\r
+                    0.034899496702500969,0.052335956242943835,\r
+                    0.069756473744125302,0.087155742747658166,\r
+                    0.104528463267653457,0.121869343405147462,\r
+                    0.139173100960065438,0.156434465040230869,\r
+                    0.173648177666930331,0.190808995376544804,\r
+                    0.207911690817759315,0.224951054343864976,\r
+                    0.241921895599667702,0.258819045102520739,\r
+                    0.275637355816999163,0.292371704722736769,\r
+                    0.309016994374947451,0.325568154457156755,\r
+                    0.342020143325668824,0.358367949545300379,\r
+                    0.374606593415912181,0.390731128489273882,\r
+                    0.406736643075800375,0.422618261740699608,\r
+                    0.438371146789077626,0.453990499739547027,\r
+                    0.469471562785891028,0.484809620246337225,\r
+                    0.500000000000000222,0.515038074910054489,\r
+                    0.529919264233205234,0.544639035015027417,\r
+                    0.559192903470747127,0.573576436351046381,\r
+                    0.587785252292473470,0.601815023152048600,\r
+                    0.615661475325658625,0.629320391049837835,\r
+                    0.642787609686539696,0.656059028990507720,\r
+                    0.669130606358858682,0.681998360062498921,\r
+                    0.694658370458997698,0.707106781186548017,\r
+                    0.719339800338651636,0.731353701619170904,\r
+                    0.743144825477394688,0.754709580222772458,\r
+                    0.766044443118978569,0.777145961456971346,\r
+                    0.788010753606722458,0.798635510047293384,\r
+                    0.809016994374947895,0.819152044288992243,\r
+                    0.829037572555042179,0.838670567945424494,\r
+                    0.848048096156426512,0.857167300702112778,\r
+                    0.866025403784439152,0.874619707139396296,\r
+                    0.882947592858927432,0.891006524188368343,\r
+                    0.898794046299167482,0.906307787036650381,\r
+                    0.913545457642601311,0.920504853452440819,\r
+                    0.927183854566787868,0.933580426497202187,\r
+                    0.939692620785908761,0.945518575599317179,\r
+                    0.951056516295153975,0.956304755963035880,\r
+                    0.961261695938319227,0.965925826289068645,\r
+                    0.970295726275996806,0.974370064785235579,\r
+                    0.978147600733805911,0.981627183447664198,\r
+                    0.984807753012208353,0.987688340595137992,\r
+                    0.990268068741570473,0.992546151641322205,\r
+                    0.994521895368273512,0.996194698091745656,\r
+                    0.997564050259824309,0.998629534754573944,\r
+                    0.999390827019095762,0.999847695156391270,\r
+                    1.000000000000000000,0.999847695156391159,\r
+                    0.999390827019095651,0.998629534754573833,\r
+                    0.997564050259824087,0.996194698091745323,\r
+                    0.994521895368273179,0.992546151641321761,\r
+                    0.990268068741570029,0.987688340595137437,\r
+                    0.984807753012207687,0.981627183447663532,\r
+                    0.978147600733805245,0.974370064785234802,\r
+                    0.970295726275996029,0.965925826289067757,\r
+                    0.961261695938318228,0.956304755963034880,\r
+                    0.951056516295152865,0.945518575599316069,\r
+                    0.939692620785907651,0.933580426497200966,\r
+                    0.927183854566786536,0.920504853452439486,\r
+                    0.913545457642599978,0.906307787036649048,\r
+                    0.898794046299166149,0.891006524188367122,\r
+                    0.882947592858926211,0.874619707139395186,\r
+                    0.866025403784438042,0.857167300702111778,\r
+                    0.848048096156425624,0.838670567945423717,\r
+                    0.829037572555041513,0.819152044288991688,\r
+                    0.809016994374947451,0.798635510047293051,\r
+                    0.788010753606722236,0.777145961456971346,\r
+                    0.766044443118978569,0.754709580222772680,\r
+                    0.743144825477395132,0.731353701619171459,\r
+                    0.719339800338652302,0.707106781186548794,\r
+                    0.694658370458998808,0.681998360062500142,\r
+                    0.669130606358860014,0.656059028990509274,\r
+                    0.642787609686541472,0.629320391049839833,\r
+                    0.615661475325660845,0.601815023152051043,\r
+                    0.587785252292476135,0.573576436351049268,\r
+                    0.559192903470750236,0.544639035015030637,\r
+                    0.529919264233208676,0.515038074910058152,\r
+                    0.500000000000004219,0.484809620246341444,\r
+                    0.469471562785895413,0.453990499739551634,\r
+                    0.438371146789082455,0.422618261740704715,\r
+                    0.406736643075805704,0.390731128489279489,\r
+                    0.374606593415918010,0.358367949545306430,\r
+                    0.342020143325675152,0.325568154457163306,\r
+                    0.309016994374954279,0.292371704722743819,\r
+                    0.275637355817006491,0.258819045102528289,\r
+                    0.241921895599675502,0.224951054343872997,\r
+                    0.207911690817767558,0.190808995376553270,\r
+                    0.173648177666939019,0.156434465040239751,\r
+                    0.139173100960074542,0.121869343405156802,\r
+                    0.104528463267663005,0.087155742747667922,\r
+                    0.069756473744135267,0.052335956242954007,\r
+                    0.034899496702511350,0.017452406437294093,\r
+                    0.000000000000010781,-0.017452406437272534,\r
+                    -0.034899496702489805,-0.052335956242932476,\r
+                    -0.069756473744113756,-0.087155742747646453,\r
+                    -0.104528463267641564,-0.121869343405135402,\r
+                    -0.139173100960053198,-0.156434465040218462,\r
+                    -0.173648177666917786,-0.190808995376532092,\r
+                    -0.207911690817746464,-0.224951054343851986,\r
+                    -0.241921895599654574,-0.258819045102507472,\r
+                    -0.275637355816985785,-0.292371704722723225,\r
+                    -0.309016994374933796,-0.325568154457142933,\r
+                    -0.342020143325654891,-0.358367949545286335,\r
+                    -0.374606593415898026,-0.390731128489259616,\r
+                    -0.406736643075785997,-0.422618261740685175,\r
+                    -0.438371146789063082,-0.453990499739532427,\r
+                    -0.469471562785876373,-0.484809620246322570,\r
+                    -0.499999999999985512,-0.515038074910039723,\r
+                    -0.529919264233190468,-0.544639035015012540,\r
+                    -0.559192903470732361,-0.573576436351031616,\r
+                    -0.587785252292458593,-0.601815023152033834,\r
+                    -0.615661475325643859,-0.629320391049823069,\r
+                    -0.642787609686525041,-0.656059028990493065,\r
+                    -0.669130606358844027,-0.681998360062484377,\r
+                    -0.694658370458983265,-0.707106781186533584,\r
+                    -0.719339800338637314,-0.731353701619156804,\r
+                    -0.743144825477380699,-0.754709580222758580,\r
+                    -0.766044443118964691,-0.777145961456957690,\r
+                    -0.788010753606709025,-0.798635510047280062,\r
+                    -0.809016994374934795,-0.819152044288979364,\r
+                    -0.829037572555029412,-0.838670567945412060,\r
+                    -0.848048096156414188,-0.857167300702100676,\r
+                    -0.866025403784427272,-0.874619707139384750,\r
+                    -0.882947592858916108,-0.891006524188357352,\r
+                    -0.898794046299156713,-0.906307787036639945,\r
+                    -0.913545457642591208,-0.920504853452430938,\r
+                    -0.927183854566778320,-0.933580426497192972,\r
+                    -0.939692620785899990,-0.945518575599308742,\r
+                    -0.951056516295145871,-0.956304755963028108,\r
+                    -0.961261695938311900,-0.965925826289061651,\r
+                    -0.970295726275990256,-0.974370064785229362,\r
+                    -0.978147600733800138,-0.981627183447658869,\r
+                    -0.984807753012203468,-0.987688340595133552,\r
+                    -0.990268068741566587,-0.992546151641318764,\r
+                    -0.994521895368270514,-0.996194698091743103,\r
+                    -0.997564050259822310,-0.998629534754572390,\r
+                    -0.999390827019094763,-0.999847695156390714,\r
+                    -1.000000000000000000,-0.999847695156391714,\r
+                    -0.999390827019096761,-0.998629534754575388,\r
+                    -0.997564050259826307,-0.996194698091748099,\r
+                    -0.994521895368276398,-0.992546151641325647,\r
+                    -0.990268068741574470,-0.987688340595142433,\r
+                    -0.984807753012213349,-0.981627183447669860,\r
+                    -0.978147600733812128,-0.974370064785242240,\r
+                    -0.970295726276004022,-0.965925826289076417,\r
+                    -0.961261695938327665,-0.956304755963044872,\r
+                    -0.951056516295163523,-0.945518575599327393,\r
+                    -0.939692620785919530,-0.933580426497213511,\r
+                    -0.927183854566799748,-0.920504853452453253,\r
+                    -0.913545457642614411,-0.906307787036664148,\r
+                    -0.898794046299181804,-0.891006524188383331,\r
+                    -0.882947592858942976,-0.874619707139412506,\r
+                    -0.866025403784455916,-0.857167300702130208,\r
+                    -0.848048096156444498,-0.838670567945443146,\r
+                    -0.829037572555061497,-0.819152044289012227,\r
+                    -0.809016994374968434,-0.798635510047314479,\r
+                    -0.788010753606744219,-0.777145961456993772,\r
+                    -0.766044443119001550,-0.754709580222796106,\r
+                    -0.743144825477418891,-0.731353701619195773,\r
+                    -0.719339800338677060,-0.707106781186574107,\r
+                    -0.694658370459024455,-0.681998360062526232,\r
+                    -0.669130606358886548,-0.656059028990536253,\r
+                    -0.642787609686568784,-0.629320391049867478,\r
+                    -0.615661475325688934,-0.601815023152079465,\r
+                    -0.587785252292504890,-0.573576436351078467,\r
+                    -0.559192903470779767,-0.544639035015060502,\r
+                    -0.529919264233238985,-0.515038074910088794,\r
+                    -0.500000000000035083,-0.484809620246372641,\r
+                    -0.469471562785926888,-0.453990499739583386,\r
+                    -0.438371146789114541,-0.422618261740737022,\r
+                    -0.406736643075838289,-0.390731128489312296,\r
+                    -0.374606593415951039,-0.358367949545339737,\r
+                    -0.342020143325708625,-0.325568154457197001,\r
+                    -0.309016994374988196,-0.292371704722777903,\r
+                    -0.275637355817040797,-0.258819045102562761,\r
+                    -0.241921895599710085,-0.224951054343907719,\r
+                    -0.207911690817802419,-0.190808995376588242,\r
+                    -0.173648177666974129,-0.156434465040274973,\r
+                    -0.139173100960109847,-0.121869343405192190,\r
+                    -0.104528463267698463,-0.087155742747703435,\r
+                    -0.069756473744170822,-0.052335956242989604,\r
+                    -0.034899496702546981,-0.017452406437329739\r
+          };\r
+\r
+static double costable[360] = {\r
+1.000000000000000000,0.999847695156391270,\r
+                    0.999390827019095762,0.998629534754573833,\r
+                    0.997564050259824198,0.996194698091745545,\r
+                    0.994521895368273290,0.992546151641321983,\r
+                    0.990268068741570362,0.987688340595137770,\r
+                    0.984807753012208020,0.981627183447663976,\r
+                    0.978147600733805689,0.974370064785235246,\r
+                    0.970295726275996473,0.965925826289068312,\r
+                    0.961261695938318894,0.956304755963035436,\r
+                    0.951056516295153531,0.945518575599316735,\r
+                    0.939692620785908317,0.933580426497201743,\r
+                    0.927183854566787313,0.920504853452440264,\r
+                    0.913545457642600867,0.906307787036649826,\r
+                    0.898794046299166927,0.891006524188367788,\r
+                    0.882947592858926766,0.874619707139395630,\r
+                    0.866025403784438486,0.857167300702112112,\r
+                    0.848048096156425846,0.838670567945423828,\r
+                    0.829037572555041513,0.819152044288991576,\r
+                    0.809016994374947229,0.798635510047292607,\r
+                    0.788010753606721681,0.777145961456970569,\r
+                    0.766044443118977680,0.754709580222771681,\r
+                    0.743144825477393911,0.731353701619170127,\r
+                    0.719339800338650748,0.707106781186547129,\r
+                    0.694658370458996810,0.681998360062498032,\r
+                    0.669130606358857682,0.656059028990506721,\r
+                    0.642787609686538697,0.629320391049836836,\r
+                    0.615661475325657626,0.601815023152047601,\r
+                    0.587785252292472471,0.573576436351045382,\r
+                    0.559192903470746128,0.544639035015026307,\r
+                    0.529919264233204124,0.515038074910053378,\r
+                    0.499999999999999112,0.484809620246336115,\r
+                    0.469471562785889862,0.453990499739545861,\r
+                    0.438371146789076460,0.422618261740698442,\r
+                    0.406736643075799154,0.390731128489272661,\r
+                    0.374606593415910960,0.358367949545299158,\r
+                    0.342020143325667547,0.325568154457155479,\r
+                    0.309016994374946230,0.292371704722735493,\r
+                    0.275637355816997887,0.258819045102519463,\r
+                    0.241921895599666398,0.224951054343863643,\r
+                    0.207911690817757955,0.190808995376543389,\r
+                    0.173648177666928888,0.156434465040229398,\r
+                    0.139173100960063939,0.121869343405145950,\r
+                    0.104528463267651903,0.087155742747656584,\r
+                    0.069756473744123679,0.052335956242942190,\r
+                    0.034899496702499304,0.017452406437281822,\r
+                    -0.000000000000001715,-0.017452406437285253,\r
+                    -0.034899496702502732,-0.052335956242945618,\r
+                    -0.069756473744127107,-0.087155742747659998,\r
+                    -0.104528463267655317,-0.121869343405149350,\r
+                    -0.139173100960067325,-0.156434465040232784,\r
+                    -0.173648177666932274,-0.190808995376546747,\r
+                    -0.207911690817761285,-0.224951054343866974,\r
+                    -0.241921895599669701,-0.258819045102522793,\r
+                    -0.275637355817001217,-0.292371704722738768,\r
+                    -0.309016994374949450,-0.325568154457158698,\r
+                    -0.342020143325670822,-0.358367949545302322,\r
+                    -0.374606593415914124,-0.390731128489275825,\r
+                    -0.406736643075802318,-0.422618261740701329,\r
+                    -0.438371146789079125,-0.453990499739548303,\r
+                    -0.469471562785892083,-0.484809620246338169,\r
+                    -0.500000000000000999,-0.515038074910054933,\r
+                    -0.529919264233205567,-0.544639035015027528,\r
+                    -0.559192903470747127,-0.573576436351046159,\r
+                    -0.587785252292473026,-0.601815023152048045,\r
+                    -0.615661475325657848,-0.629320391049836947,\r
+                    -0.642787609686538697,-0.656059028990506499,\r
+                    -0.669130606358857238,-0.681998360062497477,\r
+                    -0.694658370458996033,-0.707106781186546240,\r
+                    -0.719339800338649749,-0.731353701619168906,\r
+                    -0.743144825477392579,-0.754709580222770238,\r
+                    -0.766044443118976237,-0.777145961456968903,\r
+                    -0.788010753606719905,-0.798635510047290720,\r
+                    -0.809016994374945231,-0.819152044288989578,\r
+                    -0.829037572555039404,-0.838670567945421719,\r
+                    -0.848048096156423625,-0.857167300702109891,\r
+                    -0.866025403784436265,-0.874619707139393410,\r
+                    -0.882947592858924435,-0.891006524188365345,\r
+                    -0.898794046299164484,-0.906307787036647494,\r
+                    -0.913545457642598424,-0.920504853452437932,\r
+                    -0.927183854566784982,-0.933580426497199412,\r
+                    -0.939692620785906096,-0.945518575599314515,\r
+                    -0.951056516295151311,-0.956304755963033326,\r
+                    -0.961261695938316785,-0.965925826289066314,\r
+                    -0.970295726275994586,-0.974370064785233358,\r
+                    -0.978147600733803912,-0.981627183447662310,\r
+                    -0.984807753012206577,-0.987688340595136327,\r
+                    -0.990268068741569030,-0.992546151641320873,\r
+                    -0.994521895368272291,-0.996194698091744657,\r
+                    -0.997564050259823532,-0.998629534754573389,\r
+                    -0.999390827019095318,-0.999847695156391048,\r
+                    -1.000000000000000000,-0.999847695156391381,\r
+                    -0.999390827019096095,-0.998629534754574499,\r
+                    -0.997564050259825086,-0.996194698091746544,\r
+                    -0.994521895368274622,-0.992546151641323537,\r
+                    -0.990268068741572027,-0.987688340595139658,\r
+                    -0.984807753012210241,-0.981627183447666418,\r
+                    -0.978147600733808353,-0.974370064785238244,\r
+                    -0.970295726275999804,-0.965925826289071865,\r
+                    -0.961261695938322669,-0.956304755963039654,\r
+                    -0.951056516295157972,-0.945518575599321509,\r
+                    -0.939692620785913424,-0.933580426497207072,\r
+                    -0.927183854566793086,-0.920504853452446370,\r
+                    -0.913545457642607195,-0.906307787036656598,\r
+                    -0.898794046299173921,-0.891006524188375226,\r
+                    -0.882947592858934649,-0.874619707139403846,\r
+                    -0.866025403784447034,-0.857167300702120993,\r
+                    -0.848048096156435061,-0.838670567945433487,\r
+                    -0.829037572555051505,-0.819152044289001902,\r
+                    -0.809016994374957998,-0.798635510047303709,\r
+                    -0.788010753606733227,-0.777145961456982559,\r
+                    -0.766044443118990004,-0.754709580222784449,\r
+                    -0.743144825477407012,-0.731353701619183672,\r
+                    -0.719339800338664737,-0.707106781186561450,\r
+                    -0.694658370459011576,-0.681998360062513242,\r
+                    -0.669130606358873337,-0.656059028990522708,\r
+                    -0.642787609686555128,-0.629320391049853711,\r
+                    -0.615661475325674945,-0.601815023152065254,\r
+                    -0.587785252292490457,-0.573576436351063812,\r
+                    -0.559192903470765002,-0.544639035015045625,\r
+                    -0.529919264233223886,-0.515038074910073473,\r
+                    -0.500000000000019651,-0.484809620246357043,\r
+                    -0.469471562785911123,-0.453990499739567510,\r
+                    -0.438371146789098498,-0.422618261740720869,\r
+                    -0.406736643075822024,-0.390731128489295920,\r
+                    -0.374606593415934497,-0.358367949545323083,\r
+                    -0.342020143325691917,-0.325568154457180181,\r
+                    -0.309016994374971266,-0.292371704722760861,\r
+                    -0.275637355817023644,-0.258819045102545497,\r
+                    -0.241921895599692793,-0.224951054343890372,\r
+                    -0.207911690817784989,-0.190808995376570756,\r
+                    -0.173648177666956560,-0.156434465040257376,\r
+                    -0.139173100960092194,-0.121869343405174496,\r
+                    -0.104528463267680741,-0.087155742747685686,\r
+                    -0.069756473744153044,-0.052335956242971805,\r
+                    -0.034899496702529162,-0.017452406437311916,\r
+                    -0.000000000000028605,0.017452406437254715,\r
+                    0.034899496702471985,0.052335956242914670,\r
+                    0.069756473744095979,0.087155742747628689,\r
+                    0.104528463267623842,0.121869343405117708,\r
+                    0.139173100960035545,0.156434465040200865,\r
+                    0.173648177666900216,0.190808995376514606,\r
+                    0.207911690817729033,0.224951054343834611,\r
+                    0.241921895599637282,0.258819045102490264,\r
+                    0.275637355816968632,0.292371704722706127,\r
+                    0.309016994374916809,0.325568154457126058,\r
+                    0.342020143325638126,0.358367949545269682,\r
+                    0.374606593415881484,0.390731128489243240,\r
+                    0.406736643075769733,0.422618261740669021,\r
+                    0.438371146789047095,0.453990499739516551,\r
+                    0.469471562785860608,0.484809620246306972,\r
+                    0.499999999999970080,0.515038074910024402,\r
+                    0.529919264233175369,0.544639035014997663,\r
+                    0.559192903470717484,0.573576436351016961,\r
+                    0.587785252292444271,0.601815023152019624,\r
+                    0.615661475325629759,0.629320391049809191,\r
+                    0.642787609686511385,0.656059028990479520,\r
+                    0.669130606358830815,0.681998360062471387,\r
+                    0.694658370458970387,0.707106781186521038,\r
+                    0.719339800338624991,0.731353701619144592,\r
+                    0.743144825477368709,0.754709580222746812,\r
+                    0.766044443118953255,0.777145961456946477,\r
+                    0.788010753606698033,0.798635510047269292,\r
+                    0.809016994374924359,0.819152044288969150,\r
+                    0.829037572555019531,0.838670567945402290,\r
+                    0.848048096156404752,0.857167300702091572,\r
+                    0.866025403784418391,0.874619707139376090,\r
+                    0.882947592858907782,0.891006524188349247,\r
+                    0.898794046299148941,0.906307787036632395,\r
+                    0.913545457642583991,0.920504853452423943,\r
+                    0.927183854566771659,0.933580426497186644,\r
+                    0.939692620785893884,0.945518575599302968,\r
+                    0.951056516295140320,0.956304755963022890,\r
+                    0.961261695938306904,0.965925826289056988,\r
+                    0.970295726275985926,0.974370064785225365,\r
+                    0.978147600733796474,0.981627183447655538,\r
+                    0.984807753012200360,0.987688340595130776,\r
+                    0.990268068741564034,0.992546151641316543,\r
+                    0.994521895368268627,0.996194698091741548,\r
+                    0.997564050259821089,0.998629534754571502,\r
+                    0.999390827019094097,0.999847695156390381\r
+     \r
+     int dtoi(double x)\r
+     {\r
+          /* Rounds a floating point number to an integer */\r
+     \r
+          int y;\r
+     \r
+          y = x;\r
+          if (y < (x + 0.5))\r
+               y++;\r
+          return(y);\r
+     }\r
+     \r
+     int getmode(int *ncols)\r
+     {\r
+          /* Returns current video mode and number of columns in ncols */\r
+     \r
+          union REGS inreg,outreg;\r
+     \r
+          inreg.h.ah = 0x0F;\r
+          int86(0x10, &inreg, &outreg);\r
+          *ncols = outreg.h.ah;\r
+          return(outreg.h.al);\r
+     }\r
+     \r
+     void setvideo(unsigned char mode)\r
+     {\r
+          /* Sets the video display mode and clears the screen */\r
+          /* (modes above 127 on VGA monitors do not clear the screen) */\r
+     \r
+          asm mov ax , mode;\r
+          asm int 10h;\r
+     \r
+          /* Set global variables */\r
+          switch(mode)\r
+          {\r
+               case 0:\r
+               case 1:\r
+               case 2:\r
+               case 3: break;\r
+               case 4:\r
+               case 9:\r
+               case 13:\r
+               case 19:\r
+               case 5: maxx = 320;\r
+                    maxy = 200;\r
+                    break;\r
+               case 14:\r
+               case 10:\r
+               case 6: maxx = 640;\r
+                    maxy = 200;\r
+                    break;\r
+               case 7: maxx = 720;\r
+                    maxy = 400;\r
+                    break;\r
+               case 8: maxx = 160;\r
+                    maxy = 200;\r
+                    break;\r
+               case 15:\r
+               case 16: maxx = 640;\r
+                     maxy = 350;\r
+                     break;\r
+               case 17:\r
+               case 18: maxx = 640;\r
+                     maxy = 480;\r
+                     break;\r
+     \r
+          }\r
+          _dmode = mode;\r
+     }\r
+     \r
+     void getcursor(int *row, int *col)\r
+     {\r
+          /* Returns the cursor position and size for the currently\r
+     active\r
+             video display page */\r
+     \r
+          union REGS inreg,outreg;\r
+     \r
+          inreg.h.bh = 0;\r
+          inreg.h.ah = 0x03;\r
+          int86(0x10, &inreg, &outreg);\r
+          *row = outreg.h.dh;\r
+          *col = outreg.h.dl;\r
+     }\r
+     \r
+     void setcursor(char status)\r
+     {\r
+          /* Set cursor size, if status is 0x20 the cursor is not\r
+     displayed */\r
+     \r
+          asm mov ah,1\r
+          asm mov ch,status\r
+          asm mov cl,7\r
+          asm int 10h\r
+     }\r
+     \r
+     void at(int row, int col)\r
+     {\r
+          /* Position text cursor on current screen */\r
+     \r
+          asm mov bh , 0;\r
+          asm mov dh , row;\r
+          asm mov dl , col;\r
+          asm mov ah , 02h;\r
+          asm int 10h;\r
+     }\r
+     \r
+     void clsc(unsigned char attrib)\r
+     {\r
+          /* Clear display and fill with new attribute */\r
+     \r
+          asm mov ax , 073dh;\r
+          asm mov bh , attrib;\r
+          asm mov cx , 0;\r
+          asm mov dx , 3c4fh;\r
+          asm int 10h;\r
+     \r
+          /* Now move text cursor to origin (0,0) */\r
+          asm mov bh , 0;\r
+          asm mov dx , 0;\r
+          asm mov ah , 02h;\r
+          asm int 10h;\r
+     }\r
+     \r
+     void clrwindow(int x1, int y1,int x2, int y2, unsigned char attrib)\r
+     {\r
+          /* Clear a text window */\r
+     \r
+          union REGS inreg,outreg;\r
+     \r
+          inreg.h.al = (unsigned char)(y2 - y1 + 1);\r
+          inreg.h.bh = attrib;\r
+          inreg.h.cl = (unsigned char)x1;\r
+          inreg.h.ch = (unsigned char)y1;\r
+          inreg.h.dl = (unsigned char)x2;\r
+          inreg.h.dh = (unsigned char)y2;\r
+          inreg.h.ah = 0x06;\r
+          int86(0x10, &inreg, &outreg);\r
+     }\r
+     \r
+     void scrollsc(void)\r
+     {\r
+          /* Scroll the text screen */\r
+     \r
+          asm mov al,01h;\r
+          asm mov cx,0\r
+          asm mov dx,3c4fh;\r
+          asm mov bh,attribute;\r
+          asm mov ah, 06h;\r
+          asm int 10h;\r
+     }\r
+     \r
+     void winscr(unsigned char left,unsigned char top, unsigned char\r
+     right,unsigned char bottom,\r
+               unsigned char direction, unsigned char numlines)\r
+     {\r
+          /* Scroll a text window */\r
+     \r
+          asm mov al , numlines;\r
+          asm mov cl , left;\r
+          asm mov ch , top;\r
+          asm mov dl , right;\r
+          asm mov dh , bottom;\r
+          asm mov bh , attribute;\r
+          asm mov ah , direction;\r
+          asm int 10h;\r
+     }\r
+     \r
+     unsigned char attr(int foregrnd, int backgrnd)\r
+     {\r
+          /* Convert a colour pair into a single attribute */\r
+     \r
+          return((char)((backgrnd << 4) + foregrnd));\r
+     }\r
+     \r
+     void shadewin(unsigned char left, unsigned char top, unsigned char\r
+     right,   unsigned char bottom)\r
+     {\r
+          /* Shade a text window */\r
+     \r
+          int row;\r
+          int col;\r
+          int oldrow;\r
+          int oldcol;\r
+     \r
+          /* Preserve existing coords */\r
+          getcursor(&oldrow,&oldcol);\r
+     \r
+          col = right + 1;\r
+          for (row = top + 1; row <= bottom + 1; row++)\r
+          {\r
+               /* Move to row,col */\r
+               asm mov bh , 0;\r
+               asm mov dh , row;\r
+               asm mov dl , col;\r
+               asm mov ah , 02h;\r
+               asm int 10h;\r
+               /* Get character */\r
+               asm mov ah , 0Fh;\r
+               asm int 10h;\r
+               asm mov ah , 08h;\r
+               asm int 10h;\r
+     \r
+               /* Write in attribute 7 */\r
+               asm mov ah , 09h;\r
+               asm mov bl, 07h;\r
+               asm mov cx, 01h;\r
+               asm int 10h;\r
+          }\r
+          bottom++;\r
+          for (col = left + 1; col <= right + 1; col++)\r
+          {\r
+               /* Move to row,col */\r
+               asm mov bh , 0;\r
+               asm mov dh , bottom;\r
+               asm mov dl , col;\r
+               asm mov ah , 02h;\r
+               asm int 10h;\r
+     \r
+               /* Get character */\r
+               asm mov ah , 0Fh;\r
+               asm int 10h;\r
+               asm mov ah , 08h;\r
+               asm int 10h;\r
+     \r
+               /* Write in attribute 7 */\r
+               asm mov ah , 09h;\r
+               asm mov bl, 07h;\r
+               asm mov cx, 01h;\r
+               asm int 10h;\r
+          }\r
+     \r
+          /* Return to original position */\r
+          /* Move to row,col */\r
+          asm mov bh , 0;\r
+          asm mov dh , oldrow;\r
+          asm mov dl , oldcol;\r
+          asm mov ah , 02h;\r
+          asm int 10h;\r
+     }\r
+     \r
+     void bprintf(char *format, ...)\r
+     {\r
+          /* print text to graphics screen correctly */\r
+     \r
+          va_list arg_ptr;\r
+          char output[1000];\r
+          int c, r, n, p = 0;\r
+          unsigned char text;\r
+          unsigned char page;\r
+     \r
+          va_start(arg_ptr, format);\r
+          vsprintf(output, format, arg_ptr);\r
+     \r
+          if (strcmp(output,"\r\n") == 0)\r
+               fputs(output,stdout);\r
+     \r
+          else\r
+          {\r
+               asm mov ah , 0Fh;\r
+               asm int 10h;\r
+               asm mov page, bh;\r
+     \r
+               getmode(&n);\r
+               getcursor(&r,&c);\r
+     \r
+               while (output[p])\r
+               {\r
+                    text = output[p++];\r
+                    asm mov bh , page;\r
+                    asm mov bl , attribute;\r
+                    asm mov cx , 01h;\r
+                    asm mov ah , 09h;\r
+                    asm mov al , text;\r
+                    asm int 10h;\r
+     \r
+                    c++;\r
+                    if (c < (n-1))\r
+                         at( r, c);\r
+                    else\r
+                    {\r
+                         c = 0;\r
+                         at(++r,0);\r
+                    }\r
+               }\r
+          }\r
+     }\r
+     \r
+     void wrtstr(char *output)\r
+     {\r
+          /* TTY text output. The original screen attributes remain\r
+     unaffected */\r
+     \r
+          int p = 0;\r
+          unsigned char page;\r
+          unsigned char text;\r
+     \r
+          asm mov ah , 0Fh;\r
+          asm int 10h;\r
+          asm mov page, bh;\r
+     \r
+          while (output[p])\r
+          {\r
+               text = output[p++];\r
+               asm mov bh , page;\r
+               asm mov ah , 0Eh;\r
+               asm mov al , text;\r
+               asm int 10h;\r
+          }\r
+     }\r
+     \r
+     void getwin(int left, int top, int right, int bottom, char *buffer)\r
+     {\r
+          /* Read a text window into a variable */\r
+     \r
+          int oldleft;\r
+          unsigned char page;\r
+     \r
+          asm mov ah , 0Fh;\r
+          asm int 10h;\r
+          asm mov page, bh;\r
+     \r
+          while(top <= bottom)\r
+          {\r
+               oldleft = left;\r
+               while(left <= right)\r
+               {\r
+                    at(top,left);\r
+                    asm mov bh , page;\r
+                    asm mov ah , 08h;\r
+                    asm int 10h;\r
+                    *buffer++ = _AL;\r
+                    *buffer++ = _AH;\r
+                    left++;\r
+               }\r
+               left = oldleft;\r
+               top++;\r
+          }\r
+     }\r
+     \r
+     void putwin(int left, int top, int right, int bottom,char *buffer)\r
+     {\r
+          /* Display a text window from a variable */\r
+     \r
+          int oldleft;\r
+          unsigned char chr;\r
+          unsigned char attr;\r
+          unsigned char page;\r
+     \r
+          asm mov ah , 0Fh;\r
+          asm int 10h;\r
+          asm mov page, bh;\r
+     \r
+          while(top <= bottom)\r
+          {\r
+               oldleft = left;\r
+               while(left <= right)\r
+               {\r
+                    at(top,left);\r
+                    chr = *buffer++;\r
+                    attr = *buffer++;\r
+                    asm mov bh , page;\r
+                    asm mov ah , 09h;\r
+                    asm mov al, chr;\r
+                    asm mov bl, attr;\r
+                    asm mov cx,1;\r
+                    asm int 10h;\r
+                    left++;\r
+               }\r
+               left = oldleft;\r
+               top++;\r
+          }\r
+     }\r
+     \r
+     void setpalette(unsigned char palno)\r
+     {\r
+          /* Sets the video palette */\r
+     \r
+          asm mov bh,01h;\r
+          asm mov bl,palno;\r
+          asm mov ah,0Bh;\r
+          asm int 10h;\r
+     }\r
+     \r
+     void setborder(unsigned char x)\r
+     {\r
+          /* Set border colour */\r
+     \r
+          asm mov bh, x;\r
+          asm mov ax ,1001h;\r
+          asm int 10h;\r
+     }\r
+     \r
+     void setlines(unsigned char x)\r
+     {\r
+          /* Set text display number of lines */\r
+     \r
+          asm mov ah,11h;\r
+          asm mov al,x;\r
+          asm mov bl,0;\r
+          asm int 10h;\r
+     }\r
+     \r
+     void graphbackground(unsigned char colour)\r
+     {\r
+          /* Selects the background colour for a graphics mode */\r
+     \r
+          asm mov bh,0;\r
+          asm mov bl,colour;\r
+          asm mov ah, 0Bh;\r
+          asm int 10h;\r
+     }\r
+     \r
+     void plot(int x, int y, unsigned char colour)\r
+     {\r
+          /* Sets a pixel at the specified coordinates to the specified\r
+     colour.\r
+             The variables _lastx and _lasty are updated. */\r
+     \r
+          unsigned char far *video;\r
+     \r
+          _lastx = x;\r
+          _lasty = y;\r
+     \r
+          if (_dmode == 19)\r
+          {\r
+               video = MK_FP(0xA000,0);\r
+               video[x+y*320] = colour;\r
+          }\r
+          else\r
+          {\r
+               asm mov al , colour;\r
+               asm mov bh , 00;\r
+               asm mov cx , x;\r
+               asm mov dx , y;\r
+               asm mov ah , 0Ch;\r
+               asm int 10h;\r
+          }\r
+     }\r
+     \r
+     int pixset(int x, int y)\r
+     {\r
+          /* Returns the colour of the specified pixel */\r
+     \r
+          asm mov cx ,x;\r
+          asm mov dx ,y;\r
+          asm mov ah ,0Dh;\r
+          asm int 10h;\r
+          return(_AL);\r
+     }\r
+     \r
+     void move(int x, int y)\r
+     {\r
+          /* Sets the value of the variables _lastx and _lasty */\r
+     \r
+          _lastx = x;\r
+          _lasty = y;\r
+     }\r
+     \r
+     void line(int a, int b, int c, int d, int col)\r
+     {\r
+          /* Draws a straight line from (a,b) to (c,d) in colour col */\r
+     \r
+          int u;\r
+          int v;\r
+          int d1x;\r
+          int d1y;\r
+          int d2x;\r
+          int d2y;\r
+          int m;\r
+          int n;\r
+          int s;\r
+          int i;\r
+     \r
+          if (a < 0)\r
+               a = 0;\r
+          else\r
+          if (a > maxx)\r
+               a = maxx;\r
+     \r
+          if (c < 0)\r
+               c = 0;\r
+          else\r
+          if (c > maxx)\r
+               c = maxx;\r
+     \r
+          if (b < 0)\r
+               b = 0;\r
+          else\r
+          if (b > maxy)\r
+               b = maxy;\r
+     \r
+          if (d < 0)\r
+               d = 0;\r
+          else\r
+          if (d > maxy)\r
+               d = maxy;\r
+     \r
+          u = c - a;\r
+          v = d - b;\r
+     \r
+          if (u == 0)\r
+          {\r
+               d1x = 0;\r
+               m = 0;\r
+          }\r
+          else\r
+          {\r
+               m = abs(u);\r
+               if (u < 0)\r
+                    d1x = -1;\r
+               else\r
+               if (u > 0)\r
+                    d1x = 1;\r
+          }\r
+          if ( v == 0)\r
+          {\r
+               d1y = 0;\r
+               n = 0;\r
+          }\r
+          else\r
+          {\r
+               n = abs(v);\r
+               if (v < 0)\r
+                    d1y = -1;\r
+               else\r
+               if (v > 0)\r
+                    d1y = 1;\r
+          }\r
+          if (m > n)\r
+          {\r
+               d2x = d1x;\r
+               d2y = 0;\r
+          }\r
+          else\r
+          {\r
+               d2x = 0;\r
+               d2y = d1y;\r
+               m = n;\r
+               n = abs(u);\r
+          }\r
+          s = m / 2;\r
+     \r
+          for (i = 0; i <= m; i++)\r
+          {\r
+               asm mov al , col;\r
+               asm mov bh , 0;\r
+               asm mov ah ,0Ch;\r
+               asm mov cx ,a;\r
+               asm mov dx ,b;\r
+               asm int 10h;\r
+     \r
+               s += n;\r
+               if (s >= m)\r
+               {\r
+                    s -= m;\r
+                    a += d1x;\r
+                    b += d1y;\r
+               }\r
+               else\r
+               {\r
+                    a += d2x;\r
+                    b += d2y;\r
+               }\r
+          }\r
+          _lastx = a;\r
+          _lasty = b;\r
+     }\r
+     \r
+     void ellipse(int x, int y, int xrad, int yrad,double incline,int\r
+     col)\r
+     {\r
+          /* Draws an ellipse */\r
+     \r
+          int f;\r
+          float a;\r
+          float b;\r
+          float c;\r
+          float d;\r
+          int cols;\r
+          double div;\r
+     \r
+          incline = 1 / sintable[(int)incline];\r
+     \r
+          if (getmode(&cols) == 6)\r
+               div = 2.2;\r
+          else\r
+               div = 1.3;\r
+     \r
+               /* Compensate for pixel shape */\r
+     \r
+          a = x + xrad;\r
+          b = y + sintable[0] * yrad + xrad/incline / div;\r
+     \r
+          for(f = 5; f < 360; f += 5)\r
+          {\r
+               c = x + costable[f] * xrad;\r
+               d = y + sintable[f] * yrad + (costable[f] *\r
+     xrad)/incline/div;\r
+     \r
+               line(a,b,c,d,col);\r
+     \r
+               a = c;\r
+               b = d;\r
+          }\r
+          /* Ensure join */\r
+          line(a,b,x + xrad,y + sintable[0] * yrad + xrad/incline /\r
+     div,col);\r
+     }\r
+     \r
+     void polygon(int x, int y, int rad, int col, int sides, int start)\r
+     {\r
+          /* Draws a regular polygon */\r
+     \r
+          double f;\r
+          double div;\r
+          double a;\r
+          double b;\r
+          double c;\r
+          double d;\r
+          double aa;\r
+          double bb;\r
+          int cols;\r
+          double step;\r
+     \r
+          step = 360 / sides;\r
+     \r
+          if (getmode(&cols) == 6)\r
+               div = 2.2;\r
+          else\r
+               div = 1.3;\r
+          aa = a = x + costable[start] * rad;\r
+          bb = b = y + sintable[start] * rad / div;\r
+     \r
+          for(f = start + step; f < start + 360; f += step)\r
+          {\r
+               c = x + costable[(int)f] * rad;\r
+               d = y + sintable[(int)f] * rad / div;\r
+               line(a,b,c,d,col);\r
+               a = c;\r
+               b = d;\r
+          }\r
+          line(a,b,aa,bb,col);\r
+     }\r
+     \r
+     void arc(int x, int y, int rad, int start, int end,int col)\r
+     {\r
+          /* Draw an arc */\r
+     \r
+          int f;\r
+          float a;\r
+          float b;\r
+          float c;\r
+          float d;\r
+          int cols;\r
+          float div;\r
+     \r
+          if (getmode(&cols) == 6)\r
+               div = 2.2;\r
+          else\r
+               div = 1.3;\r
+          a = x + costable[start] * rad;\r
+          b = y + sintable[start] * rad / div;\r
+     \r
+          for(f = start; f <= end; f ++)\r
+          {\r
+               c = x + costable[f] * rad;\r
+               d = y + sintable[f] * rad / div;\r
+               line(a,b,c,d,col);\r
+               a = c;\r
+               b = d;\r
+          }\r
+     }\r
+     \r
+     void segm(int x, int y, int rad, int start, int end,int col)\r
+     {\r
+          /* Draw a segment of a circle */\r
+     \r
+          int f;\r
+          float a;\r
+          float b;\r
+          float c;\r
+          float d;\r
+          int cols;\r
+          double div;\r
+     \r
+          if (getmode(&cols) == 6)\r
+               div = 2.2;\r
+          else\r
+               div = 1.3;\r
+          a = x + costable[start] * rad;\r
+          b = y + sintable[start] * rad / div;\r
+     \r
+          line(x,y,a,b,col);\r
+     \r
+          for(f = start; f <= end ; f ++)\r
+          {\r
+               c = x + costable[f] * rad;\r
+               d = y + sintable[f] * rad / div;\r
+               line(a,b,c,d,col);\r
+               a = c;\r
+               b = d;\r
+          }\r
+          line(x,y,a,b,col);\r
+     }\r
+     \r
+     void box(int xa,int ya, int xb, int yb, int col)\r
+     {\r
+          /* Draws a box for use in 2d histogram graphs etc */\r
+     \r
+          line(xa,ya,xa,yb,col);\r
+          line(xa,yb,xb,yb,col);\r
+          line(xb,yb,xb,ya,col);\r
+          line(xb,ya,xa,ya,col);\r
+     }\r
+     \r
+     void tri(int xa,int ya, int xb, int yb, int xc, int yc,int col)\r
+     {\r
+          /* Draw a triangle */\r
+     \r
+          line(xa,ya,xb,yb,col);\r
+          line(xb,yb,xc,yc,col);\r
+          line(xc,yc,xa,ya,col);\r
+     }\r
+     \r
+     void fill(int x, int y, int col,int pattern)\r
+     {\r
+          /* Fill a boundered shape using a hatch pattern */\r
+     \r
+          int xa;\r
+          int ya;\r
+          int bn;\r
+          int byn;\r
+     \r
+          int hatch[10][8] = {     255,255,255,255,255,255,255,255,\r
+                         128,64,32,16,8,4,2,1,\r
+                         1,2,4,8,16,32,64,128,\r
+                         1,2,4,8,8,4,2,1,\r
+                         238,238,238,238,238,238,238,238,\r
+                         170,85,170,85,170,85,170,85,\r
+                         192,96,48,24,12,6,3,1,\r
+                         62,62,62,0,227,227,227,0,\r
+                         129,66,36,24,24,36,66,129,\r
+                         146,36,146,36,146,36,146,36\r
+                    };\r
+     \r
+          /* Patterns for fill, each integer describes a row of dots */\r
+     \r
+          xa = x;\r
+          ya = y;  /* Save Origin */\r
+     \r
+          if(pixset(x,y))\r
+               return;\r
+     \r
+          bn = 1;\r
+          byn = 0;\r
+     \r
+          do\r
+          {\r
+               if (hatch[pattern][byn] != 0)\r
+               {\r
+                    /* If blank ignore */\r
+                    do\r
+                    {\r
+                         if ((bn & hatch[pattern][byn]) == bn)\r
+                         {\r
+                              asm mov al , col;\r
+                              asm mov bh , 00;\r
+                              asm mov cx , x;\r
+                              asm mov dx , y;\r
+                              asm mov ah , 0Ch;\r
+                              asm int 10h;\r
+                         }\r
+                         x--;\r
+                         bn <<= 1;\r
+                         if (bn > 128)\r
+                              bn = 1;\r
+                    }\r
+                    while(!pixset(x,y) && (x > -1));\r
+     \r
+                    x = xa + 1;\r
+                    bn = 128;\r
+     \r
+                    do\r
+                    {\r
+                         if ((bn & hatch[pattern][byn]) == bn)\r
+                         {\r
+                              asm mov al , col;\r
+                              asm mov bh , 00;\r
+                              asm mov cx , x;\r
+                              asm mov dx , y;\r
+                              asm mov ah , 0Ch;\r
+                              asm int 10h;\r
+                         }\r
+                         x++;\r
+                         bn >>=1;\r
+                         if (bn <1)\r
+                              bn = 128;\r
+                    }\r
+                    while((!pixset(x,y)) && (x <= maxx));\r
+               }\r
+               x = xa;\r
+               y--;\r
+               bn = 1;\r
+               byn++;\r
+               if (byn > 7)\r
+                    byn = 0;\r
+     \r
+          }\r
+          while(!pixset(x,y) && ( y > -1));\r
+     \r
+          /* Now travel downwards */\r
+     \r
+          y = ya + 1;\r
+     \r
+          byn = 7;\r
+          bn = 1;\r
+          do\r
+          {\r
+               /* Travel left */\r
+               if (hatch[pattern][byn] !=0)\r
+               {\r
+                    do\r
+                    {\r
+                         if ( (bn & hatch[pattern][byn]) == bn)\r
+                         {\r
+                              asm mov al , col;\r
+                              asm mov bh , 00;\r
+                              asm mov cx , x;\r
+                              asm mov dx , y;\r
+                              asm mov ah , 0Ch;\r
+                              asm int 10h;\r
+                         }\r
+     \r
+                         x--;\r
+                         bn <<= 1;\r
+                         if (bn > 128)\r
+                              bn = 1;\r
+                    }\r
+                    while(!pixset(x,y) && (x > -1));\r
+     \r
+                    /* Back to x origin */\r
+                    x = xa + 1 ;\r
+                    bn = 128;\r
+     \r
+                    /* Travel right */\r
+                    do\r
+                    {\r
+                         if ((bn & hatch[pattern][byn]) == bn)\r
+                         {\r
+                              asm mov al , col;\r
+                              asm mov bh , 00;\r
+                              asm mov cx , x;\r
+                              asm mov dx , y;\r
+                              asm mov ah , 0Ch;\r
+                              asm int 10h;\r
+                         }\r
+                         x++;\r
+                         bn >>=1;\r
+                         if (bn <1)\r
+                              bn = 128;\r
+                    }\r
+                    while((!pixset(x,y)) && (x <= maxx));\r
+               }\r
+               x = xa;\r
+               bn = 1;\r
+               y++;\r
+               byn--;\r
+               if (byn < 0)\r
+                    byn = 7;\r
+          }\r
+          while((!pixset(x,y)) && (y <= maxy));\r
+     }\r
+     \r
+     void invert(int xa,int ya, int xb, int yb, int col)\r
+     {\r
+          /* Invert a pixel window */\r
+     \r
+          union REGS inreg,outreg;\r
+     \r
+          inreg.h.al = (unsigned char)(128 | col);\r
+          inreg.h.ah = 0x0C;\r
+          for(inreg.x.cx = (unsigned int)xa;      inreg.x.cx <= (unsigned\r
+     int)xb; inreg.x.cx++)\r
+               for(inreg.x.dx = (unsigned)ya; inreg.x.dx <= (unsigned)yb;\r
+     inreg.x.dx++)\r
+                    int86(0x10, &inreg, &outreg);\r
+     }\r
+     \r
+     void circle(int x_centre , int y_centre, int radius, int colour)\r
+     {\r
+          int x,y,delta;\r
+          int startx,endx,x1,starty,endy,y1;\r
+          int asp_ratio;\r
+     \r
+          if (_dmode == 6)\r
+               asp_ratio = 22;\r
+          else\r
+               asp_ratio = 13;\r
+     \r
+          y = radius;\r
+          delta = 3 - 2 * radius;\r
+          for(x = 0; x < y; )\r
+          {\r
+               starty = y * asp_ratio / 10;\r
+               endy = (y + 1) * asp_ratio / 10;\r
+               startx = x * asp_ratio / 10;\r
+               endx = (x + 1) * asp_ratio / 10;\r
+               for(x1 = startx; x1 < endx; ++x1)\r
+               {\r
+                    plot(x1+x_centre,y+y_centre,colour);\r
+                    plot(x1+x_centre,y_centre - y,colour);\r
+                    plot(x_centre - x1,y_centre - y,colour);\r
+                    plot(x_centre - x1,y + y_centre,colour);\r
+               }\r
+     \r
+               for(y1 = starty; y1 < endy; ++y1)\r
+               {\r
+                    plot(y1+x_centre,x+y_centre,colour);\r
+                    plot(y1+x_centre,y_centre - x,colour);\r
+                    plot(x_centre - y1,y_centre - x,colour);\r
+                    plot(x_centre - y1,x + y_centre,colour);\r
+               }\r
+     \r
+               if (delta < 0)\r
+                    delta += 4 * x + 6;\r
+               else\r
+               {\r
+                    delta += 4*(x-y)+10;\r
+                    y--;\r
+               }\r
+               x++;\r
+          }\r
+     \r
+          if(y)\r
+          {\r
+               starty = y * asp_ratio / 10;\r
+               endy = (y + 1) * asp_ratio / 10;\r
+               startx = x * asp_ratio / 10;\r
+               endx = (x + 1) * asp_ratio / 10;\r
+               for(x1 = startx; x1 < endx; ++x1)\r
+               {\r
+                    plot(x1+x_centre,y+y_centre,colour);\r
+                    plot(x1+x_centre,y_centre - y,colour);\r
+                    plot(x_centre - x1,y_centre - y,colour);\r
+                    plot(x_centre - x1,y + y_centre,colour);\r
+               }\r
+     \r
+               for(y1 = starty; y1 < endy; ++y1)\r
+               {\r
+                    plot(y1+x_centre,x+y_centre,colour);\r
+                    plot(y1+x_centre,y_centre - x,colour);\r
+                    plot(x_centre - y1,y_centre - x,colour);\r
+                    plot(x_centre - y1,x + y_centre,colour);\r
+               }\r
+          }\r
+     }\r
+     \r
+     void draw(int x, int y, int colour)\r
+     {\r
+          /* Draws a line from _lastx,_lasty to x,y */\r
+     \r
+          line(_lastx,_lasty,x,y,colour);\r
+     }\r
+     \r
+     void psprite(SPRITE *sprite,int x,int y)\r
+     {\r
+          int origx;\r
+          int origy;\r
+          int z;\r
+          int count;\r
+          int col;\r
+          int pos;\r
+          unsigned char far *video;\r
+     \r
+          if (_dmode == 19)\r
+          {\r
+               /* Super fast direct video write in mode 19 for sprites */\r
+     \r
+               video = MK_FP(0xA000,0);\r
+     \r
+               origx = x;\r
+               origy = y;\r
+     \r
+               if (sprite->x != -1)\r
+               {\r
+                    /* This sprite has been displayed before */\r
+                    /* replace background */\r
+                    /* This must be done first in case the sprite\r
+     overlaps itself */\r
+                    x = sprite->x;\r
+                    y = sprite->y;\r
+                    col = 0;\r
+                    pos = x + y * 320;\r
+                    for(count = 0; count < 256; count++)\r
+                    {\r
+                         video[pos] = sprite->save[count];\r
+                         col++;\r
+                         if (col == 16)\r
+                         {\r
+                              pos += 305;\r
+                              col = 0;\r
+                         }\r
+                         else\r
+                              pos++;\r
+                    }\r
+               }\r
+     \r
+               x = origx;\r
+               y = origy;\r
+               col = 0;\r
+     \r
+               pos = x + y * 320;\r
+     \r
+               for(count = 0; count < 256; count++)\r
+               {\r
+                    sprite->save[count] = video[pos];\r
+                    z = sprite->data[count];\r
+                    if (z != 255)\r
+                         video[pos] = z;\r
+     \r
+                    col++;\r
+                    if (col == 16)\r
+                    {\r
+                         pos += 305;\r
+                         col = 0;\r
+                    }\r
+                    else\r
+                         pos++;\r
+               }\r
+               sprite->x = origx;\r
+               sprite->y = origy;\r
+     \r
+               return;\r
+          }\r
+     \r
+          origx = x;\r
+          origy = y;\r
+     \r
+          if (sprite->x != -1)\r
+          {\r
+               /* This sprite has been displayed before */\r
+               /* replace background */\r
+               /* This must be done first in case the sprite overlaps\r
+     itself */\r
+               x = sprite->x;\r
+               y = sprite->y;\r
+               col = 0;\r
+               for(count = 0; count < 256; count++)\r
+               {\r
+                    if ((x >= 0) && (y >= 0) && (x < maxx) && (y < maxy))\r
+                    {\r
+                         z = sprite->save[count];\r
+                         asm mov al , z;\r
+                         asm mov bh , 00;\r
+                         asm mov cx , x;\r
+                         asm mov dx , y;\r
+                         asm mov ah , 0Ch;\r
+                         asm int 10h;\r
+                    }\r
+                    col++;\r
+                    if (col == 16)\r
+                    {\r
+                         y++;\r
+                         x = sprite->x;\r
+                         col = 0;\r
+                    }\r
+                    else\r
+                         x++;\r
+               }\r
+          }\r
+     \r
+          x = origx;\r
+          y = origy;\r
+          col = 0;\r
+     \r
+          for(count = 0; count < 256; count++)\r
+          {\r
+               if ((x >= 0) && (y >= 0) && (x < maxx) && (y < maxy))\r
+               {\r
+                    asm mov cx , x;\r
+                    asm mov dx , y;\r
+                    asm mov ah , 0Dh;\r
+                    asm int 10h;\r
+                    asm mov z ,al;\r
+                    sprite->save[count] = z;\r
+                    z = sprite->data[count];\r
+     \r
+                    if (z != 255)\r
+                    {\r
+                         asm mov al , z;\r
+                         asm mov bh , 0;\r
+                         asm mov cx , x;\r
+                         asm mov dx , y;\r
+                         asm mov ah , 0Ch;\r
+                         asm int 10h;\r
+                    }\r
+               }\r
+               col++;\r
+               if (col == 16)\r
+               {\r
+                    y++;\r
+                    x = origx;\r
+                    col = 0;\r
+               }\r
+               else\r
+                    x++;\r
+          }\r
+          sprite->x = origx;\r
+          sprite->y = origy;\r
+          return;\r
+     }\r
+     \r
+     \r
+\r
+Displaying A PCX File\r
+\r
+The following program is offered as a practical example of graphics with\r
+the IBM PC. It reads a file of the `PCX' format and displays the image on\r
+the screen.\r
+\r
+     \r
+     /* Read a PCX file and display image */\r
+     \r
+     #include <dos.h>\r
+     #include <io.h>\r
+     #include <fcntl.h>\r
+     \r
+     typedef struct\r
+     {\r
+          unsigned char man;\r
+          unsigned char version;\r
+          unsigned char encoding;\r
+          unsigned char bpp;\r
+          int xmin;\r
+          int ymin;\r
+          int xmax;\r
+          int ymax;\r
+          int hdpi;\r
+          int vdpi;\r
+          int colormap[24];\r
+          char reserved;\r
+          unsigned char planes;\r
+          int bpl;\r
+          int palette;\r
+          int hss;\r
+          int vsize;\r
+          char pad[54];\r
+     }\r
+     PCX_HEADER;\r
+     \r
+     PCX_HEADER header;\r
+     \r
+     int x;\r
+     int y;\r
+     \r
+     union REGS inreg,outreg;\r
+     \r
+     void setvideo(unsigned char mode)\r
+     {\r
+          /* Sets the video display mode     and clears the screen */\r
+     \r
+          inreg.h.al = mode;\r
+          inreg.h.ah = 0x00;\r
+          int86(0x10, &inreg, &outreg);\r
+     }\r
+     \r
+     void plot(int x, int y, unsigned char colour)\r
+     {\r
+     \r
+             if (x < header.xmax && y < header.ymax)\r
+             {\r
+     \r
+             /* Direct video plot in modes 16 & 18 only! */\r
+             asm mov   ax,y;\r
+             asm mov   dx,80;\r
+             asm mul   dx;\r
+             asm mov   bx,x;\r
+             asm mov   cl,bl;\r
+     \r
+             asm shr   bx,1;\r
+             asm shr   bx,1;\r
+             asm shr   bx,1;\r
+             asm add   bx,ax;\r
+     \r
+             asm and   cl,7;\r
+             asm xor   cl,7;\r
+             asm mov   ah,1;\r
+             asm shl   ah,cl;\r
+     \r
+             asm mov   dx,3ceh;\r
+             asm mov   al,8;\r
+             asm out   dx,ax;\r
+     \r
+             asm mov   ax,(02h shl 8) + 5;\r
+             asm out   dx,ax;\r
+     \r
+             asm mov   ax,0A000h;\r
+             asm mov   es,ax;\r
+     \r
+             asm mov   al,es:[bx];\r
+             asm mov   al,byte ptr colour;\r
+             asm mov   es:[bx],al;\r
+     \r
+             asm mov   ax,(0FFh shl 8 ) + 8;\r
+             asm out   dx,ax;\r
+     \r
+             asm mov   ax,(00h shl 8) + 5;\r
+             asm out   dx,ax;\r
+          }\r
+     }\r
+     \r
+     void DISPLAY(unsigned char data)\r
+     {\r
+          int n;\r
+          int bit;\r
+     \r
+          bit = 32;\r
+     \r
+          for (n = 0; n < 6; n++)\r
+          {\r
+               if (data & bit)\r
+                    plot(x,y,1);\r
+               else\r
+                    plot(x,y,0);\r
+               bit >>= 1;\r
+               x++;\r
+          }\r
+     }\r
+     \r
+     main(int argc, char *argv[])\r
+     {\r
+          int fp;\r
+          int total_bytes;\r
+          int n;\r
+          unsigned char data;\r
+          int count;\r
+          int scan;\r
+     \r
+          if (argc != 2)\r
+          {\r
+               puts("USAGE IS getpcx <filename>");\r
+               exit(0);\r
+          }\r
+     \r
+          setvideo(16);\r
+     \r
+          x = 0;\r
+          y = 0;\r
+     \r
+          fp = open(argv[1],O_RDONLY|O_BINARY);\r
+     \r
+          _read(fp,&header,128);\r
+     \r
+          total_bytes = header.planes * header.bpl;\r
+     \r
+          for(scan = 0; scan <= header.ymax; scan++)\r
+          {\r
+               x = 0;\r
+     \r
+               /* First scan line */\r
+     \r
+               for(n = 0; n < total_bytes; n++)\r
+               {\r
+                    /* Read byte */\r
+                    _read(fp,&data,1);\r
+     \r
+                    count = data & 192;\r
+     \r
+                    if (count == 192)\r
+                    {\r
+                         count = data & 63;\r
+                         n += count - 1;\r
+                         _read(fp,&data,1);\r
+                         while(count)\r
+                         {\r
+                              DISPLAY(data);\r
+                              count--;\r
+                         }\r
+                    }\r
+                    else\r
+                         DISPLAY(data);\r
+     \r
+               }\r
+               x = 0;\r
+               y++;\r
+          }\r
+     }\r
+          \r
+\r
+Drawing Circles\r
+\r
+\r
+What has drawing circles got to do with advanced C programming? Well\r
+quite a lot, it is a task which is often desired by modern programmers,\r
+and it is a task which can be attacked from a number of angles. This\r
+example illustrates some of the ideas already discussed for replacing\r
+floating point numbers with integers, and using lookup tables rather than\r
+repeat calls to maths functions.\r
+\r
+A circle may be drawn by plotting each point on its circumference. The\r
+location of any point is given by;\r
+\r
+\r
+     Xp = Xo + Sine(Angle) * Radius\r
+     Yp = Yo + Cosine(Angle) * Radius\r
+\r
+Where Xp,Yp is the point to be plotted, and Xo,Yo is the centre of the\r
+circle.\r
+\r
+Thus, the simplest way to draw a circle is to calculate Xp and Yp for\r
+each angle between 1 and 360 degrees and to plot these points. There is\r
+however one fundamental error with this. As the radius of the circle\r
+increases, so also does the length of the arc between each angular point.\r
+Thus a circle of sufficient radius will be drawn with a dotted line\r
+rather than a solid line.\r
+\r
+The problem of the distance between the angular points may be tackled in\r
+two ways;\r
+\r
+  1)The number of points to be plotted can be increased, to say every 30\r
+     minutes.\r
+  2)A straight line may be drawn between each angular point.\r
+\r
+The simplest circle drawing pseudo-code may then look like this;\r
+\r
+\r
+     FOR angle = 1 TO 360\r
+          PLOT Xo + SINE(angle) * radius, Yo + COSINE(angle) * radius\r
+     NEXT angle\r
+\r
+This code has two major disadvantages;\r
+\r
+     1) It uses REAL numbers for the sine and cosine figures\r
+     2) It makes numerous calculations of sine and cosine values\r
+\r
+Both of these disadvantages result in a very slow piece of code. Since a\r
+circle is a regular figure with two axis of symmetry, one in both the X\r
+and Y axis, one only needs to calculate the relative offsets of points in\r
+one quadrant of the circle and then these offsets may be applied to the\r
+other three quadrants to produce a faster piece of code. Faster because\r
+the slow sine and cosine calculations are only done 90 times instead of\r
+360 times;\r
+\r
+\r
+     FOR angle = 1 TO 90\r
+          Xa = SINE(angle) * radius\r
+          Ya = COSINE(angle) * radius\r
+          PLOT Xo + Xa, Yo + Ya\r
+          PLOT Xo + Xa, Yo - Ya\r
+          PLOT Xo - Xa, Yo + Ya\r
+          PLOT Xo - Xa, Yo - Ya\r
+     NEXT angle\r
+\r
+A further enhancement may be made by making use of sine and cosine lookup\r
+tables instead of calculating them. This means calculating the sine and\r
+cosine values for each required angle and storing them in a table. Then,\r
+instead of calculating the values for each angle the circle drawing code\r
+need only retrieve the values from a table;\r
+\r
+\r
+     FOR angle = 1 TO 90\r
+          Xa = SINE[angle] * radius\r
+          Ya = COSINE[angle] * radius\r
+          PLOT Xo + Xa, Yo + Ya\r
+          PLOT Xo + Xa, Yo - Ya\r
+          PLOT Xo - Xa, Yo + Ya\r
+          PLOT Xo - Xa, Yo - Ya\r
+     NEXT angle\r
+\r
+\r
+Most computer languages work in RADIANS rather than DEGREES. There being\r
+approximately 57 degrees in one radian, 2 * PI radians in one circle.\r
+This implies that to calculate sine and cosine values of sufficient\r
+points to draw a reasonable circle using radians one must again use real\r
+numbers, that is numbers which have figures following a decimal point.\r
+Real number arithmetic, also known as floating point arithmetic, is\r
+horrendously slow to calculate. Integer arithmetic on the other hand is\r
+very quick to calculate, but less precise.\r
+\r
+To use integer arithmetic in circle drawing code requires ingenuity. If\r
+one agrees to use sine and cosine lookup tables for degrees, rather than\r
+radians. Then the sine value of an angle of 1 degree is;\r
+\r
+       0.0175\r
+\r
+Which, truncated to an integer becomes zero! To overcome this the sine\r
+and cosine values held in the table should be multiplied by some factor,\r
+say 10000. Then, the integer value of the sine of an angle of 1 degree\r
+becomes;\r
+\r
+       175\r
+\r
+If the sine and cosine values have been multiplied by a factor, then when\r
+the calculation of the point's offset is carried out one must remember to\r
+divide the result by the same factor. Thus the calculation becomes;\r
+\r
+          Xa = SINE[angle] * radius / factor\r
+          Ya = COSINE[angle] * radius / factor\r
+\r
+The final obstacle to drawing circles on a computer is the relationship\r
+between the width of the display screen and its height. This ratio\r
+between width and height is known as the "aspect ratio" and varies upon\r
+video display mode. The IBM VGA 256 colour mode for example can display\r
+320 pixels across and 200 up the screen. This equates to an aspect ratio\r
+of 1:1.3. If the circle drawing code ignores the aspect ratio, then the\r
+shape displayed will often be ovalar to a greater or lesser degree due to\r
+the rectangular shape of the display pixels. Thus in order to display a\r
+true circle, the formulae to calculate each point on the circumference\r
+must be amended to calculate a slight ellipse in compensation of the\r
+distorting factor of the display.\r
+\r
+The offset formulae then become;\r
+\r
+          Xa = SINE[angle] * radius / factor\r
+          Ya = COSINE[angle] * radius / (factor * aspect ratio)\r
+\r
+The following short C program illustrates a practical circle drawing code\r
+segment, in a demonstrable  form;\r
+\r
+\r
+     /* Circles.c   A demonstration circle drawing program for the IBM PC\r
+     */\r
+     \r
+     \r
+     #include <stdlib.h>\r
+     \r
+     int sintable[91] = {0,175,349,523,698,\r
+               872,1045,1219,1392,\r
+               1564,1736,1908,2079,\r
+               2250,2419,2588,2756,\r
+               2924,3090,3256,3420,\r
+               3584,3746,3907,4067,\r
+               4226,4384,4540,4695,\r
+               4848,5000,5150,5299,\r
+               5446,5592,5736,5878,\r
+               6018,6157,6293,6428,\r
+               6561,6691,6820,6947,\r
+               7071,7193,7314,7431,\r
+               7547,7660,7771,7880,\r
+               7986,8090,8192,8290,\r
+               8387,8480,8572,8660,\r
+               8746,8829,8910,8988,\r
+               9063,9135,9205,9272,\r
+               9336,9397,9455,9511,\r
+               9563,9613,9659,9703,\r
+               9744,9781,9816,9848,\r
+               9877,9903,9925,9945,\r
+               9962,9976,9986,9994,\r
+               9998,10000\r
+     };\r
+     \r
+     int costable[91] = { 10000,9998,9994,9986,9976,\r
+               9962,9945,9925,9903,\r
+               9877,9848,9816,9781,\r
+               9744,9703,9659,9613,\r
+               9563,9511,9455,9397,\r
+               9336,9272,9205,9135,\r
+               9063,8988,8910,8829,\r
+               8746,8660,8572,8480,\r
+               8387,8290,8192,8090,\r
+               7986,7880,7771,7660,\r
+               7547,7431,7314,7193,\r
+               7071,6947,6820,6691,\r
+               6561,6428,6293,6157,\r
+               6018,5878,5736,5592,\r
+               5446,5299,5150,5000,\r
+               4848,4695,4540,4384,\r
+               4226,4067,3907,3746,\r
+               3584,3420,3256,3090,\r
+               2924,2756,2588,2419,\r
+               2250,2079,1908,1736,\r
+               1564,1392,1219,1045,\r
+               872,698,523,349,\r
+               175,0\r
+     };\r
+     \r
+     void setvideo(unsigned char mode)\r
+     {\r
+          /* Sets the video display mode for an IBM PC */\r
+     \r
+          asm mov al , mode;\r
+          asm mov ah , 00;\r
+          asm int 10h;\r
+     }\r
+     \r
+     void plot(int x, int y, unsigned char colour)\r
+     {\r
+          /* Code for IBM PC BIOS ROM */\r
+          /* Sets a pixel at the specified coordinates to a specified\r
+     colour */\r
+     \r
+          /* Return if out of range */\r
+          if (x < 0 || y < 0 || x > 320 || y > 200)\r
+               return;\r
+     \r
+          asm mov al , colour;\r
+          asm mov bh , 0;\r
+          asm mov cx , x;\r
+          asm mov dx , y;\r
+          asm mov ah, 0Ch;\r
+          asm int 10h;\r
+     }\r
+     \r
+     void Line(int a, int b, int c, int d, int col)\r
+     {\r
+          /* Draws a straight line from point a,b to point c,d in colour\r
+     col */\r
+     \r
+          int u;\r
+          int v;\r
+          int d1x;\r
+          int d1y;\r
+          int d2x;\r
+          int d2y;\r
+          int m;\r
+          int n;\r
+          double s; /* The only real number variable, but it's essential\r
+     */\r
+          int i;\r
+     \r
+          u = c - a;\r
+          v = d - b;\r
+          if (u == 0)\r
+          {\r
+               d1x = 0;\r
+               m = 0;\r
+          }\r
+          else\r
+          {\r
+               m = abs(u);\r
+               if (u < 0)\r
+                    d1x = -1;\r
+               else\r
+               if (u > 0)\r
+                    d1x = 1;\r
+          }\r
+     \r
+          if ( v == 0)\r
+          {\r
+               d1y = 0;\r
+               n = 0;\r
+          }\r
+          else\r
+          {\r
+               n = abs(v);\r
+               if (v < 0)\r
+                    d1y = -1;\r
+               else\r
+               if (v > 0)\r
+                    d1y = 1;\r
+          }\r
+          if (m > n)\r
+          {\r
+               d2x = d1x;\r
+               d2y = 0;\r
+          }\r
+          else\r
+          {\r
+               d2x = 0;\r
+               d2y = d1y;\r
+               m = n;\r
+               n = abs(u);\r
+          }\r
+          s = m / 2;\r
+     \r
+          for (i = 0; i <= m; i++)\r
+          {\r
+               plot(a,b,col);\r
+               s += n;\r
+               if (s >= m)\r
+               {\r
+                    s -= m;\r
+                    a += d1x;\r
+                    b += d1y;\r
+               }\r
+               else\r
+               {\r
+                    a += d2x;\r
+                    b += d2y;\r
+               }\r
+          }\r
+     }\r
+     \r
+     \r
+     void Circle(int x, int y, int rad, int col)\r
+     {\r
+          /* Draws a circle about origin x,y */\r
+          /* With a radius of rad */\r
+          /* The col parameter defines the colour for plotting */\r
+     \r
+          int f;\r
+          long xa;\r
+          long ya;\r
+          int a1;\r
+          int b1;\r
+          int a2;\r
+          int b2;\r
+          int a3;\r
+          int b3;\r
+          int a4;\r
+          int b4;\r
+     \r
+     \r
+          /* Calculate first point in each segment */\r
+     \r
+          a1 = x + ((long)(costable[0]) * (long)(rad) + 5000) / 10000;\r
+          b1 = y + ((long)(sintable[0]) * (long)(rad) + 5000) / 13000;\r
+     \r
+          a2 = x - ((long)(costable[0]) * (long)(rad) + 5000) / 10000;\r
+          b2 = y + ((long)(sintable[0]) * (long)(rad) + 5000) / 13000;\r
+     \r
+          a3 = x - ((long)(costable[0]) * (long)(rad) + 5000) / 10000;\r
+          b3 = y - ((long)(sintable[0]) * (long)(rad) + 5000) / 13000;\r
+     \r
+          a4 = x + ((long)(costable[0]) * (long)(rad) + 5000) / 10000;\r
+          b4 = y - ((long)(sintable[0]) * (long)(rad) + 5000) / 13000;\r
+     \r
+          /* Start loop at second point */\r
+          for(f = 1; f <= 90; f++)\r
+          {\r
+               /* Calculate offset to new point */\r
+               xa = ((long)(costable[f]) * (long)(rad) + 5000) / 10000;\r
+               ya = ((long)(sintable[f]) * (long)(rad) + 5000) / 13000;\r
+     \r
+               /* Draw a line from the previous point to the new point in\r
+                  each segment */\r
+               Line(a1,b1,x + xa, y + ya,col);\r
+               Line(a2,b2,x - xa, y + ya,col);\r
+               Line(a3,b3,x - xa, y - ya,col);\r
+               Line(a4,b4,x + xa, y - ya,col);\r
+     \r
+               /* Update the previous point in each segment */\r
+               a1 = x + xa;\r
+               b1 = y + ya;\r
+               a2 = x - xa;\r
+               b2 = y + ya;\r
+               a3 = x - xa;\r
+               b3 = y - ya;\r
+               a4 = x + xa;\r
+               b4 = y - ya;\r
+          }\r
+     }\r
+     \r
+     main()\r
+     {\r
+          int n;\r
+     \r
+          /* Select VGA 256 colour 320 x 200 video mode */\r
+          setvideo(19);\r
+     \r
+          /* Draw some circles */\r
+          for(n = 0; n < 100; n++)\r
+               Circle(160,100,n,n + 20);\r
+     }\r
+     \r
+     \r
+\r
+Vesa Mode\r
+\r
+The VESA BIOS provides a number of new, and exciting video modes not\r
+supported by the standard BIOS. These modes vary from one video card to\r
+another, but most support the following modes:\r
+\r
+Mode               Display\r
+                   \r
+0x54               Text 16 colours 132 x 43\r
+0x55               Text 16 colours 132 x 25\r
+0x58               Graphics 16 colours 800 x 600\r
+0x5C               Graphics 256 colours 800 x 600\r
+0x5D               Graphics 16 colours 1024 x 768\r
+0x5F               Graphics 256 colours 640 x 480\r
+0x60               Graphics 256 colours 1024 x 768\r
+0x64               Graphics 64k colours 640 x 480\r
+0x65               Graphics 64k colours 800 x 600\r
+0x6A               Graphics 16 colours 800 x 600\r
+0x6C               Graphics 16 colours 1280 x 1024\r
+0x70               Graphics 16m colours 320 x 200\r
+0x71               Graphics 16m colours 640 x 480\r
+\r
+These modes are in addition to the standard BIOS video modes described\r
+earlier.\r
+\r
+Setting a VESA video mode requires a call to a different BIOS function\r
+than the standard BIOS, as illustrated in the following example which\r
+enables any VESA or standard display mode to be selected from the DOS\r
+command line.\r
+\r
+     #include <dos.h>\r
+     #include <ctype.h>\r
+     \r
+     void setvideo(int mode)\r
+     {\r
+          /* Sets the video display to a VESA or normal mode and clears\r
+     the screen */\r
+     \r
+          union REGS inreg,outreg;\r
+     \r
+          inreg.h.ah = 0x4f;\r
+          inreg.h.al = 0x02;\r
+          inreg.x.bx = mode;\r
+          int86(0x10, &inreg, &outreg);\r
+     }\r
+     \r
+     main(int argc, char *argv[])\r
+     {\r
+          setvideo(atoi(argv[1]));\r
+     }\r
+\r
+\r
+Plotting pixels in a VESA mode graphics display can be acgieved with the\r
+standard BIOS plot functiona call, as illustrated here;\r
+\r
+     \r
+     void plot(int x, int y, unsigned char colour)\r
+     {\r
+          asm mov al , colour;\r
+          asm mov bh , 00;\r
+          asm mov cx , x;\r
+          asm mov dx , y;\r
+          asm mov ah , 0Ch;\r
+          asm int 10h;\r
+     }\r
+\r
+\r
+Or, in a 800 x 600 resolution mode you can use this fast direct video\r
+access plot function;\r
+\r
+     void plot( int x, int y, unsigned char pcolor)\r
+     {\r
+          /*\r
+               Fast 800 x 600 mode (0x58 or 0x102) plotting\r
+          */\r
+     \r
+          asm mov   ax,y;\r
+          asm mov   dx,800/8;\r
+          asm mul   dx;\r
+          asm mov   bx,x;\r
+          asm mov   cl,bl;\r
+     \r
+          asm shr   bx,1;\r
+          asm shr   bx,1;\r
+          asm shr   bx,1;\r
+          asm add   bx,ax;\r
+     \r
+          asm and   cl,7;\r
+          asm xor   cl,7;\r
+          asm mov   ah,1;\r
+          asm shl   ah,cl;\r
+     \r
+          asm mov   dx,03CEh;\r
+          asm mov   al,8;\r
+          asm out   dx,ax;\r
+     \r
+          asm mov   ax,(02h shl 8) + 5;\r
+          asm out   dx,ax;\r
+     \r
+          asm mov   ax,0A000h;\r
+          asm mov   es,ax;\r
+     \r
+          asm mov   al,es:[bx];\r
+          asm mov   al,byte ptr pcolor;\r
+          asm mov   es:[bx],al;\r
+     \r
+          asm mov   ax,(0FFh shl 8 ) + 8;\r
+          asm out   dx,ax;\r
+     \r
+          asm mov   ax,(00h shl 8) + 5;\r
+          asm out   dx,ax;\r
+     }\r
+\r
+There are lots more functions supported by the VESA BIOS, but this will\r
+get you going with the basic operations. Remember though that when using\r
+VESA display modes, that the direct console I/O functions in the C\r
+compiler library may not function correctly.\r
+                                    \r
+               DIRECTORY SEARCHING WITH THE IBM PC AND DOS\r
+\r
+Amongst the many functions provided by DOS for programmers, are a pair of\r
+functions; "Find first" and "Find next" which are used to search a DOS\r
+directory for a specified file name or names. The first function, "Find\r
+first" is accessed via DOS interrupt 21, function 4E. It takes an ascii\r
+string file specification, which can include wildcards, and the required\r
+attribute for files to match. Upon return the function fills the disk\r
+transfer area (DTA) with details of the located file and returns with the\r
+carry flag clear. If an error occurs, such as no matching files are\r
+located, the function returns with the carry flag set.\r
+\r
+Following a successful call to "Find first", a program can call "Find\r
+next", DOS interrupt 21, function 4F, to locate the next file matching\r
+the specifications provided by the initial call to "Find first". If this\r
+function succeeds, then the DTA is filled in with details of the next\r
+matching file, and the function returns with the carry flag clear.\r
+Otherwise a return is made with the carry flag set.\r
+\r
+Most C compilers for the IBM PC provide non standard library functions\r
+for accessing these two functions. Turbo C provides "findfirst()" and\r
+"findnext()". Making use of the supplied library functions shields the\r
+programmer from the messy task of worrying about the DTA. Microsoft C\r
+programmers should substitue findfirst() with _dos_findfirst() and\r
+findnext() with _dos_findnext().\r
+\r
+The following Turbo C example imitates the DOS directory command, in a\r
+basic form;\r
+\r
+     #include <stdio.h>\r
+     #include <dir.h>\r
+     #include <dos.h>\r
+     \r
+     void main(void)\r
+     {\r
+          /* Display directory listing of current directory */\r
+     \r
+          int done;\r
+          int day;\r
+          int month;\r
+          int year;\r
+          int hour;\r
+          int min;\r
+          char amflag;\r
+          struct ffblk ffblk;\r
+          struct fcb fcb;\r
+     \r
+          /* First display sub directory entries */\r
+          done = findfirst("*.",&ffblk,16);\r
+     \r
+          while (!done)\r
+          {\r
+               year = (ffblk.ff_fdate >> 9) + 80;\r
+               month = (ffblk.ff_fdate >> 5) & 0x0f;\r
+               day = ffblk.ff_fdate & 0x1f;\r
+               hour = (ffblk.ff_ftime >> 11);\r
+               min = (ffblk.ff_ftime >> 5) & 63;\r
+     \r
+               amflag = 'a';\r
+     \r
+               if (hour > 12)\r
+               {\r
+                    hour -= 12;\r
+                    amflag = 'p';\r
+               }\r
+     \r
+               printf("%-11.11s  <DIR>   %02d-%02d-%02d  %2d:%02d%c\n",\r
+                       ffblk.ff_name,day,month,year,hour,min,amflag);\r
+               done = findnext(&ffblk);\r
+          }\r
+     \r
+          /* Now all files except directories */\r
+          done = findfirst("*.*",&ffblk,231);\r
+     \r
+          while (!done)\r
+          {\r
+               year = (ffblk.ff_fdate >> 9) + 80;\r
+               month = (ffblk.ff_fdate >> 5) & 0x0f;\r
+               day = ffblk.ff_fdate & 0x1f;\r
+               hour = (ffblk.ff_ftime >> 11);\r
+               min = (ffblk.ff_ftime >> 5) & 63;\r
+     \r
+               amflag = 'a';\r
+     \r
+               if (hour > 12)\r
+               {\r
+                    hour -= 12;\r
+                    amflag = 'p';\r
+               }\r
+     \r
+               parsfnm(ffblk.ff_name,&fcb,1);\r
+     \r
+               printf("%-8.8s %-3.3s %8ld  %02d-%02d-%02d  %2d:%02d%c\n",\r
+                         fcb.fcb_name,fcb.fcb_ext,ffblk.ff_fsize,\r
+                         day,month,year,hour,min,amflag);\r
+               done = findnext(&ffblk);\r
+          }\r
+     }\r
+     \r
+\r
+The function "parsfnm()" is a Turbo C library command which makes use of\r
+the DOS function for parsing an ascii string containing a file name, into\r
+its component parts. These component parts are then put into a DOS file\r
+control block (fcb), from where they may be easily retrieved for\r
+displaying by printf().\r
+\r
+\r
+The DOS DTA is comprised as follows;\r
+\r
+Offset                   Length                   Contents\r
+                                                  \r
+00                       15                       Reserved\r
+15                       Byte                     Attribute of matched\r
+                                                  file\r
+16                       Word                     File time\r
+18                       Word                     File date\r
+1A                       04                       File size\r
+1E                       0D                       File name and extension\r
+                                                  as ascii string\r
+\r
+The file time word contains the time at which the file was last written\r
+to disc and is comprised as follows;\r
+\r
+Bits      Contents\r
+\r
+ 0 -  4             Seconds divided by 2\r
+ 5 - 10        Minutes\r
+11 - 15   Hours\r
+\r
+The file date word holds the date on which the file was last written to\r
+disc and is comprised of;\r
+\r
+Bits      Contents\r
+\r
+ 0 -  4             Day\r
+ 5 -  8             Month\r
+ 9 - 15        Years since 1980\r
+\r
+To extract these details from the DTA requires a little manipulation, as\r
+illustrated in the above example.\r
+\r
+The DTA attribute flag is comprised of the following bits being set or\r
+not;\r
+\r
+Bit       Attribute\r
+\r
+0         Read only\r
+1         Hidden\r
+2         System\r
+3         Volume label\r
+4         Directory\r
+5         Archive\r
+                                    \r
+                        ACCESSING EXPANDED MEMORY\r
+\r
+Memory (RAM) in an IBM PC comes in three flavours; Conventional, Expanded\r
+and Extended. Conventional memory is the 640K of RAM which the operating\r
+system DOS can access. This memory is normally used. However, it is often\r
+insufficient for todays RAM hungry systems. Expanded memory is RAM which\r
+is addressed outside of the area of conventional RAM not by DOS but by a\r
+second program called a LIM EMS driver. Access to this device driver is\r
+made through interrupt 67h.\r
+\r
+The main problem with accessing expanded memory is that no matter how\r
+much expanded memory is fitted to the computer, it can only be accessed\r
+through 16K blocks refered to as pages. So for example. If you have 2mB\r
+of expanded RAM fitted to your PC then that is comprised of 128 pages\r
+(128 * 16K = 2mB).\r
+\r
+A program can determine whether a LIM EMS driver is installed by\r
+attempting to open the file `EMMXXXX0' which is guarranteed by the LIM\r
+standard to be present as an IOCTL device when the device driver is\r
+active.\r
+\r
+The following source code illustrates some basic functions for testing\r
+for and accessing expanded memory.\r
+\r
+     /*\r
+     Various functions for using Expanded memory\r
+     */\r
+     \r
+     #include <dos.h>\r
+     #define   EMM  0x67\r
+     \r
+     char far *emmbase;\r
+     emmtest()\r
+     {\r
+          /*\r
+          Tests for the presence of expnaded memory by attempting to\r
+          open the file EMMXXXX0.\r
+          */\r
+     \r
+          union REGS regs;\r
+          struct SREGS sregs;\r
+          int error;\r
+          long handle;\r
+     \r
+          /* Attempt to open the file device EMMXXXX0 */\r
+          regs.x.ax = 0x3d00;\r
+          regs.x.dx = (int)"EMMXXXX0";\r
+          sregs.ds = _DS;\r
+          intdosx(&regs,&regs,&sregs);\r
+          handle = regs.x.ax;\r
+          error = regs.x.cflag;\r
+     \r
+          if (!error)\r
+          {\r
+               regs.h.ah = 0x3e;\r
+               regs.x.bx = handle;\r
+               intdos(&regs,&regs);\r
+          }\r
+          return error;\r
+     }\r
+     \r
+     emmok()\r
+     {\r
+          /*\r
+          Checks whether the expanded memory manager responds correctly\r
+          */\r
+     \r
+          union REGS regs;\r
+     \r
+          regs.h.ah = 0x40;\r
+          int86(EMM,&regs,&regs);\r
+     \r
+          if (regs.h.ah)\r
+               return 0;\r
+     \r
+          regs.h.ah = 0x41;\r
+          int86(EMM,&regs,&regs);\r
+     \r
+          if (regs.h.ah)\r
+               return 0;\r
+     \r
+          emmbase = MK_FP(regs.x.bx,0);\r
+          return 1;\r
+     }\r
+     \r
+     long emmavail()\r
+     {\r
+        /*\r
+        Returns the number of available (free) 16K pages of expanded\r
+     memory\r
+        or -1 if an error occurs.\r
+        */\r
+     \r
+             union REGS regs;\r
+     \r
+          regs.h.ah = 0x42;\r
+          int86(EMM,&regs,&regs);\r
+          if (!regs.h.ah)\r
+               return regs.x.bx;\r
+          return -1;\r
+     }\r
+     \r
+     long emmalloc(int n)\r
+     {\r
+          /*\r
+          Requests 'n' pages of expanded memory and returns the file\r
+     handle\r
+          assigned to the pages or -1 if there is an error\r
+          */\r
+     \r
+          union REGS regs;\r
+     \r
+          regs.h.ah = 0x43;\r
+          regs.x.bx = n;\r
+          int86(EMM,&regs,&regs);\r
+          if (regs.h.ah)\r
+               return -1;\r
+          return regs.x.dx;\r
+     }\r
+     \r
+     emmmap(long handle, int phys, int page)\r
+     {\r
+          /*\r
+          Maps a physical page from expanded memory into the page frame\r
+     in the\r
+          conventional memory 16K window so that data can be transfered\r
+     between\r
+          the expanded memory and conventional memory.\r
+          */\r
+     \r
+          union REGS regs;\r
+     \r
+          regs.h.ah = 0x44;\r
+          regs.h.al = page;\r
+          regs.x.bx = phys;\r
+          regs.x.dx = handle;\r
+          int86(EMM,&regs,&regs);\r
+          return (regs.h.ah == 0);\r
+     }\r
+     \r
+     void emmmove(int page, char *str, int n)\r
+     {\r
+          /*\r
+          Move 'n' bytes from conventional memory to the specified\r
+     expanded memory\r
+          page\r
+          */\r
+     \r
+          char far *ptr;\r
+     \r
+          ptr = emmbase + page * 16384;\r
+          while(n-- > 0)\r
+               *ptr++ = *str++;\r
+     }\r
+     \r
+     void emmget(int page, char *str, int n)\r
+     {\r
+          /*\r
+          Move 'n' bytes from the specified expanded memory page into\r
+     conventional\r
+          memory\r
+          */\r
+     \r
+          char far *ptr;\r
+     \r
+          ptr = emmbase + page * 16384;\r
+          while(n-- > 0)\r
+               *str++ = *ptr++;\r
+     }\r
+     \r
+     emmclose(long handle)\r
+     {\r
+          /*\r
+          Release control of the expanded memory pages allocated to\r
+     'handle'\r
+          */\r
+     \r
+          union REGS regs;\r
+     \r
+          regs.h.ah = 0x45;\r
+          regs.x.dx = handle;\r
+          int86(EMM,&regs,&regs);\r
+          return (regs.h.ah == 0);\r
+     }\r
+     \r
+     /*\r
+     Test function for the EMM routines\r
+     */\r
+     \r
+     void main()\r
+     {\r
+          long emmhandle;\r
+          long avail;\r
+          char teststr[80];\r
+          int i;\r
+     \r
+          if(!emmtest())\r
+          {\r
+               printf("Expanded memory is not present\n");\r
+               exit(0);\r
+          }\r
+     \r
+          if(!emmok())\r
+          {\r
+               printf("Expanded memory manager is not present\n");\r
+               exit(0);\r
+          }\r
+     \r
+          avail = emmavail();\r
+          if (avail == -1)\r
+          {\r
+               printf("Expanded memory manager error\n");\r
+               exit(0);\r
+          }\r
+          printf("There are %ld pages available\n",avail);\r
+     \r
+          /* Request 10 pages of expanded memory */\r
+          if((emmhandle = emmalloc(10)) < 0)\r
+          {\r
+               printf("Insufficient pages available\n");\r
+               exit(0);\r
+          }\r
+     \r
+          for (i = 0; i < 10; i++)\r
+          {\r
+               sprintf(teststr,"%02d This is a test string\n",i);\r
+               emmmap(emmhandle,i,0);\r
+               emmmove(0,teststr,strlen(teststr) + 1);\r
+          }\r
+     \r
+          for (i = 0; i < 10; i++)\r
+          {\r
+               emmmap(emmhandle,i,0);\r
+               emmget(0,teststr,strlen(teststr) + 1);\r
+               printf("READING BLOCK %d: %s\n",i,teststr);\r
+          }\r
+     \r
+          emmclose(emmhandle);\r
+     }\r
+                                    \r
+                        ACCESSING EXTENDED MEMORY\r
+\r
+\r
+Extended memory has all but taken over from Expanded Memory now (1996).\r
+It is faster and more useable than expanded memory. As with Expanded\r
+memory, Extended memory cannot be directly accessed through the standard\r
+DOS mode, and so a transfer buffer in conventional or "real-mode" memory\r
+needs to be used. The process to write data to Extended memory then\r
+involves copying the data to the transfer buffer in conventional memory,\r
+and from there copying it to Extended memory.\r
+\r
+Before any use may be made of Extended memory, a program should test to\r
+see if Extended memory is available. The following function, XMS_init(),\r
+tests for the presence of Extended memory, and if available calls another\r
+function, GetXMSEntry()  to initialise the program for using Extended\r
+Memory. The function also allocates a conventional memory transfer\r
+buffer.\r
+\r
+\r
+     \r
+     /*\r
+          BLOCKSIZE will be the size of our real-memory buffer that\r
+          we'll swap XMS through (must be a multiple of 1024, since\r
+          XMS is allocated in 1K chunks.)\r
+     */\r
+     \r
+     #ifdef __SMALL__\r
+     #define BLOCKSIZE (16L * 1024L)\r
+     #endif\r
+     \r
+     \r
+     #ifdef __MEDIUM__\r
+     #define BLOCKSIZE (16L * 1024L)\r
+     #endif\r
+     \r
+     \r
+     #ifdef __COMPACT__\r
+     #define BLOCKSIZE (64L * 1024L)\r
+     #endif\r
+     \r
+     #ifdef __LARGE__\r
+     #define BLOCKSIZE (64L * 1024L)\r
+     #endif\r
+     \r
+     \r
+     char XMS_init()\r
+     {\r
+          /*\r
+               returns 0 if XMS present,\r
+                    1 if XMS absent\r
+                    2 if unable to allocate conventional memory transfer\r
+     buffer\r
+          */\r
+          unsigned char status;\r
+          _AX=0x4300;\r
+          geninterrupt(0x2F);\r
+          status = _AL;\r
+          if(status==0x80)\r
+          {\r
+               GetXMSEntry();\r
+               XMSBuf = (char far *) farmalloc(BLOCKSIZE);\r
+               if (XMSBuf == NULL)\r
+                    return 2;\r
+               return 0;\r
+          }\r
+          return 1;\r
+     }\r
+     \r
+     void GetXMSEntry(void)\r
+     {\r
+          /*\r
+               GetXMSEntry sets XMSFunc to the XMS Manager entry point\r
+               so we can call it later\r
+          */\r
+     \r
+          _AX=0x4310;\r
+          geninterrupt(0x2F);\r
+          XMSFunc= (void (far *)(void)) MK_FP(_ES,_BX);\r
+     }\r
+\r
+\r
+Once the presence of Extended memory has been confirmed, a program can\r
+find out how much Extended memory is available;\r
+\r
+     void XMSSize(int *kbAvail, int *largestAvail)\r
+     {\r
+          /*\r
+               XMSSize returns the total kilobytes available, and the\r
+     size\r
+               in kilobytes of the largest available block\r
+          */\r
+     \r
+          _AH=8;\r
+          (*XMSFunc)();\r
+          *largestAvail=_DX;\r
+          *kbAvail=_AX;\r
+     }\r
+\r
+\r
+The following function may be called to allocate a block of Extended\r
+memory, like you would allocate a block of conventional memory.\r
+\r
+     char AllocXMS(unsigned long numberBytes)\r
+     {\r
+          /*\r
+               Allocate a block of XMS memory numberBytes long\r
+               Returns 1 on success\r
+                    0 on failure\r
+          */\r
+     \r
+          _DX = (int)(numberBytes / 1024);\r
+          _AH = 9;\r
+          (*XMSFunc)();\r
+          if (_AX==0)\r
+          {\r
+               return 0;\r
+          }\r
+          XMSHandle=_DX;\r
+          return 1;\r
+     }\r
+     \r
+\r
+Allocated Extended memory is not freed by DOS. A program using Extended\r
+memory must release it before terminating. This function frees a block of\r
+extended memory previously allocated by AllocXMS. Note, XMSHandle is a\r
+global variable of type int.\r
+\r
+     void XMS_free(void)\r
+     {\r
+          /*\r
+               Free used XMS\r
+          */\r
+          _DX=XMSHandle;\r
+          _AH=0x0A;\r
+          (*XMSFunc)();\r
+     }\r
+\r
+\r
+Two functions are now given. One for writing data to Extended memory, and\r
+one for reading data from Extended memory into conventional memory.\r
+\r
+     /*\r
+          XMSParms is a structure for copying information to and from\r
+          real-mode memory to XMS memory\r
+     */\r
+     \r
+     struct parmstruct\r
+     {\r
+          /*\r
+               blocklength is the size in bytes of block to copy\r
+          */\r
+          unsigned long blockLength;\r
+     \r
+          /*\r
+               sourceHandle is the XMS handle of source; 0 means that\r
+               sourcePtr will be a 16:16 real-mode pointer, otherwise\r
+               sourcePtr is a 32-bit offset from the beginning of the\r
+               XMS area that sourceHandle points to\r
+          */\r
+     \r
+          unsigned int sourceHandle;\r
+          far void *sourcePtr;\r
+     \r
+          /*\r
+               destHandle is the XMS handle of destination; 0 means that\r
+               destPtr will be a 16:16 real-mode pointer, otherwise\r
+               destPtr is a 32-bit offset from the beginning of the XMS\r
+               area that destHandle points to\r
+          */\r
+     \r
+          unsigned int destHandle;\r
+          far void *destPtr;\r
+     }\r
+     XMSParms;\r
+     \r
+     \r
+     char XMS_write(unsigned long loc, char far *val, unsigned length)\r
+     {\r
+          /*\r
+               Round length up to next even value\r
+          */\r
+          length += length % 2;\r
+     \r
+          XMSParms.sourceHandle=0;\r
+          XMSParms.sourcePtr=val;\r
+          XMSParms.destHandle=XMSHandle;\r
+          XMSParms.destPtr=(void far *) (loc);\r
+          XMSParms.blockLength=length;  /* Must be an even number! */\r
+          _SI = FP_OFF(&XMSParms);\r
+          _AH=0x0B;\r
+          (*XMSFunc)();\r
+          if (_AX==0)\r
+          {\r
+               return 0;\r
+          }\r
+          return 1;\r
+     }\r
+     \r
+     \r
+     void *XMS_read(unsigned long loc,unsigned length)\r
+     {\r
+          /*\r
+               Returns pointer to data\r
+               or NULL on error\r
+          */\r
+     \r
+          /*\r
+               Round length up to next even value\r
+          */\r
+          length += length % 2;\r
+     \r
+          XMSParms.sourceHandle=XMSHandle;\r
+          XMSParms.sourcePtr=(void far *) (loc);\r
+          XMSParms.destHandle=0;\r
+          XMSParms.destPtr=XMSBuf;\r
+          XMSParms.blockLength=length;       /* Must be an even number */\r
+          _SI=FP_OFF(&XMSParms);\r
+          _AH=0x0B;\r
+          (*XMSFunc)();\r
+          if (_AX==0)\r
+          {\r
+               return NULL;\r
+          }\r
+          return XMSBuf;\r
+     }\r
+     \r
+     \r
+And now putting it all together is a demonstration program.\r
+\r
+     /* A sequential table of variable length records in XMS */\r
+     \r
+     #include <dos.h>\r
+     #include <stdio.h>\r
+     #include <stdlib.h>\r
+     #include <alloc.h>\r
+     #include <string.h>\r
+     \r
+     #define TRUE 1\r
+     #define FALSE 0\r
+     \r
+     /*\r
+          BLOCKSIZE will be the size of our real-memory buffer that\r
+          we'll swap XMS through (must be a multiple of 1024, since\r
+          XMS is allocated in 1K chunks.)\r
+     */\r
+     \r
+     #ifdef __SMALL__\r
+     #define BLOCKSIZE (16L * 1024L)\r
+     #endif\r
+     \r
+     \r
+     #ifdef __MEDIUM__\r
+     #define BLOCKSIZE (16L * 1024L)\r
+     #endif\r
+     \r
+     \r
+     #ifdef __COMPACT__\r
+     #define BLOCKSIZE (64L * 1024L)\r
+     #endif\r
+     \r
+     #ifdef __LARGE__\r
+     #define BLOCKSIZE (64L * 1024L)\r
+     #endif\r
+     \r
+     \r
+     /*\r
+          XMSParms is a structure for copying information to and from\r
+          real-mode memory to XMS memory\r
+     */\r
+     \r
+     struct parmstruct\r
+     {\r
+          /*\r
+               blocklength is the size in bytes of block to copy\r
+          */\r
+          unsigned long blockLength;\r
+     \r
+          /*\r
+               sourceHandle is the XMS handle of source; 0 means that\r
+               sourcePtr will be a 16:16 real-mode pointer, otherwise\r
+               sourcePtr is a 32-bit offset from the beginning of the\r
+               XMS area that sourceHandle points to\r
+          */\r
+     \r
+          unsigned int sourceHandle;\r
+          far void *sourcePtr;\r
+     \r
+          /*\r
+               destHandle is the XMS handle of destination; 0 means that\r
+               destPtr will be a 16:16 real-mode pointer, otherwise\r
+               destPtr is a 32-bit offset from the beginning of the XMS\r
+               area that destHandle points to\r
+          */\r
+     \r
+          unsigned int destHandle;\r
+          far void *destPtr;\r
+     }\r
+     XMSParms;\r
+     \r
+     void far (*XMSFunc) (void);   /* Used to call XMS manager\r
+     (himem.sys) */\r
+     char GetBuf(void);\r
+     void GetXMSEntry(void);\r
+     \r
+     char *XMSBuf;  /* Conventional memory buffer for transfers */\r
+     \r
+     unsigned int XMSHandle;  /* handle to allocated XMS block */\r
+     \r
+     \r
+     char XMS_init()\r
+     {\r
+          /*\r
+               returns 0 if XMS present,\r
+                    1 if XMS absent\r
+                    2 if unable to allocate transfer buffer\r
+          */\r
+          unsigned char status;\r
+          _AX=0x4300;\r
+          geninterrupt(0x2F);\r
+          status = _AL;\r
+          if(status==0x80)\r
+          {\r
+               GetXMSEntry();\r
+               XMSBuf = (char far *) farmalloc(BLOCKSIZE);\r
+               if (XMSBuf == NULL)\r
+                    return 2;\r
+               return 0;\r
+          }\r
+          return 1;\r
+     }\r
+     \r
+     void GetXMSEntry(void)\r
+     {\r
+          /*\r
+               GetXMSEntry sets XMSFunc to the XMS Manager entry point\r
+               so we can call it later\r
+          */\r
+     \r
+          _AX=0x4310;\r
+          geninterrupt(0x2F);\r
+          XMSFunc= (void (far *)(void)) MK_FP(_ES,_BX);\r
+     }\r
+     \r
+     \r
+     void XMSSize(int *kbAvail, int *largestAvail)\r
+     {\r
+          /*\r
+               XMSSize returns the total kilobytes available, and the\r
+     size\r
+               in kilobytes of the largest available block\r
+          */\r
+     \r
+          _AH=8;\r
+          (*XMSFunc)();\r
+          *largestAvail=_DX;\r
+          *kbAvail=_AX;\r
+     }\r
+     \r
+     char AllocXMS(unsigned long numberBytes)\r
+     {\r
+          /*\r
+               Allocate a block of XMS memory numberBytes long\r
+          */\r
+     \r
+          _DX = (int)(numberBytes / 1024);\r
+          _AH = 9;\r
+          (*XMSFunc)();\r
+          if (_AX==0)\r
+          {\r
+               return FALSE;\r
+          }\r
+          XMSHandle=_DX;\r
+          return TRUE;\r
+     }\r
+     \r
+     void XMS_free(void)\r
+     {\r
+          /*\r
+               Free used XMS\r
+          */\r
+          _DX=XMSHandle;\r
+          _AH=0x0A;\r
+          (*XMSFunc)();\r
+     }\r
+     \r
+     char XMS_write(unsigned long loc, char far *val, unsigned length)\r
+     {\r
+          /*\r
+               Round length up to next even value\r
+          */\r
+          length += length % 2;\r
+     \r
+          XMSParms.sourceHandle=0;\r
+          XMSParms.sourcePtr=val;\r
+          XMSParms.destHandle=XMSHandle;\r
+          XMSParms.destPtr=(void far *) (loc);\r
+          XMSParms.blockLength=length;  /* Must be an even number! */\r
+          _SI = FP_OFF(&XMSParms);\r
+          _AH=0x0B;\r
+          (*XMSFunc)();\r
+          if (_AX==0)\r
+          {\r
+               return FALSE;\r
+          }\r
+          return TRUE;\r
+     }\r
+     \r
+     \r
+     void *XMS_read(unsigned long loc,unsigned length)\r
+     {\r
+          /*\r
+               Returns pointer to data\r
+               or NULL on error\r
+          */\r
+     \r
+          /*\r
+               Round length up to next even value\r
+          */\r
+          length += length % 2;\r
+     \r
+          XMSParms.sourceHandle=XMSHandle;\r
+          XMSParms.sourcePtr=(void far *) (loc);\r
+          XMSParms.destHandle=0;\r
+          XMSParms.destPtr=XMSBuf;\r
+          XMSParms.blockLength=length;  /* Must be an even number */\r
+          _SI=FP_OFF(&XMSParms);\r
+          _AH=0x0B;\r
+          (*XMSFunc)();\r
+          if (_AX==0)\r
+          {\r
+               return NULL;\r
+          }\r
+          return XMSBuf;\r
+     }\r
+     \r
+     \r
+     /*\r
+          Demonstration code\r
+          Read various length strings into a single XMS block (EMB)\r
+          and write them out again\r
+     */\r
+     \r
+     int main()\r
+     {\r
+          int kbAvail,largestAvail;\r
+          char buffer[80];\r
+          char *p;\r
+          long pos;\r
+          long end;\r
+     \r
+          if (XMS_init() == 0)\r
+               printf("XMS Available ...\n");\r
+          else\r
+          {\r
+               printf("XMS Not Available\n");\r
+               return(1);\r
+          }\r
+     \r
+          XMSSize(&kbAvail,&largestAvail);\r
+          printf("Kilobytes Available: %d; Largest block:\r
+     %dK\n",kbAvail,largestAvail);\r
+     \r
+          if (!AllocXMS(2000 * 1024L))\r
+               return(1);\r
+     \r
+     \r
+          pos = 0;\r
+     \r
+          do\r
+          {\r
+               p = fgets(buffer,1000,stdin);\r
+               if (p != NULL)\r
+               {\r
+                    XMS_write(pos,buffer,strlen(buffer) + 1);\r
+                    pos += strlen(buffer) + 1;\r
+               }\r
+          }\r
+          while(p != NULL);\r
+     \r
+          end = pos;\r
+     \r
+          pos = 0;\r
+     \r
+          do\r
+          {\r
+               memcpy(buffer,XMS_read(pos,100),70);\r
+               printf("%s",buffer);\r
+               pos += strlen(buffer) + 1;\r
+          }\r
+          while(pos < end);\r
+     \r
+          /*\r
+               It is VERY important to free any XMS before exiting!\r
+          */\r
+          XMS_free();\r
+          return 0;\r
+     }\r
+\r
+                                    \r
+                                    \r
+                                    \r
+                             TSR PROGRAMMING\r
+\r
+\r
+Programs which remain running and resident in memory while other programs\r
+are running are the most exciting line of programming for many PC\r
+developers. This type of program is known as a "Terminate and Stay\r
+Resident" or "TSR" program and they are very difficult to program\r
+sucessfuly.\r
+\r
+The difficulties in programming TSRs comes from the limitations of DOS\r
+which is not a multi-tasking operating system, and does not react well to\r
+re-enterant code. That is it's own functions (interrupts) calling\r
+themselves.\r
+\r
+In theory a TSR is quite simple. It is an ordinary program which\r
+terminates not through the usual DOS terminate function, but through the\r
+DOS "keep" function - interrupt 27h. This function reserves an area of\r
+memory, used by the program so that no other programs will overwrite it.\r
+This in itself is not a very difficult task, excepting that the program\r
+needs to tell DOS how much memory to leave it!\r
+\r
+The problems stem mainly from not being able to use DOS function calls\r
+within the TSR program once it has "gone resident".\r
+\r
+There are a few basic rules which help to clarify the problems\r
+encountered in programming TSRs:\r
+\r
+  1.Avoid DOS function calls\r
+  2.Monitor the DOS busy flag, when this flag is nonzero, DOS is\r
+     executing an interrupt 21h function and MUST NOT be disturbed!\r
+  3.Monitor interrupt 28h. This reveals when DOS is busy waiting for\r
+     console input. At this time you can disturb DOS regardless of the\r
+     DOS busy flag setting.\r
+  4.Provide some way of checking whether the TSR is already loaded to\r
+     prevent multiple copies occuring in memory.\r
+  5.Remember that other TSR programs may be chained to interrupts, and\r
+     so you must chain any interrupt vectors that your program needs.\r
+  6.Your TSR program must use its own stack, and NOT that of the running\r
+     process.\r
+  7.TSR programs must be compiled in a small memory model with stack\r
+     checking turned off.\r
+  8.When control passes to your TSR program, it must tell DOS that the\r
+     active process has changed.\r
+\r
+\r
+The following three source code modules describe a complete TSR program.\r
+This is a useful pop-up address book database which can be activated\r
+while any other program is running by pressing the key combination `Alt'\r
+and `.'.  If the address book does not respond to the key press, it is\r
+probably because DOS cannot be disturbed, and you should try to pop-it-up\r
+again.\r
+\r
+\r
+     /*\r
+        A practical TSR program (a pop-up address book database)\r
+        Compile in small memory model with stack checking OFF\r
+     */\r
+     \r
+     #include <dos.h>\r
+     #include <stdio.h>\r
+     #include <string.h>\r
+     #include <dir.h>\r
+     \r
+     static union REGS rg;\r
+     \r
+     /*\r
+        Size of the program to remain resident\r
+        experimentation is required to make this as small as possible\r
+     */\r
+     unsigned sizeprogram = 28000/16;\r
+     \r
+     /* Activate with Alt . */\r
+     unsigned scancode = 52;  /* . */\r
+     unsigned keymask = 8;         /* ALT */\r
+     \r
+     char signature[]= "POPADDR";\r
+     char fpath[40];\r
+     \r
+     /*\r
+          Function prototypes\r
+     */\r
+     \r
+     void curr_cursor(int *x, int *y);\r
+     int resident(char *, void interrupt(*)());\r
+     void resinit(void);\r
+     void terminate(void);\r
+     void restart(void);\r
+     void wait(void);\r
+     void resident_psp(void);\r
+     void exec(void);\r
+     \r
+     /*\r
+        Entry point from DOS\r
+     */\r
+     \r
+     main(int argc, char *argv[])\r
+     {\r
+          void interrupt ifunc();\r
+          int ivec;\r
+     \r
+          /*\r
+             For simplicity, assume the data file is in the root\r
+     directory\r
+             of drive C:\r
+          */\r
+          strcpy(fpath,"C:\\ADDRESS.DAT");\r
+     \r
+          if ((ivec = resident(signature,ifunc)) != 0)\r
+          {\r
+               /* TSR is resident */\r
+               if (argc > 1)\r
+               {\r
+                    rg.x.ax = 0;\r
+                    if (strcmp(argv[1],"quit") == 0)\r
+                         rg.x.ax = 1;\r
+                    else if (strcmp(argv[1],"restart") == 0)\r
+                         rg.x.ax = 2;\r
+                    else if (strcmp(argv[1],"wait") == 0)\r
+                         rg.x.ax = 3;\r
+                    if (rg.x.ax)\r
+                    {\r
+                         int86(ivec,&rg,&rg);\r
+                         return;\r
+                    }\r
+               }\r
+               printf("\nPopup Address Book is already resident");\r
+          }\r
+          else\r
+          {\r
+               /* Initial load of TSR program */\r
+               printf("Popup Address Book Resident.\nPress Alt . To\r
+     Activate....\n");\r
+               resinit();\r
+          }\r
+     }\r
+     \r
+     void interrupt ifunc(bp,di,si,ds,es,dx,cx,bx,ax)\r
+     {\r
+          if(ax == 1)\r
+               terminate();\r
+          else if(ax == 2)\r
+               restart();\r
+          else if(ax == 3)\r
+               wait();\r
+     }\r
+     \r
+     popup()\r
+     {\r
+          int x,y;\r
+     \r
+          curr_cursor(&x,&y);\r
+     \r
+          /* Call the TSR C program here */\r
+          exec();\r
+          cursor(x,y);\r
+     }\r
+     \r
+     /*\r
+          Second source module\r
+     */\r
+     \r
+     #include <dos.h>\r
+     #include <stdio.h>\r
+     \r
+     static union REGS rg;\r
+     static struct SREGS seg;\r
+     static unsigned mcbseg;\r
+     static unsigned dosseg;\r
+     static unsigned dosbusy;\r
+     static unsigned enddos;\r
+     char far *intdta;\r
+     static unsigned intsp;\r
+     static unsigned intss;\r
+     static char far *mydta;\r
+     static unsigned myss;\r
+     static unsigned stack;\r
+     static unsigned ctrl_break;\r
+     static unsigned mypsp;\r
+     static unsigned intpsp;\r
+     static unsigned pids[2];\r
+     static int pidctr = 0;\r
+     static int pp;\r
+     static void interrupt (*oldtimer)();\r
+     static void interrupt (*old28)();\r
+     static void interrupt (*oldkb)();\r
+     static void interrupt (*olddisk)();\r
+     static void interrupt (*oldcrit)();\r
+     \r
+     void interrupt newtimer();\r
+     void interrupt new28();\r
+     void interrupt newkb();\r
+     void interrupt newdisk();\r
+     void interrupt newcrit();\r
+     \r
+     extern unsigned sizeprogram;\r
+     extern unsigned scancode;\r
+     extern unsigned keymask;\r
+     \r
+     static int resoff = 0;\r
+     static int running = 0;\r
+     static int popflg = 0;\r
+     static int diskflag = 0;\r
+     static int kbval;\r
+     static int cflag;\r
+     \r
+     void dores(void);\r
+     void pidaddr(void);\r
+     \r
+     void resinit()\r
+     {\r
+          segread(&seg);\r
+          myss = seg.ss;\r
+     \r
+          rg.h.ah = 0x34;\r
+          intdos(&rg,&rg);\r
+          dosseg = _ES;\r
+          dosbusy = rg.x.bx;\r
+     \r
+          mydta = getdta();\r
+          pidaddr();\r
+          oldtimer = getvect(0x1c);\r
+          old28 = getvect(0x28);\r
+          oldkb = getvect(9);\r
+          olddisk = getvect(0x13);\r
+     \r
+          setvect(0x1c,newtimer);\r
+          setvect(9,newkb);\r
+          setvect(0x28,new28);\r
+          setvect(0x13,newdisk);\r
+     \r
+          stack = (sizeprogram - (seg.ds - seg.cs)) * 16 - 300;\r
+          rg.x.ax = 0x3100;\r
+          rg.x.dx = sizeprogram;\r
+          intdos(&rg,&rg);\r
+     }\r
+     \r
+     void interrupt newdisk(bp,di,si,ds,es,dx,cx,bx,ax,ip,cs,flgs)\r
+     {\r
+          diskflag++;\r
+          (*olddisk)();\r
+          ax = _AX;\r
+          newcrit();\r
+          flgs = cflag;\r
+          --diskflag;\r
+     }\r
+     \r
+     void interrupt newcrit(bp,di,si,ds,es,dx,cx,bx,ax,ip,cs,flgs)\r
+     {\r
+          ax = 0;\r
+          cflag = flgs;\r
+     }\r
+     \r
+     void interrupt newkb()\r
+     {\r
+          if (inportb(0x60) == scancode)\r
+          {\r
+               kbval = peekb(0,0x417);\r
+               if (!resoff && ((kbval & keymask) ^ keymask) == 0)\r
+               {\r
+                    kbval = inportb(0x61);\r
+                    outportb(0x61,kbval | 0x80);\r
+                    outportb(0x61,kbval);\r
+                    disable();\r
+                    outportb(0x20,0x20);\r
+                    enable();\r
+                    if (!running)\r
+                         popflg = 1;\r
+                    return;\r
+               }\r
+          }\r
+          (*oldkb)();\r
+     }\r
+     \r
+     void interrupt newtimer()\r
+     {\r
+          (*oldtimer)();\r
+          if (popflg && peekb(dosseg,dosbusy) == 0)\r
+               if(diskflag == 0)\r
+               {\r
+                    outportb(0x20,0x20);\r
+                    popflg = 0;\r
+                    dores();\r
+               }\r
+     }\r
+     \r
+     void interrupt new28()\r
+     {\r
+          (*old28)();\r
+          if (popflg && peekb(dosseg,dosbusy) != 0)\r
+          {\r
+               popflg = 0;\r
+               dores();\r
+          }\r
+     }\r
+     \r
+     resident_psp()\r
+     {\r
+          intpsp = peek(dosseg,*pids);\r
+          for(pp = 0; pp < pidctr; pp++)\r
+               poke(dosseg,pids[pp],mypsp);\r
+     }\r
+     \r
+     interrupted_psp()\r
+     {\r
+          for(pp = 0; pp < pidctr; pp++)\r
+               poke(dosseg,pids[pp],intpsp);\r
+     }\r
+     \r
+     void dores()\r
+     {\r
+          running = 1;\r
+          disable();\r
+          intsp = _SP;\r
+          intss = _SS;\r
+          _SP = stack;\r
+          _SS = myss;\r
+          enable();\r
+          oldcrit = getvect(0x24);\r
+          setvect(0x24,newcrit);\r
+          rg.x.ax = 0x3300;\r
+          intdos(&rg,&rg);\r
+          ctrl_break = rg.h.dl;\r
+          rg.x.ax = 0x3301;\r
+          rg.h.dl = 0;\r
+          intdos(&rg,&rg);\r
+          intdta = getdta();\r
+          setdta(mydta);\r
+          resident_psp();\r
+          popup();\r
+          interrupted_psp();\r
+          setdta(intdta);\r
+          setvect(0x24,oldcrit);\r
+          rg.x.ax = 0x3301;\r
+          rg.h.dl = ctrl_break;\r
+          intdos(&rg,&rg);\r
+          disable();\r
+          _SP = intsp;\r
+          _SS = intss;\r
+          enable();\r
+          running = 0;\r
+     }\r
+     \r
+     static int avec = 0;\r
+     \r
+     unsigned resident(char *signature,void interrupt(*ifunc)())\r
+     {\r
+          char *sg;\r
+          unsigned df;\r
+          int vec;\r
+     \r
+          segread(&seg);\r
+          df = seg.ds-seg.cs;\r
+          for(vec = 0x60; vec < 0x68; vec++)\r
+          {\r
+               if (getvect(vec) == NULL)\r
+               {\r
+                    if (!avec)\r
+                         avec = vec;\r
+                    continue;\r
+               }\r
+               for(sg = signature; *sg; sg++)\r
+               if (*sg != peekb(peek(0,2+vec*4)+df,(unsigned)sg))\r
+                    break;\r
+               if (!*sg)\r
+                    return vec;\r
+          }\r
+          if (avec)\r
+               setvect(avec,ifunc);\r
+          return 0;\r
+     }\r
+     \r
+     static void pidaddr()\r
+     {\r
+          unsigned adr = 0;\r
+     \r
+          rg.h.ah = 0x51;\r
+          intdos(&rg,&rg);\r
+          mypsp = rg.x.bx;\r
+          rg.h.ah = 0x52;\r
+          intdos(&rg,&rg);\r
+          enddos = _ES;\r
+          enddos = peek(enddos,rg.x.bx-2);\r
+          while(pidctr < 2 && (unsigned)((dosseg<<4) + adr) < (enddos\r
+     <<4))\r
+          {\r
+               if (peek(dosseg,adr) == mypsp)\r
+               {\r
+                    rg.h.ah = 0x50;\r
+                    rg.x.bx = mypsp + 1;\r
+                    intdos(&rg,&rg);\r
+                    if (peek(dosseg,adr) == mypsp + 1)\r
+                         pids[pidctr++] = adr;\r
+                    rg.h.ah = 0x50;\r
+                    rg.x.bx = mypsp;\r
+                    intdos(&rg,&rg);\r
+               }\r
+               adr++;\r
+          }\r
+     }\r
+     \r
+     static resterm()\r
+     {\r
+          setvect(0x1c,oldtimer);\r
+          setvect(9,oldkb);\r
+          setvect(0x28,old28);\r
+          setvect(0x13,olddisk);\r
+          setvect(avec,(void interrupt (*)()) 0);\r
+          rg.h.ah = 0x52;\r
+          intdos(&rg,&rg);\r
+          mcbseg = _ES;\r
+          mcbseg = peek(mcbseg,rg.x.bx-2);\r
+          segread(&seg);\r
+          while(peekb(mcbseg,0) == 0x4d)\r
+          {\r
+               if(peek(mcbseg,1) == mypsp)\r
+               {\r
+                    rg.h.ah = 0x49;\r
+                    seg.es = mcbseg+1;\r
+                    intdosx(&rg,&rg,&seg);\r
+               }\r
+               mcbseg += peek(mcbseg,3) + 1;\r
+          }\r
+     }\r
+     \r
+     terminate()\r
+     {\r
+          if (getvect(0x13) == (void interrupt (*)()) newdisk)\r
+               if (getvect(9) == newkb)\r
+                    if(getvect(0x28) == new28)\r
+                         if(getvect(0x1c) == newtimer)\r
+                         {\r
+                              resterm();\r
+                              return;\r
+                         }\r
+          resoff = 1;\r
+     }\r
+     \r
+     restart()\r
+     {\r
+          resoff = 0;\r
+     }\r
+     \r
+     wait()\r
+     {\r
+          resoff = 1;\r
+     }\r
+     \r
+     void cursor(int y, int x)\r
+     {\r
+          rg.x.ax = 0x0200;\r
+          rg.x.bx = 0;\r
+          rg.x.dx = ((y << 8) & 0xff00) + x;\r
+          int86(16,&rg,&rg);\r
+     }\r
+     \r
+     void curr_cursor(int *y, int *x)\r
+     {\r
+          rg.x.ax = 0x0300;\r
+          rg.x.bx = 0;\r
+          int86(16,&rg,&rg);\r
+          *x = rg.h.dl;\r
+          *y = rg.h.dh;\r
+     }\r
+     \r
+     /*\r
+        Third module, the simple pop-up address book\r
+        with mouse support\r
+     */\r
+     \r
+     #include <stdio.h>\r
+     #include <stdlib.h>\r
+     #include <io.h>\r
+     #include <string.h>\r
+     #include <fcntl.h>\r
+     #include <sys\stat.h>\r
+     #include <dos.h>\r
+     #include <conio.h>\r
+     #include <graphics.h>\r
+     #include <bios.h>\r
+     \r
+     /* left cannot be less than 3 */\r
+     #define left   4\r
+     \r
+     /* Data structure for records */\r
+     typedef struct\r
+     {\r
+          char name[31];\r
+          char company[31];\r
+          char address[31];\r
+          char area[31];\r
+          char town[31];\r
+          char county[31];\r
+          char post[13];\r
+          char telephone[16];\r
+          char fax[16];\r
+     }\r
+     data;\r
+     \r
+     extern char fpath[];\r
+     \r
+     static char scr[4000];\r
+     \r
+     static char sbuff[2000];\r
+     char stext[30];\r
+     data rec;\r
+     int handle;\r
+     int recsize;\r
+     union REGS inreg,outreg;\r
+     \r
+     /*\r
+          Function prototypes\r
+     */\r
+     void FATAL(char *);\r
+     void OPENDATA(void);\r
+     void CONTINUE(void);\r
+     void EXPORT_MULTI(void);\r
+     void GETDATA(int);\r
+     int GETOPT(void);\r
+     void DISPDATA(void);\r
+     void ADD_REC(void);\r
+     void PRINT_MULTI(void);\r
+     void SEARCH(void);\r
+     void MENU(void);\r
+     \r
+     int GET_MOUSE(int *buttons)\r
+     {\r
+          inreg.x.ax = 0;\r
+          int86(0x33,&inreg,&outreg);\r
+          *buttons = outreg.x.bx;\r
+          return outreg.x.ax;\r
+     }\r
+     \r
+     void MOUSE_CURSOR(int status)\r
+     {\r
+          /* Status = 0 cursor off */\r
+          /*          1 cursor on */\r
+     \r
+          inreg.x.ax = 2 - status;\r
+          int86(0x33,&inreg,&outreg);\r
+     }\r
+     \r
+     int MOUSE_LOCATION(int *x, int *y)\r
+     {\r
+          inreg.x.ax = 3;\r
+          int86(0x33,&inreg,&outreg);\r
+     \r
+          *x = outreg.x.cx / 8;\r
+          *y = outreg.x.dx / 8;\r
+     \r
+          return outreg.x.bx;\r
+     }\r
+     \r
+     int GETOPT()\r
+     {\r
+          int result;\r
+          int x;\r
+          int y;\r
+     \r
+          do\r
+          {\r
+               do\r
+               {\r
+                    result = MOUSE_LOCATION(&x,&y);\r
+                    if (result & 1)\r
+                    {\r
+                         if (x >= 52 && x <= 53 && y >= 7 && y <= 15)\r
+                              return y - 7;\r
+                         if (x >= 4 && x <= 40 && y >= 7 && y <= 14)\r
+                              return y + 10;\r
+     \r
+                         if (x >= 4 && x <= 40 && y == 15)\r
+                              return y + 10;\r
+                    }\r
+               }\r
+               while(!bioskey(1));\r
+     \r
+               result = bioskey(0);\r
+               x = result & 0xff;\r
+               if (x == 0)\r
+               {\r
+                    result = result >> 8;\r
+                    result -= 60;\r
+               }\r
+          }\r
+          while(result < 0 || result > 8);\r
+          return result;\r
+     }\r
+     \r
+     void setvideo(unsigned char mode)\r
+     {\r
+          /* Sets the video display mode     and clears the screen */\r
+     \r
+          inreg.h.al = mode;\r
+          inreg.h.ah = 0x00;\r
+          int86(0x10, &inreg, &outreg);\r
+     }\r
+     \r
+     \r
+     int activepage(void)\r
+     {\r
+          /* Returns the currently selected video display page */\r
+     \r
+          union REGS inreg,outreg;\r
+     \r
+          inreg.h.ah = 0x0F;\r
+          int86(0x10, &inreg, &outreg);\r
+          return(outreg.h.bh);\r
+     }\r
+     \r
+     void print(char *str)\r
+     {\r
+          /*\r
+             Prints characters only directly to the current display page\r
+             starting at the current cursor position. The cursor is not\r
+             advanced.\r
+             This function assumes a colour display card. For use with a\r
+             monochrome display card change 0xB800 to read 0xB000\r
+          */\r
+     \r
+          int page;\r
+          int offset;\r
+          unsigned row;\r
+          unsigned col;\r
+          char far *ptr;\r
+     \r
+          page = activepage();\r
+          curr_cursor(&row,&col);\r
+     \r
+          offset = page * 4000 + row * 160 + col * 2;\r
+     \r
+          ptr = MK_FP(0xB800,offset);\r
+     \r
+          while(*str)\r
+          {\r
+               *ptr++= *str++;\r
+               ptr++;\r
+          }\r
+     }\r
+     \r
+     \r
+     void TRUESHADE(int lef, int top, int right, int bottom)\r
+     {\r
+          int n;\r
+     \r
+          /* True Shading of a screen block */\r
+     \r
+          gettext(lef,top,right,bottom,sbuff);\r
+          for(n = 1; n < 2000; n+= 2)\r
+               sbuff[n] = 7;\r
+          puttext(lef,top,right,bottom,sbuff);\r
+     }\r
+     \r
+     void DBOX(int l, int t, int r, int b)\r
+     {\r
+          /* Draws a double line box around the described area */\r
+     \r
+          int n;\r
+     \r
+          cursor(t,l);\r
+          print("\90");\r
+          for(n = 1; n < r - l; n++)\r
+          {\r
+               cursor(t,l + n);\r
+               print("I");\r
+          }\r
+          cursor(t,r);\r
+          print("¯");\r
+     \r
+          for (n = t + 1; n < b; n++)\r
+          {\r
+               cursor(n,l);\r
+               print("§");\r
+               cursor(n,r);\r
+               print("§");\r
+          }\r
+          cursor(b,l);\r
+          print("E");\r
+          for(n = 1; n < r - l; n++)\r
+          {\r
+               cursor(b,l+n);\r
+               print("I");\r
+          }\r
+          cursor(b,r);\r
+          print("¬");\r
+     }\r
+     \r
+     int INPUT(char *text,unsigned length)\r
+     {\r
+          /* Receive a string from the operator */\r
+     \r
+          unsigned key_pos;\r
+          int key;\r
+          unsigned start_row;\r
+          unsigned start_col;\r
+          unsigned end;\r
+          char temp[80];\r
+          char *p;\r
+     \r
+          curr_cursor(&start_row,&start_col);\r
+     \r
+          key_pos = 0;\r
+          end = strlen(text);\r
+          for(;;)\r
+          {\r
+               key = bioskey(0);\r
+               if ((key & 0xFF) == 0)\r
+               {\r
+                    key = key >> 8;\r
+                    if (key == 79)\r
+                    {\r
+                         while(key_pos < end)\r
+                              key_pos++;\r
+                         cursor(start_row,start_col + key_pos);\r
+                    }\r
+                    else\r
+                    if (key == 71)\r
+                    {\r
+                         key_pos = 0;\r
+                         cursor(start_row,start_col);\r
+                    }\r
+                    else\r
+                    if ((key == 75) && (key_pos > 0))\r
+                    {\r
+                         key_pos--;\r
+                         cursor(start_row,start_col + key_pos);\r
+                    }\r
+                    else\r
+                    if ((key == 77) && (key_pos < end))\r
+                    {\r
+                         key_pos++;\r
+                         cursor(start_row,start_col + key_pos);\r
+                    }\r
+                    else\r
+                    if (key == 83)\r
+                    {\r
+                         p = text + key_pos;\r
+                         while(*(p+1))\r
+                         {\r
+                              *p = *(p+1);\r
+                              p++;\r
+                         }\r
+                         *p = 32;\r
+                         if (end > 0)\r
+                              end--;\r
+                         cursor(start_row,start_col);\r
+                         cprintf(text);\r
+                         cprintf(" ");\r
+                         if ((key_pos > 0) && (key_pos == end))\r
+                              key_pos--;\r
+                         cursor(start_row,start_col + key_pos);\r
+                    }\r
+               }\r
+               else\r
+               {\r
+                    key = key & 0xFF;\r
+                    if (key == 13 || key == 27)\r
+                         break;\r
+                    else\r
+                    if ((key == 8) && (key_pos > 0))\r
+                    {\r
+                         end--;\r
+                         key_pos--;\r
+                         text[key_pos--] = '\0';\r
+                         strcpy(temp,text);\r
+                         p = text + key_pos + 2;\r
+                         strcat(temp,p);\r
+                         strcpy(text,temp);\r
+                         cursor(start_row,start_col);\r
+                         cprintf("%-*.*s",length,length,text);\r
+                         key_pos++;\r
+                         cursor(start_row,start_col + key_pos);\r
+                    }\r
+                    else\r
+                    if ((key > 31) && (key_pos < length)  &&\r
+                       (start_col + key_pos < 80))\r
+                    {\r
+                         if (key_pos <= end)\r
+                         {\r
+                              p = text + key_pos;\r
+                              memmove(p+1,p,end - key_pos);\r
+                              if (end < length)\r
+                                   end++;\r
+                              text[end] = '\0';\r
+                         }\r
+                         text[key_pos++] = (char)key;\r
+                         if (key_pos > end)\r
+                         {\r
+                              end++;\r
+                              text[end] = '\0';\r
+                         }\r
+                         cursor(start_row,start_col);\r
+                         cprintf("%-*.*s",length,length,text);\r
+                         cursor(start_row,start_col + key_pos);\r
+                    }\r
+               }\r
+          }\r
+          text[end] = '\0';\r
+          return key;\r
+     }\r
+     \r
+     void FATAL(char *error)\r
+     {\r
+          /* A fatal error has occured */\r
+     \r
+          printf("\nFATAL ERROR: %s",error);\r
+          exit(0);\r
+     }\r
+     \r
+     void OPENDATA()\r
+     {\r
+          /* Check for existence of data file and if not create it */\r
+          /* otherwise open it for reading/writing at end of file */\r
+     \r
+          handle = open(fpath,O_RDWR,S_IWRITE);\r
+     \r
+          if (handle == -1)\r
+          {\r
+               handle = open(fpath,O_RDWR|O_CREAT,S_IWRITE);\r
+               if (handle == -1)\r
+                    FATAL("Unable to create data file");\r
+          }\r
+          /* Read in first rec */\r
+          read(handle,&rec,recsize);\r
+     }\r
+     \r
+     void CLOSEDATA()\r
+     {\r
+          close(handle);\r
+     }\r
+     \r
+     void GETDATA(int start)\r
+     {\r
+          /* Get address data from operator */\r
+     \r
+          textcolor(BLACK);\r
+          textbackground(GREEN);\r
+          gotoxy(left,8);\r
+          print("Name ");\r
+          gotoxy(left,9);\r
+          print("Company ");\r
+          gotoxy(left,10);\r
+          print("Address ");\r
+          gotoxy(left,11);\r
+          print("Area ");\r
+          gotoxy(left,12);\r
+          print("Town ");\r
+          gotoxy(left,13);\r
+          print("County ");\r
+          gotoxy(left,14);\r
+          print("Post Code ");\r
+          gotoxy(left,15);\r
+          print("Telephone ");\r
+          gotoxy(left,16);\r
+          print("Fax ");\r
+     \r
+          switch(start)\r
+          {\r
+               case 0: gotoxy(left + 10,8);\r
+                    if(INPUT(rec.name,30) == 27)\r
+                         break;\r
+               case 1: gotoxy(left + 10,9);\r
+                    if(INPUT(rec.company,30) == 27)\r
+                         break;\r
+               case 2: gotoxy(left + 10,10);\r
+                    if(INPUT(rec.address,30) == 27)\r
+                         break;\r
+               case 3: gotoxy(left + 10,11);\r
+                    if(INPUT(rec.area,30) == 27)\r
+                         break;\r
+               case 4: gotoxy(left + 10,12);\r
+                    if(INPUT(rec.town,30) == 27)\r
+                         break;\r
+               case 5: gotoxy(left + 10,13);\r
+                    if(INPUT(rec.county,30) == 27)\r
+                         break;\r
+               case 6: gotoxy(left + 10,14);\r
+                    if(INPUT(rec.post,12) == 27)\r
+                         break;\r
+               case 7: gotoxy(left + 10,15);\r
+                    if(INPUT(rec.telephone,15) == 27)\r
+                         break;\r
+               case 8: gotoxy(left + 10,16);\r
+                    INPUT(rec.fax,15);\r
+                    break;\r
+          }\r
+          textcolor(WHITE);\r
+          textbackground(RED);\r
+          gotoxy(left + 23,21);\r
+          print("                                                 ");\r
+     }\r
+     \r
+     void DISPDATA()\r
+     {\r
+          /* Display address data */\r
+          textcolor(BLACK);\r
+          textbackground(GREEN);\r
+          cursor(7,3);\r
+          cprintf("Name    %-30.30s",rec.name);\r
+          cursor(8,3);\r
+          cprintf("Company   %-30.30s",rec.company);\r
+          cursor(9,3);\r
+          cprintf("Address   %-30.30s",rec.address);\r
+          cursor(10,3);\r
+          cprintf("Area    %-30.30s",rec.area);\r
+          cursor(11,3);\r
+          cprintf("Town    %-30.30s",rec.town);\r
+          cursor(12,3);\r
+          cprintf("County     %-30.30s",rec.county);\r
+          cursor(13,3);\r
+          cprintf("Post Code %-30.30s",rec.post);\r
+          cursor(14,3);\r
+          cprintf("Telephone %-30.30s",rec.telephone);\r
+          cursor(15,3);\r
+          cprintf("Fax      %-30.30s",rec.fax);\r
+     }\r
+     \r
+     int LOCATE(char *text)\r
+     {\r
+          int result;\r
+     \r
+          do\r
+          {\r
+               /* Read rec into memory */\r
+               result = read(handle,&rec,recsize);\r
+               if (result > 0)\r
+               {\r
+                    /* Scan rec for matching data */\r
+                    if (strstr(strupr(rec.name),text) != NULL)\r
+                         return(1);\r
+                    if (strstr(strupr(rec.company),text) != NULL)\r
+                         return(1);\r
+                    if (strstr(strupr(rec.address),text) != NULL)\r
+                         return(1);\r
+                    if (strstr(strupr(rec.area),text) != NULL)\r
+                         return(1);\r
+                    if (strstr(strupr(rec.town),text) != NULL)\r
+                         return(1);\r
+                    if (strstr(strupr(rec.county),text) != NULL)\r
+                         return(1);\r
+                    if (strstr(strupr(rec.post),text) != NULL)\r
+                         return(1);\r
+                    if (strstr(strupr(rec.telephone),text) != NULL)\r
+                         return(1);\r
+                    if (strstr(strupr(rec.fax),text) != NULL)\r
+                         return(1);\r
+               }\r
+          }\r
+          while(result > 0);\r
+          return(0);\r
+     }\r
+     \r
+     void SEARCH()\r
+     {\r
+          int result;\r
+     \r
+          gotoxy(left,21);\r
+          textcolor(WHITE);\r
+          textbackground(RED);\r
+          cprintf("Enter data to search for ");\r
+          strcpy(stext,"");\r
+          INPUT(stext,30);\r
+          if (*stext == 0)\r
+          {\r
+               gotoxy(left,21);\r
+               cprintf("%70c",32);\r
+               return;\r
+          }\r
+          gotoxy(left,21);\r
+          textcolor(WHITE);\r
+          textbackground(RED);\r
+          cprintf("Searching for %s Please Wait....",stext);\r
+          strupr(stext);\r
+          /* Locate start of file */\r
+          lseek(handle,0,SEEK_SET);\r
+          result = LOCATE(stext);\r
+          if (result == 0)\r
+          {\r
+               gotoxy(left,21);\r
+               cprintf("%70c",32);\r
+               gotoxy(left + 27,21);\r
+               cprintf("NO MATCHING RECORDS");\r
+               gotoxy(left + 24,22);\r
+               cprintf("Press RETURN to Continue");\r
+               bioskey(0);\r
+               gotoxy(left,21);\r
+               cprintf("%70c",32);\r
+               gotoxy(left,22);\r
+               cprintf("%70c",32);\r
+          }\r
+          else\r
+          {\r
+               lseek(handle,0 - recsize,SEEK_CUR);\r
+               read(handle,&rec,recsize);\r
+               DISPDATA();\r
+          }\r
+          textcolor(WHITE);\r
+          textbackground(RED);\r
+          gotoxy(left,21);\r
+          cprintf("%70c",32);\r
+          textcolor(BLACK);\r
+          textbackground(GREEN);\r
+     }\r
+     \r
+     void CONTINUE()\r
+     {\r
+          int result;\r
+          long curpos;\r
+     \r
+          curpos = tell(handle) - recsize;\r
+     \r
+          result = LOCATE(stext);\r
+          textcolor(WHITE);\r
+          textbackground(RED);\r
+          if (result == 0)\r
+          {\r
+               gotoxy(left + 24,21);\r
+               cprintf("NO MORE MATCHING RECORDS");\r
+               gotoxy(left + 24,22);\r
+               cprintf("Press RETURN to Continue");\r
+               bioskey(0);\r
+               gotoxy(left,21);\r
+               cprintf("%70c",32);\r
+               gotoxy(left,22);\r
+               cprintf("%70c",32);\r
+               lseek(handle,curpos,SEEK_SET);\r
+               read(handle,&rec,recsize);\r
+               DISPDATA();\r
+          }\r
+          else\r
+          {\r
+               lseek(handle,0 - recsize,SEEK_CUR);\r
+               read(handle,&rec,recsize);\r
+               DISPDATA();\r
+          }\r
+          textcolor(WHITE);\r
+          textbackground(RED);\r
+          gotoxy(left,21);\r
+          cprintf("%70c",32);\r
+          gotoxy(left,22);\r
+          cprintf("                                           ");\r
+          textcolor(BLACK);\r
+          textbackground(GREEN);\r
+     }\r
+     \r
+     void PRINT_MULTI()\r
+     {\r
+          data buffer;\r
+          char destination[60];\r
+          char text[5];\r
+          int result;\r
+          int ok;\r
+          int ok2;\r
+          int blanks;\r
+          int total_lines;\r
+          char *p;\r
+          FILE *fp;\r
+     \r
+          textcolor(WHITE);\r
+          textbackground(RED);\r
+          gotoxy(left + 23,21);\r
+          cprintf("Enter selection criteria");\r
+     \r
+          /* Clear existing rec details */\r
+          memset(&rec,0,recsize);\r
+     \r
+          DISPDATA();\r
+          GETDATA(0);\r
+     \r
+          textcolor(WHITE);\r
+          textbackground(RED);\r
+          gotoxy(left,21);\r
+          cprintf("Enter report destination PRN");\r
+          strcpy(destination,"PRN");\r
+          gotoxy(left,22);\r
+          cprintf("Enter Address length in lines 18");\r
+          strcpy(text,"18");\r
+          gotoxy(left + 25,21);\r
+          INPUT(destination,40);\r
+          gotoxy(left +30,22);\r
+          INPUT(text,2);\r
+          gotoxy(left,21);\r
+          cprintf("%72c",32);\r
+          gotoxy(left,22);\r
+          cprintf("%72c",32);\r
+     \r
+          total_lines = atoi(text) - 6;\r
+          if (total_lines < 0)\r
+               total_lines = 0;\r
+     \r
+          fp = fopen(destination,"w+");\r
+          if (fp == NULL)\r
+          {\r
+               gotoxy(left,21);\r
+               cprintf("Unable to print to %s",destination);\r
+               gotoxy(left,22);\r
+               cprintf("Press RETURN to Continue");\r
+               bioskey(0);\r
+               gotoxy(left,21);\r
+               cprintf("%78c",32);\r
+               gotoxy(left,22);\r
+               cprintf("                          ");\r
+          }\r
+     \r
+          /* Locate start of file */\r
+          lseek(handle,0,SEEK_SET);\r
+     \r
+          do\r
+          {\r
+               /* Read rec into memory */\r
+               result = read(handle,&buffer,recsize);\r
+               if (result > 0)\r
+               {\r
+                    ok = 1;\r
+                    /* Scan rec for matching data */\r
+                    if (*rec.name)\r
+                         if (stricmp(buffer.name,rec.name))\r
+                              ok = 0;\r
+                    if (*rec.company)\r
+                         if (stricmp(buffer.company,rec.company))\r
+                              ok = 0;\r
+                    if (*rec.address)\r
+                         if (stricmp(buffer.address,rec.address))\r
+                              ok = 0;\r
+                    if (*rec.area)\r
+                         if (stricmp(buffer.area,rec.area))\r
+                              ok = 0;\r
+                    if (*rec.town)\r
+                         if (stricmp(buffer.town,rec.town))\r
+                              ok = 0;\r
+                    if (*rec.county)\r
+                         if (stricmp(buffer.county,rec.county))\r
+                              ok = 0;\r
+                    if (*rec.post)\r
+                         if (stricmp(buffer.post,rec.post))\r
+                         ok = 0;\r
+                    if (*rec.telephone)\r
+                         if (stricmp(buffer.telephone,rec.telephone))\r
+                              ok = 0;\r
+                    if (*rec.fax)\r
+                         if (stricmp(buffer.fax,rec.fax))\r
+                              ok = 0;\r
+                    if (ok)\r
+                    {\r
+                         blanks = total_lines;\r
+                         p = buffer.name;\r
+                         ok2 = 0;\r
+                         while(*p)\r
+                         {\r
+                              if (*p != 32)\r
+                              {\r
+                                   ok2 = 1;\r
+                                   break;\r
+                              }\r
+                              p++;\r
+                         }\r
+                         if (!ok2)\r
+                              blanks++;\r
+                         else\r
+                              fprintf(fp,"%s\n",buffer.name);\r
+                         p = buffer.company;\r
+                         ok2 = 0;\r
+                         while(*p)\r
+                         {\r
+                              if (*p != 32)\r
+                              {\r
+                                   ok2 = 1;\r
+                                   break;\r
+                              }\r
+                              p++;\r
+                         }\r
+                         if (!ok2)\r
+                              blanks++;\r
+                         else\r
+                              fprintf(fp,"%s\n",buffer.company);\r
+                         p = buffer.address;\r
+                         ok2 = 0;\r
+     \r
+                         while(*p)\r
+                         {\r
+                              if (*p != 32)\r
+                              {\r
+                                   ok2 = 1;\r
+                                   break;\r
+                              }\r
+                              p++;\r
+                         }\r
+                         if (!ok2)\r
+                              blanks++;\r
+                         else\r
+                              fprintf(fp,"%s\n",buffer.address);\r
+                         p = buffer.area;\r
+                         ok2 = 0;\r
+                         while(*p)\r
+                         {\r
+                              if (*p != 32)\r
+                              {\r
+                                   ok2 = 1;\r
+                                   break;\r
+                              }\r
+                              p++;\r
+                         }\r
+                         if (!ok2)\r
+                              blanks++;\r
+                         else\r
+                              fprintf(fp,"%s\n",buffer.area);\r
+                         p = buffer.town;\r
+                         ok2 = 0;\r
+                         while(*p)\r
+                         {\r
+                              if (*p != 32)\r
+                              {\r
+                                   ok2 = 1;\r
+                                   break;\r
+                              }\r
+                              p++;\r
+                         }\r
+                         if (!ok2)\r
+                              blanks++;\r
+                         else\r
+                              fprintf(fp,"%s\n",buffer.town);\r
+                         p = buffer.county;\r
+                         ok2 = 0;\r
+     \r
+                         while(*p)\r
+                         {\r
+                              if (*p != 32)\r
+                              {\r
+                                   ok2 = 1;\r
+                                   break;\r
+                              }\r
+                              p++;\r
+                         }\r
+                         if (!ok2)\r
+                              blanks++;\r
+                         else\r
+                              fprintf(fp,"%s\n",buffer.county);\r
+                         p = buffer.post;\r
+                         ok2 = 0;\r
+                         while(*p)\r
+                         {\r
+                              if (*p != 32)\r
+                              {\r
+                                   ok2 = 1;\r
+                                   break;\r
+                              }\r
+                              p++;\r
+                         }\r
+                         if (!ok2)\r
+                              blanks++;\r
+                         else\r
+                              fprintf(fp,"%s\n",buffer.post);\r
+                         while(blanks)\r
+                         {\r
+                              fprintf(fp,"\n");\r
+                              blanks--;\r
+                         }\r
+                    }\r
+               }\r
+          }\r
+          while(result > 0);\r
+          fclose(fp);\r
+          lseek(handle,0,SEEK_SET);\r
+          read(handle,&rec,recsize);\r
+          DISPDATA();\r
+     }\r
+     \r
+     void EXPORT_MULTI()\r
+     {\r
+          data buffer;\r
+          char destination[60];\r
+          int result;\r
+          int ok;\r
+          FILE *fp;\r
+     \r
+          textcolor(WHITE);\r
+          textbackground(RED);\r
+          gotoxy(left + 23,21);\r
+          cprintf("Enter selection criteria");\r
+     \r
+          /* Clear existing rec details */\r
+          memset(&rec,0,recsize);\r
+     \r
+          DISPDATA();\r
+          GETDATA(0);\r
+     \r
+          textcolor(WHITE);\r
+          textbackground(RED);\r
+          gotoxy(left,21);\r
+          cprintf("Enter export file address.txt");\r
+          strcpy(destination,"address.txt");\r
+          gotoxy(left + 18,21);\r
+          INPUT(destination,59);\r
+          gotoxy(left,21);\r
+          cprintf("%70c",32);\r
+     \r
+          fp = fopen(destination,"w+");\r
+          if (fp == NULL)\r
+          {\r
+               gotoxy(left,21);\r
+               cprintf("Unable to print to %s",destination);\r
+               gotoxy(left,22);\r
+               cprintf("Press RETURN to Continue");\r
+               bioskey(0);\r
+               gotoxy(left,21);\r
+               cprintf("%78c",32);\r
+               gotoxy(left,22);\r
+               cprintf("                          ");\r
+          }\r
+          /* Locate start of file */\r
+          lseek(handle,0,SEEK_SET);\r
+     \r
+          do\r
+          {\r
+               /* Read rec into memory */\r
+               result = read(handle,&buffer,recsize);\r
+               if (result > 0)\r
+               {\r
+                    ok = 1;\r
+                    /* Scan rec for matching data */\r
+                    if (*rec.name)\r
+                         if (stricmp(buffer.name,rec.name))\r
+                              ok = 0;\r
+                    if (*rec.company)\r
+                         if (stricmp(buffer.company,rec.company))\r
+                              ok = 0;\r
+                    if (*rec.address)\r
+                         if (stricmp(buffer.address,rec.address))\r
+                              ok = 0;\r
+                    if (*rec.area)\r
+                         if (stricmp(buffer.area,rec.area))\r
+                              ok = 0;\r
+                    if (*rec.town)\r
+                         if (stricmp(buffer.town,rec.town))\r
+                              ok = 0;\r
+                    if (*rec.county)\r
+                         if (stricmp(buffer.county,rec.county))\r
+                              ok = 0;\r
+                    if (*rec.post)\r
+                         if (stricmp(buffer.post,rec.post))\r
+                         ok = 0;\r
+                    if (*rec.telephone)\r
+                         if (stricmp(buffer.telephone,rec.telephone))\r
+                              ok = 0;\r
+                    if (*rec.fax)\r
+                         if (stricmp(buffer.fax,rec.fax))\r
+                              ok = 0;\r
+                    if (ok)\r
+                    {\r
+                         fprintf(fp,"\"%s\",",buffer.name);\r
+                         fprintf(fp,"\"%s\",",buffer.company);\r
+                         fprintf(fp,"\"%s\",",buffer.address);\r
+                         fprintf(fp,"\"%s\",",buffer.area);\r
+                         fprintf(fp,"\"%s\",",buffer.town);\r
+                         fprintf(fp,"\"%s\",",buffer.county);\r
+                         fprintf(fp,"\"%s\",",buffer.post);\r
+                         fprintf(fp,"\"%s\",",buffer.telephone);\r
+                         fprintf(fp,"\"%s\"\n",buffer.fax);\r
+     \r
+                    }\r
+               }\r
+          }\r
+     \r
+          while(result > 0);\r
+          fclose(fp);\r
+          lseek(handle,0,SEEK_SET);\r
+          read(handle,&rec,recsize);\r
+          DISPDATA();\r
+     }\r
+     \r
+     void MENU()\r
+     {\r
+          int option;\r
+          long result;\r
+          long end;\r
+          int new;\r
+     \r
+          do\r
+          {\r
+               cursor(21,26);\r
+               print("Select option (F2 - F10)");\r
+               cursor(7,52);\r
+               print("F2 Next record");\r
+               cursor(8,52);\r
+               print("F3 Previous record");\r
+               cursor(9,52);\r
+               print("F4 Amend record");\r
+               cursor(10,52);\r
+               print("F5 Add new record");\r
+               cursor(11,52);\r
+               print("F6 Search");\r
+               cursor(12,52);\r
+               print("F7 Continue search");\r
+               cursor(13,52);\r
+               print("F8 Print address labels");\r
+               cursor(14,52);\r
+               print("F9 Export records");\r
+               cursor(15,52);\r
+               print("F10 Exit");\r
+               MOUSE_CURSOR(1);\r
+               option = GETOPT();\r
+               MOUSE_CURSOR(0);\r
+     \r
+               switch(option)\r
+               {\r
+                    case 0 : /* Next rec */\r
+                          result = read(handle,&rec,recsize);\r
+                          if (!result)\r
+                          {\r
+                              lseek(handle,0,SEEK_SET);\r
+                               result = read(handle,&rec,recsize);\r
+                          }\r
+                          DISPDATA();\r
+                          break;\r
+     \r
+                    case 1 : /* Previous rec */\r
+                         result = lseek(handle,0 - recsize * 2,SEEK_CUR);\r
+                         if (result <= -1)\r
+                              lseek(handle,0 - recsize,SEEK_END);\r
+                         result = read(handle,&rec,recsize);\r
+                         DISPDATA();\r
+                         break;\r
+\r
+                    case 3 : /* Add rec */\r
+                          lseek(handle,0,SEEK_END);\r
+                          memset(&rec,0,recsize);\r
+                          DISPDATA();\r
+     \r
+                    case 2 : /* Amend current rec */\r
+                          new = 1;\r
+                          if (*rec.name)\r
+                              new = 0;\r
+                          else\r
+                          if (*rec.company)\r
+                              new = 0;\r
+                          else\r
+                          if (*rec.address)\r
+                              new = 0;\r
+                          else\r
+                          if (*rec.area)\r
+                              new = 0;\r
+                          else\r
+                          if (*rec.town)\r
+                              new = 0;\r
+                          else\r
+                          if (*rec.county)\r
+                              new = 0;\r
+                          else\r
+                          if (*rec.post)\r
+                              new = 0;\r
+                          else\r
+                          if (*rec.telephone)\r
+                              new = 0;\r
+                          else\r
+                          if (*rec.fax)\r
+                              new = 0;\r
+                          result = tell(handle);\r
+                          lseek(handle,0,SEEK_END);\r
+                          end = tell(handle);\r
+     \r
+                          /* Back to original position */\r
+                          lseek(handle,result,SEEK_SET);\r
+     \r
+                          /* If not at end of file, && !new rewind one\r
+rec */\r
+                          if (result != end || ! new)\r
+                              result = lseek(handle,0 -\r
+recsize,SEEK_CUR);\r
+                          result = tell(handle);\r
+                          gotoxy(left + 22,21);\r
+                          print(" Enter address details  ");\r
+                          GETDATA(0);\r
+                          if (*rec.name || *rec.company)\r
+                              result =  write(handle,&rec,recsize);\r
+                          break;\r
+\r
+                    case 4 : /* Search */\r
+                          gotoxy(left + 22,21);\r
+                          print("                           ");\r
+                          SEARCH();\r
+                          break;\r
+     \r
+                    case 5 : /* Continue */\r
+                          gotoxy(left + 22,21);\r
+                          print("                           ");\r
+                          CONTINUE();\r
+                          break;\r
+     \r
+                    case 6 : /* Print */\r
+                          gotoxy(left + 22,21);\r
+                          print("                           ");\r
+                          PRINT_MULTI();\r
+                          break;\r
+\r
+                    case 7 : /* Export */\r
+                          gotoxy(left + 22,21);\r
+                          print("                           ");\r
+                          EXPORT_MULTI();\r
+                          break;\r
+     \r
+                    case 8 : /* Exit */\r
+                          break;\r
+     \r
+                    default: /* Amend current rec */\r
+                          new = 1;\r
+                          if (*rec.name)\r
+                              new = 0;\r
+                          else\r
+                          if (*rec.company)\r
+                              new = 0;\r
+                          else\r
+                          if (*rec.address)\r
+                              new = 0;\r
+                          else\r
+                          if (*rec.area)\r
+                              new = 0;\r
+                          else\r
+                          if (*rec.town)\r
+                              new = 0;\r
+                          else\r
+                          if (*rec.county)\r
+                              new = 0;\r
+                          else\r
+                          if (*rec.post)\r
+                              new = 0;\r
+                          else\r
+                          if (*rec.telephone)\r
+                              new = 0;\r
+                          else\r
+                          if (*rec.fax)\r
+                              new = 0;\r
+                          result = tell(handle);\r
+                          lseek(handle,0,SEEK_END);\r
+                          end = tell(handle);\r
+\r
+                          /* Back to original position */\r
+                          lseek(handle,result,SEEK_SET);\r
+\r
+                          /* If not at end of file, && !new rewind one\r
+rec */\r
+                          if (result != end || ! new)\r
+                              result = lseek(handle,0 -\r
+recsize,SEEK_CUR);\r
+                          result = tell(handle);\r
+                          gotoxy(left + 22,21);\r
+                          print(" Enter address details  ");\r
+                          GETDATA(option - 17);\r
+                          if (*rec.name || *rec.company)\r
+                              result = write(handle,&rec,recsize);\r
+                          option = -1;\r
+                          break;\r
+\r
+               }\r
+          }\r
+     \r
+          while(option != 8);\r
+     }\r
+     \r
+     void exec()\r
+     {\r
+          gettext(1,1,80,25,scr);\r
+          setvideo(3);\r
+          textbackground(WHITE);\r
+          textcolor(BLACK);\r
+          clrscr();\r
+          recsize = sizeof(data);\r
+     \r
+          OPENDATA();\r
+     \r
+          TRUESHADE(left,3,79,5);\r
+          window(left - 2,2 ,78, 4);\r
+          textcolor(YELLOW);\r
+          textbackground(MAGENTA);\r
+          clrscr();\r
+          DBOX(left - 3, 1, 77, 3);\r
+          gotoxy(3,2);\r
+          print("Servile Software             PC ADDRESS BOOK 5.2\r
+     (c) 1994");\r
+     \r
+          TRUESHADE(left,8,left + 43,18);\r
+          window(left - 2,7 , left + 42, 17);\r
+          textcolor(BLACK);\r
+          textbackground(GREEN);\r
+          clrscr();\r
+          DBOX(left - 3, 6, left + 41, 16);\r
+     \r
+          TRUESHADE(left + 48,8,79,18);\r
+          window(left + 46, 7 , 78, 17);\r
+          textbackground(BLUE);\r
+          textcolor(YELLOW);\r
+          clrscr();\r
+          DBOX(left + 45,6,77,16);\r
+     \r
+          TRUESHADE(left ,21,79,24);\r
+          window(left - 2, 20 , 78, 23);\r
+          textbackground(RED);\r
+          textcolor(WHITE);\r
+          clrscr();\r
+          DBOX(left - 3,19,77,22);\r
+     \r
+          window(1,1,80,25);\r
+          textcolor(BLACK);\r
+          textbackground(GREEN);\r
+          DISPDATA();\r
+     \r
+          MENU();\r
+     \r
+          CLOSEDATA();\r
+          puttext(1,1,80,25,scr);\r
+          return;\r
+     }\r
+     \r
+                                    \r
+                       INTERFACING C WITH CLIPPER\r
+\r
+The Clipper programming language is a popular xBase environment for the\r
+PC.  However, it lacks many of the facilities available to programmers of\r
+other languages, and it is quite slow compared to C. Because of this\r
+there are a large number of third party add-on libraries available for\r
+Clipper which provide the facilities lacked.\r
+\r
+As a programmer you probably want to write your own library for Clipper,\r
+or perhaps individual functions to cater for circumstances which Clipper\r
+cannot handle, such as high resolution graphics.\r
+\r
+Throughout this section, Clipper refers to the Summer `87 Clipper\r
+compiler, although initial tests show that the functions described here\r
+work perfectly well with the new Clipper 5 compiler also, we are not in a\r
+position to guarrantee success!\r
+\r
+\r
+COMPILING AND LINKING\r
+The  Clipper extend functions allow user defined functions to be written\r
+in C, linked with and used by the Clipper application. The  first\r
+problem a programmer must address when writing functions in C  to link\r
+with  a  Clipper  application is that of the  C  compiler's  run  time\r
+libraries.\r
+\r
+If one is writing functions with Microsoft C,  then most of the required\r
+run time  library  functions  will be found in the  Clipper.lib  and\r
+Extend.lib libraries which are part of Clipper.\r
+\r
+If,  however, one is using a different C compiler, such as Borland's\r
+Turbo C then the run time library routines must be supplied on the link\r
+line.\r
+\r
+All C functions must be compiled using the large memory model the\r
+following line is used with Microsoft C\r
+\r
+\r
+     cl /c /AL /Zl /Oalt /FPa /Gs <program.c>\r
+\r
+and this compile line may be used with Turbo C\r
+\r
+     tcc -c -ml <program>\r
+\r
+simply substitute <program> for the program name to be compiled.\r
+\r
+Having compiled a C function it must be linked in with the application.\r
+If the C function was compiled with Microsoft C then the link line will\r
+look a little like this;\r
+\r
+\r
+     LINK /SE:500 /NOE program.obj cfunc.obj,,,Clipper Extend\r
+\r
+If  the C function was linked with another C compiler you will also need\r
+to link in the C run time libraries,  for example to link in the Turbo C\r
+large memory mode library use the following link line;\r
+\r
+\r
+     LINK /SE:500 /NOE program.obj cfunc.obj,,,Clipper Extend cl\r
+\r
+If one is using a number of separately compiled C functions it is a good\r
+idea to  collect  them in a library.  If you are using Microsoft C then\r
+you  can simply  create  the  library by using Microsoft Lib.exe with\r
+the  following command line;\r
+\r
+\r
+      LIB mylib +prog1 +prog2, NUL, NUL\r
+\r
+This tells the librarian to add prog1.obj and prog2.obj to a library\r
+called mylib.lib,   creating  it  if it does not exist.  The NUL\r
+parameter  is  for supressing the listing file.\r
+\r
+\r
+If you have been using another C compiler you should copy the C large\r
+memory model run time library before adding your functions to it for\r
+example;\r
+\r
+\r
+      COPY C:\TURBOC\LIB\cl.lib mylib.lib\r
+      LIB mylib +prog1 +prog2, NUL, NUL\r
+\r
+Then when you link your Clipper application you will use a link line\r
+similar to;\r
+\r
+     LINK /SE:500 /NOE myprog,,,Clipper Extend Mylib\r
+\r
+Often  when  linking C functions with  Clipper applications link errors\r
+will occur such as those shown below;\r
+\r
+     \r
+     Microsoft (R) Overlay Linker  Version 3.65\r
+     Copyright (C) Microsoft Corp 1983-1988.  All rights reserved.\r
+     \r
+     \r
+     LINK : error L2029: Unresolved externals:\r
+     \r
+     \r
+     FIWRQQ in file(s):\r
+      M:SLIB.LIB(TEST)\r
+     FIDRQQ in file(s):\r
+      M:SLIB.LIB(TEST)\r
+     \r
+     There were 2 errors detected\r
+     \r
+\r
+Example Link Errors\r
+\r
+The  errors  shown  here  are  `Unresolved externals',   that  is  they\r
+are references to functions which are not found in any of the object\r
+modules  or libraries  specified on the link line.  These occur because\r
+the C  compilers often  scatter  functions and variables through a number\r
+of  libraries.   In tracking  these  functions down  use may be made of\r
+the Microsoft  librarian list file option.  If you run Lib.Exe on the\r
+Turbo C `emu.lib'  library file and specify a listing file as follows;\r
+\r
+\r
+     LIB emu,emu.lst\r
+\r
+The  librarian  will create an ascii file which contains the names  of\r
+each object module contained in the specified library file, and the names\r
+of each function and public variable declared in each object module, as\r
+shown in this listing of Borland's EMU.LIB library.\r
+\r
+\r
+     e086_Entry........EMU086      e086_Shortcut.....EMU086\r
+     e087_Entry........EMU087      e087_Shortcut.....EMU087\r
+     FIARQQ............EMUINIT          FICRQQ............EMUINIT\r
+     FIDRQQ............EMUINIT          FIERQQ............EMUINIT\r
+     FISRQQ............EMUINIT          FIWRQQ............EMUINIT\r
+     FJARQQ............EMUINIT          FJCRQQ............EMUINIT\r
+     FJSRQQ............EMUINIT          __EMURESET........EMUINIT\r
+     \r
+     \r
+     EMUINIT        Offset: 00000010H  Code and data size: 1a2H\r
+     FIARQQ         FICRQQ              FIDRQQ              FIERQQ\r
+     FISRQQ         FIWRQQ              FJARQQ              FJCRQQ\r
+     FJSRQQ         __EMURESET\r
+     \r
+     EMU086         Offset: 00000470H  Code and data size: 2630H\r
+       e086_Entry        e086_Shortcut\r
+     \r
+     EMU087         Offset: 00003200H  Code and data size: 417H\r
+       e087_Entry        e087_Shortcut\r
+     \r
+\r
+\r
+Receiving Parameters\r
+\r
+Clipper provides six different functions for receiving parameters in a C\r
+function. These functions are;\r
+\r
+\r
+  Receive a string      char * _parc(int,[int])\r
+  Receive a Date string char * _pards(int,[int])\r
+  Receive a logical     int _parl(int,[int])\r
+  Receive an integer         int _parni(int,[int])\r
+  Receive a long        long _parnl(int,[int])\r
+  Receive a double      double _parnd(int,[int])\r
+\r
+\r
+\r
+To  illustrate  simple  parameter  receiving in a C  function  I  offer\r
+the following  simple C function which receives two numeric parameters\r
+from  the calling  Clipper program,  and uses these two numeric\r
+parameters to set  the size of the cursor.\r
+\r
+\r
+     #include <nandef.h>            /* Clipper header files */\r
+     #include <extend.h>\r
+     #include <dos.h>                   /* Header file to define REGS */\r
+     \r
+     CLIPPER s_curset()\r
+     {\r
+          /* Demonstration function to set cursor shape */\r
+     \r
+          union REGS inreg,outreg;\r
+     \r
+          inreg.h.ah = 0x01;\r
+          inreg.h.ch = _parni(1);   /* Get integer parameter 1 */\r
+          inreg.h.cl = _parni(2);   /* Get integer parameter 2 */\r
+          int86(0x10,&inreg,&outreg);\r
+          _ret();                        /* Return to Clipper */\r
+     }\r
+\r
+Clipper  provides four more functions for dealing with received\r
+parameters;\r
+\r
+_parclen(int,[int])  which returns the length of a string including\r
+imbedded `\0's,   _parcsiz(int[int])  which returns the length of a\r
+character  string passed by reference from Clipper, _parinfa(int,[int])\r
+which returns the type of  a  specified  array  element  or the length of\r
+an  array,   and  finally _parinfo(int) whic returns the type of a\r
+parameter.\r
+\r
+The following example function uses _parinfa()  to determine both the\r
+length of an array passed from a Clipper program,  and the type of each\r
+element  in the array.  The function then returns to Clipper an integer\r
+representing the number of defined elements in the array.\r
+\r
+\r
+\r
+     #include <nandef.h>\r
+     #include <extend.h>\r
+     \r
+     CLIPPER s_alen()\r
+     {\r
+          int total;\r
+          int n;\r
+          int defined;\r
+          int type;\r
+     \r
+          /* Return the number of defined elements in an array */\r
+          /* From Clipper use defined = s_alen(arr) */\r
+     \r
+          total = _parinfa(1,0); /* Get declared number of elements in\r
+     array */\r
+     \r
+          defined = 0;\r
+     \r
+          for (n = 1; n <= total; n++){\r
+               type = _parinfa(1,n);   /* Get array parameter type */\r
+               if (type)\r
+                    defined++;\r
+          }\r
+          _retni(defined);              /* Return an integer to Clipper\r
+     */\r
+     }\r
+     \r
+\r
+This  function  goes  one step further to return the  mean  average  of\r
+all numeric  values  in an array.  Notice the use of _parnd()  to\r
+retrieve  the numeric  values as doubles.  You may find that because of\r
+the floating point arithmetic  in  this  function  that it will  only\r
+work  if  compiled  with Microsoft C.\r
+\r
+\r
+     #include <nandef.h>\r
+     #include <extend.h>\r
+     \r
+     CLIPPER s_aave()\r
+     {\r
+          int total;\r
+          int defined;\r
+          int n;\r
+          int type;\r
+          double sum;\r
+     \r
+          /* Return the mean average value of numbers in array */\r
+          /* From Clipper use mean = s_aave(arr)\r
+     \r
+     \r
+          total = _parinfa(1,0);               /* Get declared number of\r
+     elements */\r
+     \r
+          defined = 0;\r
+     \r
+          for (n = 1; n <= total; n++){    /* Determine number of defined\r
+     */\r
+               type = _parinfa(1,n);            /* elements */\r
+               if (type == 2)\r
+                    defined++;\r
+          }\r
+     \r
+          sum = 0;\r
+     \r
+          for (n = 1; n <= total; n++){\r
+               type = _parinfa(1,n);\r
+               if (type == 2)                  /* Only sum numeric values\r
+     */\r
+                    sum += _parnd(1,n);\r
+          }\r
+          _retnd(sum / defined);               /* Return a double to\r
+     Clipper */\r
+     }\r
+     \r
+     \r
+\r
+Returning Values\r
+\r
+The  Clipper  manual  lists seven functions for returning  from  a\r
+function written in another language. These return functions for C are as\r
+follows;\r
+\r
+  character        _retc(char *)\r
+  date        _retds(char *)\r
+  logical     _retl(int)\r
+  numeric (int)    _retni(int)\r
+  numeric (long)   _retnl(long)\r
+  numeric  (double)     _retnd(double)\r
+  nothing     _ret(void)\r
+\r
+Omitted  from  the  Clipper manual is the information that  you  may\r
+return different types of value back from a function! For example,  you\r
+may wish to return a character string under normal circumstances,  fine\r
+use _retc().  On error occurences however you can return a logical using\r
+_retl(). The Clipper program  will  assign the received value to the\r
+receiving  variable  in  the correct manner.\r
+\r
+The following simple C function returns a random number.  Notice the use\r
+of integers  which  limits  the range of the function to +-32767.   For\r
+larger values you should use longs instead of integers.\r
+\r
+\r
+     #include <nandef.h>\r
+     #include <extend.h>\r
+     #include <dos.h>\r
+     \r
+     CLIPPER s_random()\r
+     {\r
+          /* Returns a random number between 0 and param1 - 1 */\r
+          /* From Clipper use x = s_random(param1) */\r
+     \r
+          int param1;\r
+          int x;\r
+     \r
+          param1 = _parni(1);\r
+     \r
+          x = rand() % param1;\r
+          _retni(x);\r
+     }\r
+     \r
+\r
+This function receives a string from Clipper,  and passes back an upper\r
+case copy of the string,  leaving the original unchanged.  The maximum\r
+length  of the  string  which can be processed is determined by the size\r
+of  target[], here set to 5000 characters.\r
+\r
+\r
+     #include <nandef.h>\r
+     #include <extend.h>\r
+     \r
+     CLIPPER s_upper()\r
+     {\r
+          /* Returns an upper case copy of string */\r
+          /* From Clipper use ? s_upper("this is a string")\r
+     \r
+          char *p;\r
+          char *q;\r
+          char *string;\r
+          char target[5000];\r
+          int n;\r
+     \r
+          string = _parc(1);\r
+     \r
+          p = string;\r
+          q = target;\r
+     \r
+          while(*string){\r
+               *q++ = toupper(*string);\r
+               string++;\r
+          }\r
+          *q = '\0';\r
+          string = p;\r
+          _retc(target);\r
+     }\r
+     \r
+     \r
+This  function  may be used to change the current DOS directory.  If  it\r
+is successful  it  returns .T.  to the calling Clipper program,\r
+otherwise  it returns .F.\r
+\r
+     #include <nandef.h>\r
+     #include <extend.h>\r
+     #include <dos.h>\r
+     \r
+     CLIPPER s_chdir()\r
+     {\r
+          /* Attempts to change the current DOS directory */\r
+          /* From Clipper use result = s_chdir(path) */\r
+     \r
+          union REGS inreg,outreg;\r
+          struct SREGS segreg;\r
+     \r
+          char *path;\r
+          int x;\r
+     \r
+          path = _parc(1);              /* Retrieve string from Clipper\r
+     */\r
+     \r
+          inreg.h.ah = 0x3b;\r
+          segreg.ds = FP_SEG(path);\r
+          inreg.x.dx = FP_OFF(path);\r
+          intdosx(&inreg,&outreg,&segreg);\r
+     \r
+          x = outreg.x.ax;\r
+     \r
+          if (x == 3)\r
+               _retl(0);    /* Return logical .F. back to Clipper */\r
+          else\r
+               _retl(1);    /* Return logical .T. back to Clipper */\r
+     }\r
+     \r
+\r
+\r
+Avoiding Unresolved Externals\r
+\r
+As we have already seen, a common problem plaguing the programmer\r
+interfacing C functions with Clipper programs is Unresolved Externals.\r
+\r
+The  following  example  C function called s_print()   will  not  link\r
+with Clipper.\r
+\r
+     #include <nandef.h>\r
+     #include <extend.h>\r
+     #include <stdio.h>\r
+     \r
+     CLIPPER s_print()\r
+     {\r
+          char *x;\r
+     \r
+          x = _parc(1);\r
+     \r
+          printf("\nI received %s from Clipper.\n",x);\r
+     \r
+          _ret();\r
+     }\r
+     \r
+\r
+The linker gives you the following reply;\r
+\r
+     Microsoft (R) Overlay Linker  Version 3.65\r
+     Copyright (C) Microsoft Corp 1983-1988.  All rights reserved.\r
+     \r
+     M:SLIB.LIB(IOERROR) : error L2025: __doserrno : symbol defined more\r
+     than once\r
+      pos: 16C6F Record type: 53C6\r
+     \r
+     LINK : error L2029: Unresolved externals:\r
+     \r
+     \r
+\r
+     __RealCvtVector in file(s):\r
+      M:SLIB.LIB(REALCVT)\r
+     _abort in file(s):\r
+      M:SLIB.LIB(CVTFAK)\r
+     \r
+     There were 3 errors detected\r
+     \r
+\r
+The error L2025 `symbol defined more than once' can in this case be\r
+ignored.  However,   the unresolved externals `RealCvtVector'  and\r
+`abort'  cannot  be ignored.  These two functions are referenced by the\r
+function printf()  which has  been  included in the C function.  The\r
+answer is to use as few  of  the compiler's  run time library functions\r
+as possible,  use ROM  calls  instead with INT86() and INTDOSX() etc.\r
+\r
+\r
+Adding High Resolution Graphics To Clipper With C\r
+\r
+The most annoying omission from Clipper, in my opinion, is the lack of\r
+high resolution graphics facilities. The following functions, written in\r
+Turbo C, provide high resolution graphics to Clipper.\r
+\r
+First we require a means to change the video display mode to a high\r
+resolution graphics mode, and back to text mode. The IBM PC BIOS provides\r
+the means for this and can be called from C as follows;\r
+\r
+\r
+\r
+\r
+     /*         Servile Software Library For Clipper              */\r
+     \r
+     #include <nandef.h>\r
+     #include <extend.h>\r
+     #include <dos.h>\r
+     \r
+     CLIPPER s_smode()\r
+     {\r
+          /* Set Video Mode */\r
+          /* From Clipper use s_smode(mode) */\r
+     \r
+          union REGS inreg,outreg;\r
+     \r
+          inreg.h.al = _parni(1);\r
+          inreg.h.ah = 0x00;\r
+          int86 (0x10, &inreg, &outreg);\r
+     \r
+     \r
+     /*  1 40x25 colour text\r
+          2 40x25 bw text\r
+          3 80x25 colour text\r
+          4 320x200 4 colour graphics\r
+          5 320x200 4 colour graphics colour burst off\r
+          6 640x200 2 colour graphics\r
+          etc\r
+     */\r
+          _ret();\r
+     }\r
+\r
+Having set the computer into graphics mode, how about setting pixels to a\r
+specified colour?\r
+\r
+     \r
+     /*         Servile Software Library For Clipper              */\r
+     \r
+     #include <nandef.h>\r
+     #include <extend.h>\r
+     #include <dos.h>\r
+     \r
+     CLIPPER s_plot()\r
+     {\r
+     \r
+          union REGS inreg,outreg;\r
+     \r
+          /* Sets a pixel at the specified coordinates to the specified\r
+     colour. */\r
+     \r
+          inreg.h.bh = 0x00;\r
+          inreg.x.cx = _parni(1);\r
+          inreg.x.dx = _parni(2);\r
+          inreg.h.al = _parni(3);\r
+          inreg.h.ah = 0x0C;\r
+          int86(0x10, &inreg, &outreg);\r
+     }\r
+     \r
+Line drawing and circles are handled by these two functions;\r
+\r
+     \r
+     /*         Servile Software Library For Clipper              */\r
+     \r
+     #include <nandef.h>\r
+     #include <extend.h>\r
+     #include <dos.h>\r
+     \r
+     CLIPPER s_line()\r
+     {\r
+     \r
+          union REGS inreg,outreg;\r
+     \r
+          /* Draws a straight line from (a,b) to (c,d) in colour col */\r
+     \r
+          int a;\r
+          int b;\r
+          int c;\r
+          int d;\r
+          int col;\r
+          int u;\r
+          int v;\r
+          int d1x;\r
+          int d1y;\r
+          int d2x;\r
+          int d2y;\r
+          int m;\r
+          int n;\r
+          int s;\r
+          int i;\r
+     \r
+          a = _parni(1);\r
+          b = _parni(2);\r
+          c = _parni(3);\r
+          d = _parni(4);\r
+          col = _parni(5);\r
+     \r
+          u = c - a;\r
+          v = d - b;\r
+          if (u == 0)\r
+          {\r
+               d1x = 0;\r
+               m = 0;\r
+          }\r
+          else\r
+          {\r
+               m = abs(u);\r
+               if (u < 0)\r
+                    d1x = -1;\r
+               else\r
+                    if (u > 0)\r
+                         d1x = 1;\r
+          }\r
+          if ( v == 0)\r
+          {\r
+               d1y = 0;\r
+               n = 0;\r
+          }\r
+          else\r
+          {\r
+               n = abs(v);\r
+               if (v < 0)\r
+                    d1y = -1;\r
+               else\r
+                    if (v > 0)\r
+                         d1y = 1;\r
+          }\r
+          if (m > n)\r
+          {\r
+               d2x = d1x;\r
+               d2y = 0;\r
+          }\r
+          else\r
+          {\r
+               d2x = 0;\r
+               d2y = d1y;\r
+               m = n;\r
+               n = abs(u);\r
+          }\r
+          s = (m / 2);\r
+     \r
+          inreg.h.al = (unsigned char)col;\r
+          inreg.h.bh = 0x00;\r
+          inreg.h.ah = 0x0C;\r
+          for (i = 0; i <= m; i++)\r
+          {\r
+               inreg.x.cx = (unsigned int)(a);\r
+               inreg.x.dx = (unsigned int)(b);\r
+               int86(0x10, &inreg, &outreg);\r
+               s += n;\r
+               if (s >= m)\r
+               {\r
+                    s -= m;\r
+                    a += d1x;\r
+                    b += d1y;\r
+               }\r
+               else\r
+               {\r
+                    a += d2x;\r
+                    b += d2y;\r
+               }\r
+          }\r
+     }\r
+     \r
+     \r
+\r
+This circle drawing function uses in-line assembler to speed up the\r
+drawing process. It can easily be replaced with inreg and outreg\r
+parameters as in the other functions, or the other functions can be\r
+changed to in-line assembler.  Both methods are shown to illustrate\r
+different ways of achieving the same result.\r
+\r
+\r
+     /*         Servile Software Library For Clipper              */\r
+     \r
+     #include <nandef.h>\r
+     #include <extend.h>\r
+     #include <dos.h>\r
+     \r
+     \r
+     void plot(int x, int y, unsigned char colour)\r
+     {\r
+          asm mov al , colour;\r
+          asm mov bh , 00;\r
+          asm mov cx , x;\r
+          asm mov dx , y;\r
+          asm mov ah , 0Ch;\r
+          asm int 10h;\r
+     }\r
+     \r
+     int getmode()\r
+     {\r
+          /* Returns current video mode  and number of columns in ncols\r
+     */\r
+     \r
+          asm mov ah , 0Fh;\r
+          asm int 10h;\r
+          return(_AL);\r
+     }\r
+     \r
+     \r
+     CLIPPER s_circle()\r
+     {\r
+          int x_centre;\r
+          int y_centre;\r
+          int radius;\r
+          int colour;\r
+          int x,y,delta;\r
+          int startx,endx,x1,starty,endy,y1;\r
+          int asp_ratio;\r
+     \r
+          x_centre = _parni(1);\r
+          y_centre = _parni(2);\r
+          radius = _parni(3);\r
+          colour = _parni(4);\r
+     \r
+     \r
+     \r
+     \r
+          if (getmode() == 6)\r
+               asp_ratio = 22;\r
+          else\r
+               asp_ratio = 13;\r
+     \r
+          y = radius;\r
+          delta = 3 - 2 * radius;\r
+     \r
+          for(x = 0; x < y; )\r
+          {\r
+               starty = y * asp_ratio / 10;\r
+               endy = (y + 1) * asp_ratio / 10;\r
+               startx = x * asp_ratio / 10;\r
+               endx = (x + 1) * asp_ratio / 10;\r
+     \r
+               for(x1 = startx; x1 < endx; ++x1)\r
+               {\r
+                    plot(x1+x_centre,y+y_centre,colour);\r
+                    plot(x1+x_centre,y_centre - y,colour);\r
+                    plot(x_centre - x1,y_centre - y,colour);\r
+                    plot(x_centre - x1,y + y_centre,colour);\r
+               }\r
+     \r
+               for(y1 = starty; y1 < endy; ++y1)\r
+               {\r
+                    plot(y1+x_centre,x+y_centre,colour);\r
+                    plot(y1+x_centre,y_centre - x,colour);\r
+                    plot(x_centre - y1,y_centre - x,colour);\r
+                    plot(x_centre - y1,x + y_centre,colour);\r
+               }\r
+     \r
+               if (delta < 0)\r
+                    delta += 4 * x + 6;\r
+               else\r
+               {\r
+                    delta += 4*(x-y)+10;\r
+                    y--;\r
+               }\r
+               x++;\r
+          }\r
+     \r
+     \r
+     \r
+     \r
+          if(y)\r
+          {\r
+               starty = y * asp_ratio / 10;\r
+               endy = (y + 1) * asp_ratio / 10;\r
+               startx = x * asp_ratio / 10;\r
+               endx = (x + 1) * asp_ratio / 10;\r
+               for(x1 = startx; x1 < endx; ++x1)\r
+               {\r
+                    plot(x1+x_centre,y+y_centre,colour);\r
+                    plot(x1+x_centre,y_centre - y,colour);\r
+                    plot(x_centre - x1,y_centre - y,colour);\r
+                    plot(x_centre - x1,y + y_centre,colour);\r
+               }\r
+     \r
+               for(y1 = starty; y1 < endy; ++y1)\r
+               {\r
+                    plot(y1+x_centre,x+y_centre,colour);\r
+                    plot(y1+x_centre,y_centre - x,colour);\r
+                    plot(x_centre - y1,y_centre - x,colour);\r
+                    plot(x_centre - y1,x + y_centre,colour);\r
+               }\r
+          }\r
+     }\r
+     \r
+\r
+The Clipper facilities for displaying text on the screen, @....SAY and ?\r
+do not work when the monitor is in graphics mode. You then need the\r
+following function to allow text to be displayed in a graphics mode;\r
+\r
+     \r
+     /*         Servile Software Library For Clipper              */\r
+     \r
+     #include <nandef.h>\r
+     #include <extend.h>\r
+     #include <dos.h>\r
+     \r
+     int sgetmode(int *ncols)\r
+     {\r
+          /* Returns current video mode  and number of columns in ncols\r
+     */\r
+     \r
+          union REGS inreg,outreg;\r
+     \r
+          inreg.h.ah = 0x0F;\r
+          int86(0x10, &inreg, &outreg);\r
+          *ncols = outreg.h.ah;\r
+          return(outreg.h.al);\r
+     }\r
+     \r
+     void at(int row, int col)\r
+     {\r
+          asm mov bh , 0;\r
+          asm mov dh , row;\r
+          asm mov dl , col;\r
+          asm mov ah , 02h;\r
+          asm int 10h;\r
+     }\r
+     \r
+     \r
+     \r
+     \r
+     CLIPPER s_say()\r
+     {\r
+          char *output;\r
+          int p = 0;\r
+          unsigned char page;\r
+          unsigned char text;\r
+          int n;\r
+          int r;\r
+          int c;\r
+          int attribute;\r
+     \r
+          output = _parc(1);\r
+          r = _parni(2);\r
+          c = _parni(3);\r
+          attribute = _parni(4);\r
+     \r
+          asm mov ah , 0Fh;\r
+          asm int 10h;\r
+          asm mov page, bh;\r
+     \r
+          sgetmode(&n);\r
+     \r
+          at(r,c);\r
+     \r
+          while (output[p])\r
+          {\r
+               text = output[p++];\r
+               asm mov bh , page;\r
+               asm mov bl , attribute;\r
+               asm mov cx , 01h;\r
+               asm mov ah , 09h;\r
+               asm mov al , text;\r
+               asm int 10h;\r
+               c++;\r
+               if (c < (n-1))\r
+                    at( r, c);\r
+               else\r
+               {\r
+                    c = 0;\r
+                    at(++r,0);\r
+               }\r
+          }\r
+     }\r
+     \r
+\r
+\r
+\r
+When drawing graphs, it is often required to fill in areas of the graph\r
+in different patterns. This is a graphics function to fill boundered\r
+shapes with a specified hatching pattern providing a means to achieve\r
+more usable graphs;\r
+\r
+\r
+     \r
+     /*         Servile Software Library For Clipper              */\r
+     \r
+     #include <nandef.h>\r
+     #include <extend.h>\r
+     #include <dos.h>\r
+     \r
+     int pixset(int x, int y)\r
+     {\r
+          /* Returns the colour of the specified pixel */\r
+     \r
+          asm mov cx ,x;\r
+          asm mov dx ,y;\r
+          asm mov ah ,0Dh;\r
+          asm int 10h;\r
+          return(_AL);\r
+     }\r
+     \r
+     CLIPPER s_fill()\r
+     {\r
+          /* Fill a boundered shape using a hatch pattern */\r
+     \r
+          int mode;\r
+          int xa;\r
+          int ya;\r
+          int bn;\r
+          int byn;\r
+          int x;\r
+          int y;\r
+          int col;\r
+          int pattern;\r
+          int maxx;\r
+          int maxy;\r
+          int hatch[10][8] = { 255,255,255,255,255,255,255,255,\r
+                                   128,64,32,16,8,4,2,1,\r
+                                   1,2,4,8,16,32,64,128,\r
+                                   1,2,4,8,8,4,2,1,\r
+                                   238,238,238,238,238,238,238,238,\r
+                                   170,85,170,85,170,85,170,85,\r
+                                   192,96,48,24,12,6,3,1,\r
+                                   62,62,62,0,227,227,227,0,\r
+                                   129,66,36,24,24,36,66,129,\r
+                                   146,36,146,36,146,36,146,36};\r
+     \r
+          /* Patterns for fill, each integer describes a row of dots */\r
+     \r
+     \r
+     \r
+     \r
+          x = _parni(1);\r
+          y = _parni(2);\r
+          col = _parni(3);\r
+          pattern = _parni(4);\r
+     \r
+          mode = getmode();\r
+     \r
+          switch(mode)\r
+          {\r
+               case 0:\r
+               case 1:\r
+               case 2:\r
+               case 3: break;\r
+               case 4:\r
+               case 9:\r
+               case 13:\r
+               case 19:\r
+               case 5: maxx = 320;\r
+                         maxy = 200;\r
+                         break;\r
+               case 14:\r
+               case 10:\r
+               case 6: maxx = 640;\r
+                         maxy = 200;\r
+                         break;\r
+               case 7: maxx = 720;\r
+                         maxy = 400;\r
+                         break;\r
+               case 8: maxx = 160;\r
+                         maxy = 200;\r
+                         break;\r
+               case 15:\r
+               case 16: maxx = 640;\r
+                          maxy = 350;\r
+                          break;\r
+               case 17:\r
+               case 18: maxx = 640;\r
+                          maxy = 480;\r
+                          break;\r
+     \r
+          }\r
+     \r
+          xa = x;\r
+          ya = y;  /* Save Origin */\r
+     \r
+          if(pixset(x,y))\r
+               return;\r
+     \r
+          bn = 1;\r
+          byn = 0;\r
+     \r
+     \r
+     \r
+     \r
+          do\r
+          {\r
+               if (hatch[pattern][byn] != 0)\r
+               {  /* If blank ignore */\r
+                    do\r
+                    {\r
+                         if ((bn & hatch[pattern][byn]) == bn)\r
+                         {\r
+                              asm mov al , col;\r
+                              asm mov bh , 00;\r
+                              asm mov cx , x;\r
+                              asm mov dx , y;\r
+                              asm mov ah , 0Ch;\r
+                              asm int 10h;\r
+                         }\r
+                         x--;\r
+                         bn <<= 1;\r
+                         if (bn > 128)\r
+                              bn = 1;\r
+                    }\r
+                    while(!pixset(x,y) && (x > -1));\r
+     \r
+                    x = xa + 1;\r
+                    bn = 128;\r
+     \r
+                    do\r
+                    {\r
+                         if ((bn & hatch[pattern][byn]) == bn)\r
+                         {\r
+                              asm mov al , col;\r
+                              asm mov bh , 00;\r
+                              asm mov cx , x;\r
+                              asm mov dx , y;\r
+                              asm mov ah , 0Ch;\r
+                              asm int 10h;\r
+                         }\r
+                         x++;\r
+                         bn >>=1;\r
+                         if (bn <1)\r
+                              bn = 128;\r
+                    }\r
+                    while((!pixset(x,y)) && (x <= maxx));\r
+               }\r
+               x = xa;\r
+               y--;\r
+               bn = 1;\r
+               byn++;\r
+               if (byn > 7)\r
+                    byn = 0;\r
+     \r
+     \r
+     \r
+     \r
+          }\r
+          while(!pixset(x,y) && ( y > -1));\r
+     \r
+          /* Now travel downwards */\r
+     \r
+          y = ya + 1;\r
+     \r
+          byn = 7;\r
+          bn = 1;\r
+          do\r
+          {\r
+               /* Travel left */\r
+               if (hatch[pattern][byn] !=0)\r
+               {\r
+                    do\r
+                    {\r
+                         if ((bn & hatch[pattern][byn]) == bn)\r
+                         {\r
+                              asm mov al , col;\r
+                              asm mov bh , 00;\r
+                              asm mov cx , x;\r
+                              asm mov dx , y;\r
+                              asm mov ah , 0Ch;\r
+                              asm int 10h;\r
+                         }\r
+                         x--;\r
+                         bn <<= 1;\r
+                         if (bn > 128)\r
+                              bn = 1;\r
+                    }\r
+                    while(!pixset(x,y) && (x > -1));\r
+     \r
+                    /* Back to x origin */\r
+                    x = xa + 1 ;\r
+                    bn = 128;\r
+     \r
+                    /* Travel right */\r
+                    do\r
+                    {\r
+                         if ((bn & hatch[pattern][byn]) == bn)\r
+                         {\r
+                              asm mov al , col;\r
+                              asm mov bh , 00;\r
+                              asm mov cx , x;\r
+                              asm mov dx , y;\r
+                              asm mov ah , 0Ch;\r
+                              asm int 10h;\r
+                         }\r
+                         x++;\r
+                         bn >>=1;\r
+     \r
+     \r
+     \r
+     \r
+                         if (bn <1)\r
+                              bn = 128;\r
+                    }\r
+                    while((!pixset(x,y)) && (x <= maxx));\r
+               }\r
+               x = xa;\r
+               bn = 1;\r
+               y++;\r
+               byn--;\r
+               if (byn < 0)\r
+                    byn = 7;\r
+          }\r
+          while((!pixset(x,y)) && (y <= maxy));\r
+     }\r
+     \r
+     \r
+     \r
+                                    \r
+                       SPELL - AN EXAMPLE PROGRAM\r
+\r
+It has been said that example programs provide a good way of learning a\r
+new computer language. On that basis the following simple program is\r
+offered as an example of making use of the dynamic memory allocation\r
+provided by DOS.\r
+\r
+This is a spell checker for ASCII (text) documents, written in, and\r
+making use of Borland's Turbo C text graphics facilities for displaying\r
+windows of text.\r
+\r
+\r
+     /* Spell checker for ascii documents */\r
+     /* Compile with -mc (compact memory model) and unsigned characters\r
+     */\r
+     \r
+     \r
+     #include <stdio.h>\r
+     #include <conio.h>\r
+     #include <fcntl.h>\r
+     #include <io.h>\r
+     #include <dos.h>\r
+     #include <string.h>\r
+     #include <alloc.h>\r
+     #include <ctype.h>\r
+     #include <stdlib.h>\r
+     #include <bios.h>\r
+     #include <dir.h>\r
+     #include <stat.h>\r
+     \r
+     #define  ON           0x06\r
+     #define  OFF         0x20\r
+     #define  MAXIMUM     15000\r
+     #define  WORDLEN     20\r
+     #define  LEFTMARGIN  1\r
+     \r
+     union REGS inreg,outreg;\r
+     \r
+     char *dicname;\r
+     char *dic[MAXIMUM];      /* Array of text lines */\r
+     char word[31];\r
+     char comp[31];\r
+     char fname[160];\r
+     int lastelem;\r
+     char changed;\r
+     char *ignore[100];\r
+     int lastign;\r
+     int insert;\r
+     int n;\r
+     int bp;\r
+     int mp;\r
+     int tp;\r
+     int result;\r
+     \r
+     \r
+     \r
+     \r
+     char *text;\r
+     char *textsav;\r
+     \r
+     void AT(int, int);\r
+     void BANNER(void);\r
+     int COMPARE(void);\r
+     void CORRECT(void);\r
+     void FATAL(char *);\r
+     void FILERR(char *);\r
+     void GETDIC(void);\r
+     void IGNORE(void);\r
+     void INSERT(void);\r
+     int MATCHSTR(char *, char *);\r
+     void SPELL(void);\r
+     void UPDATE(void);\r
+     \r
+     void CURSOR(char status)\r
+     {\r
+          /* Toggle cursor display on and off */\r
+     \r
+          union REGS inreg,outreg;\r
+     \r
+          inreg.h.ah = 1;\r
+          inreg.h.ch = (unsigned char)status;\r
+          inreg.h.cl = 7;\r
+          int86(0x10,&inreg,&outreg);\r
+     }\r
+     \r
+     void DISPLAY(char *text)\r
+     {\r
+          /* Display 'text' expanding tabs and newline characters */\r
+     \r
+          while(*text)\r
+          {\r
+               switch(*text)\r
+               {\r
+                    case '\n':  cputs("\r\n");\r
+                                   break;\r
+                    case '\t':  cputs("      ");\r
+                                   break;\r
+                    default:  putch(*text);\r
+               }\r
+               text++;\r
+          }\r
+     }\r
+     \r
+     \r
+     \r
+     \r
+     void GETDIC()\r
+     {\r
+          /* Read dictionary into memory */\r
+     \r
+          FILE *fp;\r
+          char *p;\r
+          int poscr;\r
+          int handle;\r
+     \r
+          window(1,22,80,24);\r
+          clrscr();\r
+          gotoxy(28,2);\r
+          cprintf("Reading Dictionary....");\r
+     \r
+          changed = 0;\r
+          lastelem = 0;\r
+     \r
+          dicname = searchpath("spell.dic");\r
+          handle = open(dicname,O_RDWR);\r
+          if (handle < 0)\r
+               FILERR("spell.dic");\r
+     \r
+          fp = fdopen(handle,"r");\r
+          if (fp == NULL)\r
+               FILERR("spell.dic");\r
+     \r
+          do\r
+          {\r
+               dic[lastelem] = calloc(WORDLEN,1);\r
+               if (dic[lastelem])\r
+               {\r
+                    p = fgets(dic[lastelem],79,fp);\r
+                    /* Remove carriage return from end of text line */\r
+                    poscr = (int)strlen(dic[lastelem]) - 1;\r
+                    if (dic[lastelem][poscr] == '\n')\r
+                         dic[lastelem][poscr] = 0;\r
+               }\r
+               else\r
+                    FATAL("Unable To Allocate Memory");\r
+          }\r
+          while((p != NULL) && (lastelem++ < MAXIMUM));\r
+     \r
+          lastelem--;\r
+     \r
+          fclose(fp);\r
+     }\r
+     \r
+     \r
+     \r
+     \r
+     void UPDATE()\r
+     {\r
+          FILE *fp;\r
+          int n;\r
+     \r
+          if (changed)\r
+          {\r
+               window(1,22,80,24);\r
+               clrscr();\r
+               gotoxy(27,2);\r
+               cprintf("Updating Dictionary....");\r
+     \r
+               fp = fopen(dicname,"w+");\r
+               if (fp == NULL)\r
+                    FILERR("spell.dic");\r
+     \r
+               for(n = 0; n <= lastelem; n++)\r
+                    fprintf(fp,"%s\n",dic[n]);\r
+     \r
+               fclose(fp);\r
+          }\r
+     }\r
+     \r
+     void IGNORE()\r
+     {\r
+          /* Add a word to the ignore table */\r
+     \r
+          if (lastign < 100)\r
+          {\r
+               ignore[lastign] = calloc(strlen(word) + 1,1);\r
+               if (ignore[lastign])\r
+                    strcpy(ignore[lastign++],comp);\r
+               else\r
+               {\r
+                    clrscr();\r
+                    cprintf("No available memory for new words!\r\nPress\r
+     A key....");\r
+                    bioskey(0);\r
+               }\r
+           }\r
+           else\r
+           {\r
+               clrscr();\r
+               cprintf("No available memory for new words!\r\nPress A\r
+     key....");\r
+               bioskey(0);\r
+           }\r
+     }\r
+     \r
+     \r
+     \r
+     \r
+     void FATAL(char *text)\r
+     {\r
+          /* Fatal error drop out */\r
+     \r
+          textcolor(LIGHTGRAY);\r
+          textbackground(BLACK);\r
+          window(1,1,80,25);\r
+          clrscr();\r
+          printf("SERVILE SOFTWARE\n\nSPELL V1.7\nFATAL ERROR:\r
+     %s\n\n",text);\r
+          CURSOR(ON);\r
+          exit(0);\r
+     }\r
+     \r
+     void FILERR(char *fname)\r
+     {\r
+          char text[60];\r
+     \r
+          strcpy(text,"Unable To Access: ");\r
+          strcat(text,fname);\r
+          FATAL(text);\r
+     }\r
+     \r
+     int COMPARE()\r
+     {\r
+          char **p;\r
+     \r
+          /* Check Ignore table */\r
+          for(p = ignore; p <= &ignore[lastign]; p++)\r
+               if (strcmp(comp,*p) == 0)\r
+                    return(1);\r
+     \r
+          /* Binary search of dictionary file */\r
+          bp = 0;\r
+          tp = lastelem;\r
+          mp = (tp + bp) / 2;\r
+     \r
+     \r
+     \r
+     \r
+          while((result = strcmp(dic[mp],comp)) != 0)\r
+          {\r
+               if (mp >= tp)\r
+               {\r
+                    /* Not found! */\r
+                    insert = mp;\r
+                    if (result > 0)\r
+                         insert--;\r
+                    return(0);\r
+               }\r
+               if (result < 0)\r
+                    bp = mp + 1;\r
+               else\r
+                    tp = mp - 1;\r
+     \r
+               mp = (bp + tp) / 2;\r
+          }\r
+          return(1);\r
+     }\r
+     \r
+     void INSERT()\r
+     {\r
+          int n;\r
+     \r
+          changed = 1;\r
+          lastelem++;\r
+          n = lastelem;\r
+     \r
+          dic[n] = calloc(WORDLEN,1);\r
+     \r
+          if (dic[n] == NULL)\r
+          {\r
+               clrscr();\r
+               cprintf("No available memory for new words!\r\nPress A\r
+     key....");\r
+               bioskey(0);\r
+               free(dic[n]);\r
+               lastelem--;\r
+               return;\r
+          }\r
+     \r
+          while(n > (insert + 1))\r
+          {\r
+               strcpy(dic[n],dic[n-1]);\r
+               n--;\r
+          };\r
+     \r
+          strcpy(dic[insert + 1],comp);\r
+     }\r
+     \r
+     \r
+     \r
+     \r
+     void SPELL()\r
+     {\r
+          FILE *target;\r
+          FILE *source;\r
+          char *p;\r
+          char *x;\r
+          char temp[256];\r
+          char dat1[1250];\r
+          char dat2[1250];\r
+          int c;\r
+          int m;\r
+          int found;\r
+          int curpos;\r
+          int key;\r
+          int row;\r
+          int col;\r
+          int srow;\r
+          int scol;\r
+     \r
+          window(1,1,80,20);\r
+          textcolor(BLACK);\r
+          textbackground(WHITE);\r
+     \r
+          /* Open temporary file to take spell checked copy */\r
+          target = fopen("spell.$$$","w+");\r
+     \r
+          source = fopen(fname,"r");\r
+     \r
+          if (source == NULL)\r
+               FILERR(fname);\r
+     \r
+          lastign = 0;\r
+     \r
+          do\r
+          {\r
+               clrscr();\r
+     \r
+               text = dat1;\r
+     \r
+               p = text;\r
+     \r
+               textsav = dat2;\r
+     \r
+               strcpy(text,"");\r
+     \r
+               /* Display read text */\r
+               row = wherey();\r
+               col = wherex();\r
+     \r
+     \r
+     \r
+     \r
+               for(m = 0; m < 15; m++)\r
+               {\r
+                    x = fgets(temp,200,source);\r
+                    if (x)\r
+                    {\r
+                         strcat(text,temp);\r
+                         DISPLAY(temp);\r
+                    }\r
+                    if (wherey() > 18)\r
+                         break;\r
+               }\r
+     \r
+               /* return cursor to start position */\r
+               gotoxy(col,row);\r
+     \r
+               do\r
+               {\r
+                    memset(word,32,30);\r
+                    curpos = 0;\r
+                    do\r
+                    {\r
+                         c = *text++;\r
+                         if ((isalpha(c)) || (c == '-') && (curpos != 0))\r
+                              word[curpos++] = c;\r
+                    }\r
+                    while(((isalpha(c)) || (c == '-') && (curpos != 0))\r
+                            && (curpos < 30));\r
+                    word[curpos] = 0;\r
+                    strcpy(comp,word);\r
+                    strupr(comp);\r
+     \r
+                    if (*comp != 0)\r
+                    {\r
+                         found = COMPARE();\r
+                         if (!found){\r
+                              textbackground(RED);\r
+                              textcolor(WHITE);\r
+                         }\r
+                    }\r
+                    else\r
+                         found = 1;\r
+     \r
+                    srow = wherey();\r
+                    scol = wherex();\r
+     \r
+                    cputs(word);\r
+                    textbackground(WHITE);\r
+                    textcolor(BLACK);\r
+     \r
+     \r
+     \r
+     \r
+     \r
+                    switch(c)\r
+                    {\r
+                         case '\n': cputs("\r\n");\r
+                                      break;\r
+                         case '\t': cputs("       ");\r
+                                      break;\r
+                         default: putch(c);\r
+                    }\r
+     \r
+                    row = wherey();\r
+                    col = wherex();\r
+     \r
+                    if (!found)\r
+                    {\r
+                         window(1,22,80,24);\r
+                         clrscr();\r
+                         cputs("Unknown word ");\r
+                         textcolor(BLUE);\r
+                         cprintf("%s ",word);\r
+                         textcolor(BLACK);\r
+                         cputs("[A]dd  [I]gnore  [C]orrect  [S]kip");\r
+                         do\r
+                         {\r
+                              key = toupper(getch());\r
+                              if (key == 27)\r
+                                   key = 'Q';\r
+                         }\r
+                         while(strchr("AICSQ",key) == NULL);\r
+     \r
+                         switch(key)\r
+                         {\r
+                              case 'A':INSERT();\r
+                                         break;\r
+     \r
+                              case 'C':CORRECT();\r
+                                         break;\r
+     \r
+                              case 'I':IGNORE();\r
+                                         break;\r
+                         }\r
+     \r
+     \r
+     \r
+     \r
+                         if (key == 'C')\r
+                         {\r
+                              clrscr();\r
+                              gotoxy(1,1);\r
+                              strcpy(textsav,--text);\r
+                              /* Delete old word */\r
+                              text -= strlen(comp);\r
+                              *text = 0;\r
+                              /* Insert new word */\r
+                              strcat(text,word);\r
+                              /* Append remainder of text */\r
+                              strcat(text,textsav);\r
+                              text += strlen(word);\r
+                              text++;\r
+                              /* Length of text may have changed ! */\r
+                              if (strlen(word) < strlen(comp))\r
+                                   col -= (strlen(comp) - strlen(word));\r
+                              window(1,1,80,20);\r
+                              clrscr();\r
+                              DISPLAY(p);\r
+                         }\r
+                         else\r
+                         {\r
+                              clrscr();\r
+                              gotoxy(29,2);\r
+                              cputs("Checking Spelling....");\r
+                              window(1,1,80,20);\r
+                              gotoxy(scol,srow);\r
+                              cputs(word);\r
+                         }\r
+                         window(1,1,80,20);\r
+                         gotoxy(col,row);\r
+                    }\r
+               }\r
+               while((*text) && (key != 'Q'));\r
+               fprintf(target,"%s",p);\r
+          }\r
+          while((x != NULL) && (key != 'Q'));\r
+     \r
+          window(1,22,80,24);\r
+          clrscr();\r
+          gotoxy(27,2);\r
+          cprintf("Writing Updated File....");\r
+     \r
+     \r
+     \r
+     \r
+          do\r
+          {\r
+               p = fgets(temp,200,source);\r
+               if (p)\r
+                    fprintf(target,"%s",temp);\r
+          }\r
+          while(p);\r
+     \r
+          fclose(target);\r
+          fclose(source);\r
+     \r
+          /* Now transfer spell.$$$ to fname */\r
+          unlink(fname);\r
+          rename("SPELL.$$$",fname);\r
+     }\r
+     \r
+     void CORRECT()\r
+     {\r
+          /* Locate a good match and return word */\r
+     \r
+          char text[51];\r
+          int m;\r
+          int n;\r
+          int key;\r
+     \r
+          window(1,22,80,24);\r
+          clrscr();\r
+          gotoxy(25,2);\r
+          cprintf("Searching For Alternatives....");\r
+     \r
+          /* Remove any pending key strokes from keyboard buffer */\r
+          while(kbhit())\r
+               getch();\r
+     \r
+          for(n = 0; n <= lastelem; n++)\r
+          {\r
+               if (MATCHSTR(dic[n],comp))\r
+               {\r
+                    strcpy(text,dic[n]);\r
+                    if (strlen(word) <= strlen(text))\r
+                    {\r
+                         for (m = 0; m < strlen(word); m++)\r
+                         {\r
+                              if (isupper(word[m]))\r
+                                   text[m] = toupper(text[m]);\r
+                              else\r
+                                   text[m] = tolower(text[m]);\r
+                         }\r
+     \r
+     \r
+     \r
+     \r
+                         for(m = strlen(word); m < strlen(text); m++)\r
+                              if (isupper(word[strlen(word)]))\r
+                                   text[m] = toupper(text[m]);\r
+                              else\r
+                                   text[m] = tolower(text[m]);\r
+                    }\r
+                    else\r
+                    {\r
+                         for (m = 0; m < strlen(text); m++)\r
+                         {\r
+                              if (isupper(word[m]))\r
+                                   text[m] = toupper(text[m]);\r
+                              else\r
+                                   text[m] = tolower(text[m]);\r
+                         }\r
+                    }\r
+                    clrscr();\r
+                    cprintf("Replace ");\r
+                    textcolor(BLUE);\r
+                    cprintf("%s ",word);\r
+                    textcolor(BLACK);\r
+                    cprintf("With ");\r
+                    textcolor(BLUE);\r
+                    cprintf("%s",text);\r
+                    textcolor(BLACK);\r
+                    cprintf(" Yes No Continue");\r
+                    do\r
+                    {\r
+                         key = toupper(getch());\r
+                    }\r
+                    while(strchr("YNC",key) == NULL);\r
+                    if (key == 'Y')\r
+                    {\r
+                         strcpy(word,text);\r
+                         return;\r
+                    }\r
+                    clrscr();\r
+                    gotoxy(25,2);\r
+                    cprintf("Searching For Alternatives....");\r
+     \r
+                    /* Remove any pending key strokes from keyboard\r
+     buffer */\r
+                    while(kbhit())\r
+                         getch();\r
+     \r
+                    if (key == 'C')\r
+                         return;\r
+               }\r
+          }\r
+          clrscr();\r
+          gotoxy(23,2);\r
+     \r
+     \r
+     \r
+     \r
+          cprintf("NO ALTERNATIVES FOUND! (Press a key)");\r
+          bioskey(0);\r
+          return;\r
+     }\r
+     \r
+     \r
+     int MATCHSTR(char *src, char *tgt)\r
+     {\r
+          /* Compare two words and return non zero if they are similar */\r
+     \r
+          int match;\r
+          int result;\r
+          int strsrc;\r
+          int strtgt;\r
+          int longest;\r
+     \r
+          strtgt = strlen(strupr(tgt));\r
+          strsrc = strlen(strupr(src));\r
+     \r
+          longest = max(strtgt,strsrc);\r
+     \r
+          match = 0;\r
+     \r
+          if(strtgt > strsrc)\r
+          {\r
+               for(; *src ; match += (*src++ == *tgt++))\r
+                    ;\r
+          }\r
+          else\r
+          {\r
+               for(; *tgt ; match += (*src++ == *tgt++))\r
+                    ;\r
+          }\r
+     \r
+          result = (match * 100 / longest);\r
+     \r
+          /* result holds percentage similarity */\r
+     \r
+          if (result > 50)\r
+               return(1);\r
+          return(0);\r
+     }\r
+     \r
+     void AT(int row, int col)\r
+     {\r
+          /* Position the text cursor */\r
+          inreg.h.bh = 0;\r
+          inreg.h.dh = row;\r
+          inreg.h.dl = col;\r
+          inreg.h.ah = 0x02;\r
+          int86 (0x10, &inreg, &outreg);\r
+     }\r
+     \r
+     void WRTCHA (unsigned char ch, unsigned char attrib,  int num)\r
+     {\r
+          /* Display a character num times in colour attrib */\r
+          /* via the BIOS */\r
+     \r
+          inreg.h.al = ch;\r
+          inreg.h.bh = 0;\r
+          inreg.h.bl = attrib;\r
+          inreg.x.cx = num;\r
+          inreg.h.ah = 0x09;\r
+          int86 (0x10, &inreg, &outreg);\r
+     }\r
+     \r
+     void SHADE_BLOCK(int left,int top,int right,int bottom)\r
+     {\r
+          int c;\r
+     \r
+          AT(bottom,right);\r
+          WRTCHA(223,56,1);\r
+          AT(top,right);\r
+          WRTCHA('á',7,1);\r
+          for (c = top+1; c < bottom; c++)\r
+          {\r
+               AT(c,right);\r
+               WRTCHA(' ',7,1);\r
+          }\r
+          AT(bottom,left+1);\r
+          WRTCHA('\9a',7,right-left);\r
+     }\r
+     \r
+     \r
+     \r
+     \r
+     void BOX(int l, int t, int r, int b)\r
+     {\r
+          /* Draws a single line box around a described area */\r
+     \r
+          int n;\r
+          char top[81];\r
+          char bottom[81];\r
+          char tolc[5];\r
+          char torc[5];\r
+          char bolc[5];\r
+          char borc[5];\r
+          char hoor[5];\r
+     \r
+          sprintf(tolc,"%c",218);\r
+          sprintf(bolc,"%c",192);\r
+          sprintf(hoor,"%c",196);\r
+          sprintf(torc,"%c",191);\r
+          sprintf(borc,"%c",217);\r
+     \r
+               strcpy(top,tolc);\r
+          strcpy(bottom,bolc);\r
+          for(n = l + 1; n < r; n++)\r
+          {\r
+               strcat(top,hoor);\r
+               strcat(bottom,hoor);\r
+          }\r
+          strcat(top,torc);\r
+          strcat(bottom,borc);\r
+     \r
+          window(1,1,80,25);\r
+               gotoxy(l,t);\r
+          cputs(top);\r
+          for (n = t + 1; n < b; n++)\r
+          {\r
+               gotoxy(l,n);\r
+               putch(179);\r
+               gotoxy(r,n);\r
+               putch(179);\r
+          }\r
+          gotoxy(l,b);\r
+          cputs(bottom);\r
+     }\r
+     \r
+     \r
+     \r
+     \r
+     void BANNER()\r
+     {\r
+          window (2,2,78,4);\r
+          textcolor(BLACK);\r
+          textbackground(GREEN);\r
+          clrscr();\r
+          SHADE_BLOCK(1,1,78,4);\r
+          BOX(2,2,78,4);\r
+          gotoxy(4,3);\r
+          cprintf("Servile Software                SPELL CHECKER V1.7\r
+                     (c)1992");\r
+     }\r
+     \r
+     \r
+     void main(int argc, char *argv[])\r
+     {\r
+          char *p;\r
+          char tmp_name[160];\r
+          char tmp_fname[160];\r
+     \r
+          if (argc != 2)\r
+          {\r
+               puts("\nERROR: Usage is SPELL document");\r
+               exit(1);\r
+          }\r
+          else\r
+               strcpy(fname,argv[1]);\r
+     \r
+          CURSOR(OFF);\r
+     \r
+          GETDIC();\r
+     \r
+          window(1,22,80,24);\r
+          clrscr();\r
+          gotoxy(28,2);\r
+          cprintf("Making Backup File....");\r
+     \r
+          strcpy(tmp_fname,argv[1]);\r
+     \r
+          /* Remove extension from tmp_fname */\r
+          p = strchr(tmp_fname,'.');\r
+          if(p)\r
+               *p = 0;\r
+     \r
+          /* Create backup file name using DOS */\r
+          sprintf(tmp_name,"copy %s %s.!s! > NUL",argv[1],tmp_fname);\r
+     \r
+          system(tmp_name);\r
+     \r
+          window(1,1,80,25);\r
+     \r
+     \r
+     \r
+     \r
+          textcolor(WHITE);\r
+          textbackground(BLACK);\r
+          clrscr();\r
+          gotoxy(29,2);\r
+          cprintf("Checking Spelling....");\r
+     \r
+          SPELL();\r
+     \r
+          UPDATE();\r
+          window(1,1,80,25);\r
+          textcolor(LIGHTGRAY);\r
+          textbackground(BLACK);\r
+          clrscr();\r
+          CURSOR(ON);\r
+     }\r
+     \r
+                                    \r
+                         APPENDIX A - USING LINK\r
+\r
+\r
+\r
+General Syntax:\r
+\r
+     LINK [options] obj[,[exe][,[map][,[lib]]]][;]\r
+\r
+`obj' is a list of object files to be linked. Each obj file name must be\r
+separated by a + or a space. If you do not specify an extension, LINK\r
+will assume .OBJ. `exe' allows you to specify a name for the executable\r
+file. If this file name is ommited, LINK will use the first obj file name\r
+and suffix it with .EXE. `map' is an optional map file name. If you\r
+specify the name `NUL', no map file is produced. `lib' is a list of\r
+library files to link. LINK searches each library file and only links in\r
+modules which are referenced.\r
+\r
+\r
+eg:\r
+\r
+     LINK filea+fileb,myfile,NUL;\r
+\r
+Links .obj files `filea.obj' and `fileb.obj' into .exe file `myfile.exe'\r
+with no map file produced. The ; at the end of the line tells LINK that\r
+there are no more parameters.\r
+\r
+\r
+\r
+Using Overlays\r
+\r
+Overlay .obj modules are specified by encasing the .obj name in\r
+parenthesis in the link line.\r
+\r
+eg:\r
+\r
+     LINK filea + (fileb) + (filec),myfile,NUL;\r
+\r
+Will link filea.obj fileb.obj and filec.obj with modules fileb.obj and\r
+filec.obj as overlay code.\r
+\r
+\r
+Overlay modules must use FAR call/return instructions.\r
+\r
+\r
+Linker Options\r
+\r
+All LINK options commence with a forward slash `/'. Options which accept\r
+a number can accept a decimal number or a hex number prefixed 0X. eg:\r
+0X10 is interpreted as 10h, decimal 16.\r
+\r
+\r
+\r
+Pause during Linking (/PAU)\r
+\r
+     Tells LINK to wait before writing the .exe file to disk. LINK\r
+displays a\r
+     message and waits for you to press enter.\r
+\r
+Display Linker Process Information (/I)\r
+\r
+     Tells LINK to display information about the link process.\r
+\r
+Pack Executable File (/E)\r
+\r
+     Tells LINK to remove sequences of repeated bytes and to optimise the\r
+load-time\r
+     relocation table before creating the executable file. Symbolic debug\r
+     information is stripped out of the file.\r
+\r
+List Public Symbols (/M)\r
+\r
+     Tells LINK to create a list of all public symbols defined in the\r
+     object files\r
+     in the MAP file.\r
+\r
+Include Line Numbers In Map File (/LI)\r
+\r
+     Tells LINK to include line numbers and associated addresses of the\r
+source\r
+     program in the MAP file.\r
+\r
+Preserve Case Sensitivity (/NOI)\r
+\r
+     By default LINK treats uppercase and lowercase letters as the same.\r
+This\r
+     option tells LINK that they are different.\r
+\r
+Ignore Default Libraries (/NOD)\r
+\r
+     Tells LINK not to search any library specified in the object files\r
+to resolve\r
+     external references.\r
+\r
+Controlling Stack Size (/ST:n)\r
+\r
+     Specifies the size of the stack segment where 'n' is the number of\r
+bytes.\r
+\r
+Setting Maximum Allocation Space (/CP:n)\r
+\r
+     Tells LINK to write the parameter 'n' into the exe file header. When\r
+the exe\r
+     file is executed by DOS, 'n' 16 byte paragraphs of memory are\r
+reserved. If 'n'\r
+     is less than the minimum required, it will be set to the minimum.\r
+This option\r
+     is ESSENTIAL to free memory from the program. C programs free memory\r
+     automatically on start-up, assembly language programs which want to\r
+use\r
+     dynamic memory allocation must be linked with this option set to a\r
+minimum.\r
+\r
+Setting Maximum Number Of Segments (/SE:n)\r
+\r
+     Tells LINK how many segments a program is allowed to have. The\r
+default is 128\r
+     but 'n' can be any number between 1 and 3072.\r
+\r
+\r
+Setting Overlay Interrupt (/O:n)\r
+\r
+     Tells LINK which interrupt number will be used for passing control\r
+to\r
+     overlays. The default is 63. Valid values for 'n' are 0 through 255.\r
+\r
+Ordering Segments (/DO)\r
+\r
+     Tells LINK to use DOS segment ordering. This option is also enabled\r
+by the\r
+     MASM directive .DOSSEG.\r
+\r
+Controlling Data Loading (/DS)\r
+\r
+     By default LINK loads all data starting at the low end of the data\r
+segment. At\r
+     run time the DS register is set to the lowest possible address to\r
+allow the\r
+     entire data segment to be used. This option tells LINK to load all\r
+data\r
+     starting at the high end of the data segment.\r
+\r
+Control Exe File Loading (/HI)\r
+\r
+     Tells LINK to place the exe file as high as possible in memory.\r
+\r
+Prepare for Debugging (/CO)\r
+\r
+     Tells LINK to include symbolic debug information for use by\r
+codeview.\r
+\r
+Optimising Far Calls (/F)\r
+\r
+     Tells LINK to translate FAR calls to NEAR calls where possible. This\r
+results\r
+     in faster code.\r
+\r
+Disabling Far Call Optimisation (/NOF)\r
+\r
+     Tells LINK not to translate FAR calls. This option is specified by\r
+default.\r
+\r
+Packing Contiguous Segments (/PAC:n)\r
+\r
+     Tells LINK to group together neighbouring code segments, providing\r
+more\r
+     oportunities for FAR call translation. 'n' specifies the maximum\r
+size of a\r
+     segment. By default 'n' is 65530. This option is only relevant to\r
+obj files\r
+     created using FAR calls.\r
+\r
+Disabling Segment Packing (/NOP)\r
+\r
+     Disables segment packing. This option is specified by default.\r
+\r
+\r
+\r
+Using Response Files\r
+\r
+Linker options and file names may be specified in a response file. Each\r
+file list starting on a new line instead of being separated by a comma.\r
+\r
+eg:\r
+\r
+     filea.obj fileb.obj\r
+     myfile.exe\r
+     NUL\r
+     liba.lib libb.lib\r
+     \r
+A response file is specified to LINK by prefixing the response file name\r
+with '@'.\r
+eg:\r
+\r
+     LINK @response\r
+\r