--- /dev/null
+\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,®s,®s);\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,®s,®s);\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,®s,®s);\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,®s,®s);\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(®s,®s,&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(®s,®s);\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,®s,®s);\r
+ \r
+ if (regs.h.ah)\r
+ return 0;\r
+ \r
+ regs.h.ah = 0x41;\r
+ int86(EMM,®s,®s);\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,®s,®s);\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,®s,®s);\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,®s,®s);\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,®s,®s);\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