THE IBM PC PROGRAMMER'S GUIDE TO C 3rd Edition Matthew Probert COPYRIGHT NOTICE This publication remains the property of Matthew Probert. License is hereby given for this work to be freely distibuted in whole under the proviso that credit is given to the author. Sections of this work may be used and distributed without payment under the proviso that credit is given to both this work and the author. Source code occuring in this work may be used within commercial and non-commercial applications without charge and without reference to the author. BIOGRAPHICAL NOTES Matthew Probert is a software consultant working for his own firm, Servile Software. He has been involved with micro-computer software design and programming since the age of eighteen and has been involved with the C programming language for the past ten years. His educational background lies in the non-too distinguished honour of having a nervous break down during his last year of secondary school which resulted in a lack of formal qualifications. However, Matthew has made up for it by achieving a complete recovery and has been studying Psychology with particular attention to behaviourism and conversation ever since. His chequered career spans back twelve years during which time he has trained people in the design and use of database management applications, been called upon to design and implement structured methodologies and has been a "good old fashioned" analyst programmer. Matthew Probert is currently researching Artificial Intelligence with particular reference to the application of natural language processing, whereby a computer software package may decode written human language and respond to it in an intelligent manner. He is also monitoring the progress of facilitated communication amongst autistic and children with severe learning and challenging behaviour and hopes one day to be able to develope a computer based mechanism for true and reliable communication between autistic people and the rest of society. Matthew Probert can be contacted via Servile Software 5 Longcroft Close Basingstoke Hampshire RG21 8XG England Telephone 01256 478576 PREFACE In 1992, an English software house, Servile Software published a paper entitled "HOW TO C", which sought to introduce computer programmers to the C programming language. That paper was written by Matthew Probert. A follow up effort was "HOW TO CMORE", a document that was also published by Servile Software. Now those two documents have been amalgamated and thoroughly revamped to create this book. I have included loads of new source code that can be lifted directly out of the text. All the program listings have been typed in to the Turbo C compiler, compiled and executed successfully before being imported into this document. I hope you enjoy my work, and more I hope that you learn to program in C. It really is a great language, there can be no other language that gives the computer the opportunity to live up to the old saying; "To err is human, to make a complete balls up requires a computer!" Warning! This document is the result of over ten years experience as a software engineer. This document contains professional source code that is not intended for beginers. INTRODUCTION The major distinguishing features of the C programming language are; � block-structured flow-control constructs (typical of most high-level languages); � freedom to manipulate basic machine objects (eg: bytes) and to refer to them using any particular object view desired (typical of assembly- languages); � both high-level operations (eg: floating-point arithmetic) and low- level operations (which map closely onto machine-language instructions, thereby offering the means to code in an optimal, yet portable, manner). This book sets out to describe the C programming language, as commonly found with compilers for the IBM PC, to enable a computer programmer with no previous knowledge of the C programming language to program in C using the IBM PC including the ROM facilities provided by the PC and facilities provided DOS. It is assumed that the reader has access to a C compiler, and to the documentation that accompanies it regarding library functions. The example programs were written with Borland's Turbo C, most of the non- standard facilities provided by Turbo C should be found in later releases of Microsoft C. Differences Between the Various Versions of C The original C (prior to the definitive book by K&R) defined the combination assignment operators (eg: +=, *=, etc.) backwards (ie: they were written =+, =*, etc.). This caused terrible confusion when a statement such as x=-y; was compiled - it could have meant x = x - y; or x = (-y); Ritchie soon spotted this ambiguity and changed the language to have these operators written in the now-familiar manner (+=, *=, etc.). The major variations, however, are between K&R C and ANSI C. These can be summarized as follows: � introduction of function prototypes in declarations; change of function definition preamble to match the style of prototypes; � introduction of the ellipsis ("...") to show variable-length function argument lists; � introduction of the keyword `void' (for functions not returning a value) and the type `void *' for generic pointer variables; � addition of string-merging, token-pasting and stringizing functions in the preprocessor; � addition of trigraph translation in the preprocessor; � addition of the `#pragma' directive and formalization of the `declared()' pseudofunction in the preprocessor; � introduction of multi-byte strings and characters to support non- English languages; � introduction of the `signed' keyword (to complement the `unsigned' keyword when used in integer declarations) and the unary plus (`+') operator. C is a medium level language The powerful facilities offered by C to allow manipulation of direct memory addresses and data, even down to the bit level, along with C's structured approach to programming cause C to be classified as a "medium level" programming language. It possesses fewer ready made facilities than a high level language, such as BASIC, but a higher level of structure than low level Assembler. Key words The original C language as described in; "The C programming language", by Kernighan and Ritchie, provided 27 key words. To those 27 the ANSI standards committee on C have added five more. This confusingly results in two standards for the C language. However, the ANSI standard is quickly taking over from the old K & R standard. The 32 C key words are; auto double int struct break else long switch case enum register typedef char extern return union const float short unsigned continue for signed void default goto sizeof volatile do if static while Some C compilers offer additional key words specific to the hardware environment that they operate on. You should be aware of your own C compilers additional key words. Most notably on the PC these are; near far huge Structure C programs are written in a structured manner. A collection of code blocks are created that call each other to comprise the complete program. As a structured language C provides various looping and testing commands such as; do-while, for, while, if and the use of jumps, while provided for, are rarely used. A C code block is contained within a pair of curly braces "{ }", and may be a complete procedure, in C terminology called a "function", or a subset of code within a function. For example the following is a code block. The statements within the curly braces are only executed upon satisfaction of the condition that "x < 10"; if (x < 10) { a = 1; b = 0; } while this, is a complete function code block containing a sub code block as a do-while loop; int GET_X() { int x; do { printf("\nEnter a number between 0 and 10 "); scanf("%d",&x); } while(x < 0 || x > 10); return(x); } Notice how every statement line is terminated in a semicolon, unless that statement marks the start of a code block, in which case it is followed by a curly brace. C is a case sensitive but free flow language, spaces between commands are ignored, and therefore the semicolon delimiter is required to mark the end of the command line. Having a freeflow structure the following commands are recognised as the same by the C compiler; x = 0; x =0; x=0; The general form of a C program is as follows; compiler preprocessor statements global data declarations return-type main(parameter list) { statements } return-type f1(parameter list) { statements } return-type f2(parameter list) { statements } . . . return-type fn(parameter list) { statements } Comments C allows comments to be included in the program. A comment line is defined by being enclosed within "/*" and "*/". Thus the following is a comment; /* This is a legitimate C comment line */ Libraries C programs are compiled and combined with library functions provided with the C compiler. These libraries are of generally standard functions, the functionality of which are defined in the ANSI standard of the C language, but are provided by the individual C compiler manufacturers to be machine dependant. Thus, the standard library function "printf()" provides the same facilities on a DEC VAX as on an IBM PC, although the actual machine language code in the library is quite different for each. The C programmer however, does not need to know about the internals of the libraries, only that each library function will behave in the same way on any computer. DATA TYPES There are four basic types of data in the C language; character, integer, floating point, and valueless that are referred to by the C key words; "char", "int", "float" and "void" respectively. To the basic data types may be added the type modifiers; signed, unsigned, long and short to produce further data types. By default data types are assumed signed, and the signed modifier is rarely used, unless to overide a compiler switch defaulting a data type to unsigned. The size of each data type varies from one hardware platform to another, but the least range of values that can be held is described in the ANSI standard as follows; Type Size Range char 8 -127 to 127 unsigned char 8 0 to 255 int 16 -32767 to 32767 unsigned int 16 0 to 65535 long int 32 -2147483647 to 2147483647 unsigned long int 32 0 to 4294967295 float 32 Six digit precision double 64 Ten digit precision long double 80 Ten digit precision In practice, this means that the data type `char' is particularly suitable for storing flag type variables, such as status codes, which have a limited range of values. The `int' data type can be used, but if the range of values does not exceed 127 (or 255 for an unsigned char), then each declared variable would be wasting storage space. Which real number data type to use: `float', `double' or `long double' is another tricky question. When numeric accuracy is required, for example in an accounting application, the instinct would be to use the `long double', but this requires at least 10 bytes of storage space for each variable. And real numbers are not as precise as integers anyway, so perhaps one should use integer data types instead and work around the problem. The data type `float' is worse than useless since its six digit precision is too inaccurate to be relied upon. Generally, then, you should use integer data types where ever possible, but if real numbers are required use at least a `double'. Declaring a variable All variables in a C program must be declared before they can be used. The general form of a variable definition is; type name; So, for example to declare a variable "x", of data type "int" so that it may store a value in the range -32767 to 32767, you use the statement; int x; Character strings may be declared, which are in reality arrays of characters. They are declared as follows; char name[number_of_elements]; So, to declare a string thirty characters long, and called `name' you would use the declaration; char name[30]; Arrays of other data types also may be declared in one, two or more dimensions in the same way. For example to declare a two dimensional array of integers; int x[10][10]; The elements of this array are then accessed as; x[0][0] x[0][1] x[n][n] There are three levels of access to variable; local, module and global. A variable declared within a code block is only known to the statements within that code block. A variable declared outside any function code blocks but prefixed with the storage modifier "static" is known only to the statements within that source module. A variable declared outside any functions and not prefixed with the static storage type modifier may be accessed by any statement within any source module of the program. For example; int error; static int a; main() { int x; int y; } funca() { /* Test variable 'a' for equality with 0 */ if (a == 0) { int b; for(b = 0; b < 20; b++) printf("\nHello World"); } } In this example the variable `error' is accessible by all source code modules compiled together to form the finished program. The variable `a' is accessible by statements in both functions `main()' and `funca()', but is invisible to any other source module. Variables `x' and `y' are only accessible by statements within function `main()'. The variable `b' is only accessible by statements within the code block following the `if' statement. If a second source module wished to access the variable `error' it would need to declare `error' as an `extern' global variable thus; extern int error; funcb() { } C will quite happily allow you, the programmer, to assign different data types to each other. For example, you may declare a variable to be of type `char' in which case a single byte of data will be allocated to store the variable. To this variable you can attempt to allocate larger values, for example; main() { x = 5000; } In this example the variable `x' can only store a value between -127 and 128, so the figure 5000 will NOT be assigned to the variable `x'. Rather the value 136 will be assigned! Often you may wish to assign different data types to each other, and to prevent the compiler from warning you of a possible error you can use a cast to tell the compiler that you know what you're doing. A cast statement is a data type in parenthesis preceding a variable or expression; main() { float x; int y; x = 100 / 25; y = (int)x; } In this example the (int) cast tells the compiler to convert the value of the floating point variable x to an integer before assigning it to the variable y. Formal parameters A C function may receive parameters from a calling function. These parameters are declared as variables within the parentheses of the function name, thus; int MULT(int x, int y) { /* Return parameter x multiplied by parameter y */ return(x * y); } main() { int a; int b; int c; a = 5; b = 7; c = MULT(a,b); printf("%d multiplied by %d equals %d\n",a,b,c); } Access modifiers There are two access modifiers; `const' and `volatile'. A variable declared to be `const' may not be changed by the program, whereas a variable declared as type as type `volatile' may be changed by the program. In addition, declaring a variable to be volatile prevents the C compiler from allocating the variable to a register, and reduces the optimization carried out on the variable. Storage class types C provides four storage types; `extern', `static', `auto' and `register'. The extern storage type is used to allow a source module within a C program to access a variable declared in another source module. Static variables are only accessible within the code block that declared them, and additionally if the variable is local, rather than global, they retain their old value between subsequent calls to the code block. Register variables are stored within CPU registers where ever possible, providing the fastest possible access to their values. The auto type variable is only used with local variables, and declares the variable to retain its value locally only. Since this is the default for local variables the auto storage type is very rarely used. OPERATORS Operators are tokens that cause a computation to occur when applied to variables. C provides the following operators; & Address * Indirection + Unary plus - Unary minus ~ Bitwise compliment ! Logical negation ++ As a prefix; preincrement As a suffix; postincrement -- As a prefix; predecrement As a suffix; postdecrement + Addition - Subtraction * Multiply / Divide % Remainder << Shift left >> Shift right & Bitwise AND | Bitwise OR ^ Bitwise XOR && Logical AND || Logical OR = Assignment *= Assign product /= Assign quotient %= Assign remainder (modulus) += Assign sum -= Assign difference <<= Assign left shift >>= Assign right shift &= Assign bitwise AND |= Assign bitwise OR ^= Assign bitwise XOR < Less than > Greater than <= Less than or equal to >= Greater than or equal to == Equal to != Not equal to . Direct component selector -> Indirect component selector a ? x:y "If a is true then x else y" [] Define arrays () Parenthesis isolate conditions and expressions ... Ellipsis are used in formal parameter lists of function prototypes to show a variable number of parameters or parameters of varying types. To illustrate some more commonly used operators consider the following short program; main() { int a; int b; int c; a = 5; /* Assign a value of 5 to variable 'a' */ b = a / 2; /* Assign the value of 'a' divided by two to variable 'b' */ c = b * 2; /* Assign the value of 'b' multiplied by two to variable 'c' */ if (a == c) /* Test if 'a' holds the same value as 'c' */ puts("Variable 'a' is an even number"); else puts("Variable 'a' is an odd number"); } Normally when incrementing the value of a variable you would write something like; x = x + 1 C provides the incremental operator '++' as well so that you can write; x++ Similarly you can decrement the value of a variable using '--' as; x-- All the other mathematical operators may be used the same, so in a C program you can write in shorthand; NORMAL C x = x + 1 x++ x = x - 1 x-- x = x * 2 x *= 2 x = x / y x /= y x = x % 5 x %= 5 and so on. FUNCTIONS Functions are the source code procedures that comprise a C program. They follow the general form; return_type function_name(parameter_list) { statements } The return_type specifies the data type that will be returned by the function; char, int, double, void &c. The code within a C function is invisible to any other C function, and jumps may not be made from one function into the middle of another, although functions may call other functions. Also, functions cannot be defined within functions, only within source modules. Parameters may be passed to a function either by value, or by reference. If a parameter is passed by value, then only a copy of the current value of the parameter is passed to the function. A parameter passed by reference however, is a pointer to the actual parameter that may then be changed by the function. The following example passes two parameters by value to a function, funca(), which attempts to change the value of the variables passed to it. And then passes the same two parameters by reference to funcb() which also attempts to modify their values. #include int funca(int x, int y) { /* This function receives two parameters by value, x and y */ x = x * 2; y = y * 2; printf("\nValue of x in funca() %d value of y in funca() %d",x,y); return(x); } int funcb(int *x, int *y) { /* This function receives two parameters by reference, x and y */ *x = *x * 2; *y = *y * 2; printf("\nValue of x in funcb() %d value of y in funcb() %d",*x,*y); return(*x); } main() { int x; int y; int z; x = 5; y = 7; z = funca(x,y); z = funcb(&x,&y); printf("\nValue of x %d value of y %d value of z %d",x,y,z); } Actually funcb() does not change the values of the parameters it receives. Rather it changes the contents of the memory addresses pointed to by the received parameters. While funca() receives the values of variables `x' and `y' from function main(), funcb() receives the memory addresses of the variables `x' and `y' from function main(). Passing an array to a function The following program passes an array to a function, funca(), which initialises the array elements; #include void funca(int x[]) { int n; for(n = 0; n < 100; n++) x[n] = n; } main() { int array[100]; int counter; funca(array); for(counter = 0; counter < 100; counter++) printf("\nValue of element %d is %d",counter,array[counter]); } The parameter of funca() `int x[]' is declared to be an array of any length. This works because the compiler passes the address of the start of the array parameter to the function, rather than the value of the individual elements. This does of course mean that the function can change the value of the array elements. To prevent a function from changing the values you can specify the parameter as type `const'; funca(const int x[]) { } This will then generate a compiler error at the line that attempts to write a value to the array. However, specifying a parameter to be const does not protect the parameter from indirect assignment as the following program illustrates; #include int funca(const int x[]) { int *ptr; int n; /* This line gives a 'suspicious pointer conversion warning' */ /* because x is a const pointer, and ptr is not */ ptr = x; for(n = 0; n < 100; n++) { *ptr = n; ptr++; } } main() { int array[100]; int counter; funca(array); for(counter = 0; counter < 100; counter++) printf("\nValue of element %d is %d",counter,array[counter]); } Passing parameters to main() C allows parameters to be passed from the operating system to the program when it starts executing through two parameters; argc and argv[], as follows; #include main(int argc, char *argv[]) { int n; for(n = 0; n < argc; n++) printf("\nParameter %d equals %s",n,argv[n]); } Parameter argc holds the number of parameters passed to the program, and the array argv[] holds the addresses of each parameter passed. argv[0] is always the program name. This feature may be put to good use in applications that need to access system files. Consider the following scenario: A simple database application stores its data in a single file called "data.dat". The application needs to be created so that it may be stored in any directory on either a floppy diskette or a hard disk, and executed both from within the host directory and through a DOS search path. To work correctly the application must always know where to find the data file; "data.dat". This is solved by assuming that the data file will be in the same directory as the executable module, a not unreasonable restriction to place upon the operator. The following code fragment then illustrates how an application may apply this algorithm into practice to be always able to locate a desired system file: #include char system_file_name[160]; void main(int argc,char *argv[]) { char *data_file = "DATA.DAT"; char *p; strcpy(system_file_name,argv[0]); p = strstr(system_file_name,".EXE"); if (p == NULL) { /* The executable is a .COM file */ p = strstr(system_file_name,".COM"); } /* Now back track to the last '\' character in the file name */ while(*(p - 1) != '\\') p--; strcpy(p,data_file); } In practice this code creates a string in system_file_name that is comprised of path\data.dat, so if for example the executable file is called "test.exe" and resides in the directory \borlandc, then system_file_name will be assigned with: \borlandc\data.dat Returning from a function The command `return' is used to return immediately from a function. If the function was declared with a return data type, then return should be used with a parameter of the same data type. Function prototypes Prototypes for functions allow the C compiler to check that the type of data being passed to and from functions is correct. This is very important to prevent data overflowing its allocated storage space into other variables areas. A function prototype is placed at the beginning of the program, after any preprocessor commands, such as #include , and before the declaration of any functions. THE C PREPROCESSOR C allows for commands to the compiler to be included in the source code. These commands are then called preprocessor commands and are defined by the ANSI standard to be; #if #ifdef #ifndef #else #elif #include #define #undef #line #error #pragma All preprocessor commands start with a hash symbol, "#", and must be on a line on their own (although comments may follow). #define The #define command specifies an identifier and a string that the compiler will substitute every time it comes accross the identifier within that source code module. For example; #define FALSE 0 #define TRUE !FALSE The compiler will replace any subsequent occurence of `FALSE' with `0' and any subsequent occurence of `TRUE' with `!0'. The substitution does NOT take place if the compiler finds that the identifier is enclosed by quotation marks, so printf("TRUE"); would NOT be replaced, but printf("%d",FALSE); would be. The #define command also can be used to define macros that may include parameters. The parameters are best enclosed in parenthesis to ensure that correct substitution occurs. This example declares a macro `larger()' that accepts two parameters and returns the larger of the two; #include #define larger(a,b) (a > b) ? (a) : (b) int main() { printf("\n%d is largest",larger(5,7)); } #error The #error command causes the compiler to stop compilation and to display the text following the #error command. For example; #error REACHED MODULE B will cause the compiler to stop compilation and display; REACHED MODULE B #include The #include command tells the compiler to read the contents of another source file. The name of the source file must be enclosed either by quotes or by angular brackets thus; #include "module2.c" #include Generally, if the file name is enclosed in angular brackets, then the compiler will search for the file in a directory defined in the compiler's setup. Whereas if the file name is enclosed in quotes then the compiler will look for the file in the current directory. #if, #else, #elif, #endif The #if set of commands provide conditional compilation around the general form; #if constant_expression statements #else statements #endif #elif stands for '#else if' and follows the form; #if expression statements #elif expression statements #endif #ifdef, #ifndef These two commands stand for '#if defined' and '#if not defined' respectively and follow the general form; #ifdef macro_name statements #else statements #endif #ifndef macro_name statements #else statements #endif where 'macro_name' is an identifier declared by a #define statement. #undef Undefines a macro previously defined by #define. #line Changes the compiler declared global variables __LINE__ and __FILE__. The general form of #line is; #line number "filename" where number is inserted into the variable '__LINE__' and 'filename' is assigned to '__FILE__'. #pragma This command is used to give compiler specific commands to the compiler. The compiler's manual should give you full details of any valid options to go with the particular implementation of #pragma that it supports. PROGRAM CONTROL STATEMENTS As with any computer language, C includes statements that test the outcome of an expression. The outcome of the test is either TRUE or FALSE. The C language defines a value of TRUE as non-zero, and FALSE as zero. Selection statements The general purpose selection statement is "if" that follows the general form; if (expression) statement else statement Where "statement" may be a single statement, or a code block enclosed in curly braces. The "else" is optional. If the result of the expression equates to TRUE, then the statement(s) following the if() will be evaluated. Otherwise the statement(s) following the else, if there is one, will be evaluated. An alternative to the if....else combination is the ?: command that takes the form; expression ? true_expression : false_expression Where if the expression evaluates to TRUE, then the true_expression will be evaluated, otherwise the false_expression will be evaluated. Thus we get; #include main() { int x; x = 6; printf("\nx is an %s number", x % 2 == 0 ? "even" : "odd"); } C also provides a multiple branch selection statement, switch, which successively tests a value of an expression against a list of values and branches program execution to the first match found. The general form of switch is; switch (expression) { case value1 : statements break; case value2 : statements break; . . . . case valuen : statements break; default : statements } The break statement is optional, but if omitted, program execution will continue down the list. #include main() { int x; x = 6; switch(x) { case 0 : printf("\nx equals zero"); break; case 1 : printf("\nx equals one"); break; case 2 : printf("\nx equals two"); break; case 3 : printf("\nx equals three"); break; default : printf("\nx is larger than three"); } } Switch statements may be nested within one another. This is a particularly useful feature for confusing people who read your source code! Iteration statements C provides three looping or iteration statements; for, while, and do- while. The for loop has the general form; for(initialization;condition;increment) and is useful for counters such as in this example that displays the entire ascii character set; #include main() { int x; for(x = 32; x < 128; x++) printf("%d\t%c\t",x,x); } An infinite for loop is also quite valid; for(;;) { statements } Also, C allows empty statements. The following for loop removes leading spaces from a string; for(; *str == ' '; str++) ; Notice the lack of an initializer, and the empty statement following the loop. The while loop is somewhat simpler than the for loop and follows the general form; while (condition) statements The statement following the condition, or statements enclosed in curly braces will be executed until the condition is FALSE. If the condition is false before the loop commences, the loop statements will not be executed. The do-while loop on the other hand is always executed at least once. It takes the general form; do { statements } while(condition); Jump statements The "return" statement is used to return from a function to the calling function. Depending upon the declared return data type of the function it may or may not return a value; int MULT(int x, int y) { return(x * y); } or; void FUNCA() { printf("\nHello World"); return; } The "break" statement is used to break out of a loop or from a switch statement. In a loop it may be used to terminate the loop prematurely, as shown here; #include main() { int x; for(x = 0; x < 256; x++) { if (x == 100) break; printf("%d\t",x); } } In contrast to "break" is "continue", which forces the next iteration of the loop to occur, effectively forcing program control back to the loop statement. C provides a function for terminating the program prematurely, "exit()". Exit() may be used with a return value to pass back to the calling program; exit(return_value); More About ?: A powerful, but often misunderstood feature of the C programming language is ?:. This is an operator that acts upon a boolean expression, and returns one of two values dependant upon the result of the expression; ? : It can be used almost anywhere, for example it was used in the binary search demonstration program; printf("\n%s\n",(result == 0) ? "Not found" : "Located okay"); Here it passes either "Not found" or "Located okay" to the printf() function dependant upon the outcome of the boolean expression `result == 0'. Alternatively it can be used for assigning values to a variable; x = (a == 0) ? (b) : (c); Which will assign the value of b to variable x if a is equal to zero, otherwise it will assign the value of c to variable x. This example returns the name of the executing program, without any path description; #include #include #include char *progname(char *pathname) { /* Return name of running program */ unsigned l; char *p; char *q; static char bnbuf[256]; return pathname? p = strrchr (pathname, '\\'), q = strrchr (pathname, '.'), l = (q == NULL? strchr (pathname, '\0'): q) - (p == NULL? p = pathname: ++p), strncpy (bnbuf, p, l), bnbuf[l] = '\0', strlwr (bnbuf) : NULL; } void main(int argc, char *argv[]) { printf("\n%s",progname(argv[0])); } Continue The continue keyword forces control to jump to the test statement of the innermost loop (while, do...while()). This can be useful for terminating a loop gracefuly as this program that reads strings from a file until there are no more illustrates; #include void main() { FILE *fp; char *p; char buff[100]; fp = fopen("data.txt","r"); if (fp == NULL) { fprintf(stderr,"Unable to open file data.txt"); exit(0); } do { p = fgets(buff,100,fp); if (p == NULL) /* Force exit from loop */ continue; puts(p); } while(p); } With a for() loop however, continue passes control back to the third parameter! INPUT AND OUTPUT Input Input to a C program may occur from the console, the standard input device (unless otherwise redirected this is the console), from a file or from a data port. The general input command for reading data from the standard input stream `stdin' is scanf(). Scanf() scans a series of input fields, one character at a time. Each field is then formatted according to the appropriate format specifier passed to the scanf() function as a parameter. This field is then stored at the ADDRESS passed to scanf() following the format specifiers list. For example, the following program will read a single integer from the stream stdin; main() { int x; scanf("%d",&x); } Notice the address operator & prefixing the variable name `x' in the scanf() parameter list. This is because scanf() stores values at ADDRESSES rather than assigning values to variables directly. The format string is a character string that may contain three types of data: whitespace characters (space, tab and newline), non-whitespace characters (all ascii characters EXCEPT %) and format specifiers. Format specifiers have the general form; %[*][width][h|l|L]type_character After the % sign the format specifier is comprised of: an optional assignment suppression character, *, which suppresses assignment of the next input field. an optional width specifier, width, which declares the maximum number of characters to be read. an optional argument type modifier, h or l or L, where: h is a short integer l is a long L is a long double the data type character that is one of; d Decimal integer D Decimal long integer o Octal integer O Octal long integer i Decimal, octal or hexadecimal integer I Decimal, octal or hexadecimal long integer u Decimal unsigned integer U Decimal unsigned long integer x Hexadecimal integer X Hexadecimal long integer e Floating point f Floating point g Floating point s Character string c Character % % is stored An example using scanf(); #include main() { char name[30]; int age; printf("\nEnter your name and age "); scanf("%30s%d",name,&age); printf("\n%s %d",name,age); } Notice the include line, "#include ", this is to tell the compiler to also read the file stdio.h that contains the function prototypes for scanf() and printf(). If you type in and run this sample program you will see that only one name can be entered, that is you can't enter; JOHN SMITH because scanf() detects the whitespace between "JOHN" and "SMITH" and moves on to the next input field, which is age, and attempts to assign the value "SMITH" to the age field! The limitations of scanf() as an input function are obvious. An alternative input function is gets() that reads a string of characters from the stream stdin until a newline character is detected. The newline character is replaced by a null (0 byte) in the target string. This function has the advantage of allowing whitespace to be read in. The following program is a modification to the earlier one, using gets() instead of scanf(). #include #include #include main() { char data[80]; char *p; char name[30]; int age; printf("\nEnter your name and age "); /* Read in a string of data */ gets(data); /* P is a pointer to the last character in the input string */ p = &data[strlen(data) - 1]; /* Remove any trailing spaces by replacing them with null bytes */ while(*p == ' '){ *p = 0; p--; } /* Locate last space in the string */ p = strrchr(data,' '); /* Read age from string and convert to an integer */ age = atoi(p); /* Terminate data string at start of age field */ *p = 0; /* Copy data string to name variable */ strcpy(name,data); /* Display results */ printf("\nName is %s age is %d",name,age); } Output The most common output function is printf() that is very similar to scanf() except that it writes formatted data out to the standard output stream stdout. Printf() takes a list of output data fields and applies format specifiers to each and outputs the result. The format specifiers are the same as for scanf() except that flags may be added to the format specifiers. These flags include; - Which left justifies the output padding to the right with spaces. + Which causes numbers to be prefixed by their sign The width specifier is also slightly different for printf(). In its most useful form is the precision specifier; width.precision So, to print a floating point number to three decimal places you would use; printf("%.3f",x); And to pad a string with spaces to the right or truncate the string to twenty characters if it is longer, to form a fixed twenty character width; printf("%-20.20s",data); Special character constants may appear in the printf() parameter list. These are; \n Newline \r Carriage return \t Tab \b Sound the computer's bell \f Formfeed \v Vertical tab \\ Backslash character \' Single quote \" Double quote \? Question mark \O Octal string \x Hexadecimal string Thus, to display "Hello World", surrounded in quotation marks and followed by a newline you would use; printf("\"Hello World\"\n"); The following program shows how a decimal integer may be displayed as a decimal, hexadecimal or octal integer. The 04 following the % in the printf() format tells the compiler to pad the displayed figure to a width of at least four digits padded with leading zeroes if required. /* A simple decimal to hexadecimal and octal conversion program */ #include main() { int x; do { printf("\nEnter a number, or 0 to end "); scanf("%d",&x); printf("%04d %04X %04o",x,x,x); } while(x != 0); } There are associated functions to printf() that you should be aware of. fprintf() has the prototype; fprintf(FILE *fp,char *format[,argument,...]); This variation on printf() simply sends the formatted output to the specified file stream. sprintf() has the function prototype; sprintf(char *s,char *format[,argument,...]); and writes the formatted output to a string. You should take care using sprintf() that the target string has been declared long enough to hold the result of the sprintf() output. Otherwise other data will be overwritten in memory. An example of using sprintf() to copy multiple data to a string; #include int main() { char buffer[50]; sprintf(buffer,"7 * 5 == %d\n",7 * 5); puts(buffer); } An alternative to printf() for outputting a simple string to the stream stdout is puts(). This function sends a string to the stream stdout followed by a newline character. It is faster than printf(), but far less flexible. Instead of; printf("Hello World\n"); You can use; puts("Hello World"); Direct Console I/O Data may be sent to, and read directly from the console (keyboard and screen) using the direct console I/O functions. These functions are prefixed `c'. The direct console I/O equivalent of printf() is then cprintf(), and the equivalent of puts() is cputs(). Direct console I/O functions differ from standard I?o functions: 1. They do not make use of the predefined streams, and hence may not be redirected 2. They are not portable across operating systems (for example you cant use direct console I/O functions in a Windows programme). 3. They are faster than their standard I/O equivalents 4. They may not work with all video modes (especially VESA display modes). POINTERS A pointer is a variable that holds the memory address of an item of data. Therefore it points to another item. A pointer is declared like an ordinary variable, but its name is prefixed by `*', thus; char *p; This declares the variable `p' to be a pointer to a character variable. Pointers are very powerful, and similarly dangerous! If only because a pointer can be inadvertently set to point to the code segment of a program and then some value assigned to the address of the pointer! The following program illustrates a simple, though fairly useless application of a pointer; #include main() { int a; int *x; /* x is a pointer to an integer data type */ a = 100; x = &a; printf("\nVariable 'a' holds the value %d at memory address %p",a,x); } Here is a more useful example of a pointer illustrating how because the compiler knows the type of data pointed to by the pointer, when the pointer is incremented it is incremented the correct number of bytes for the data type. In this case two bytes; #include main() { int n; int a[25]; int *x; /* x is a pointer to an integer data type */ /* Assign x to point to array element zero */ x = a; /* Assign values to the array */ for(n = 0; n < 25; n++) a[n] = n; /* Now print out all array element values */ for(n = 0; n < 25; n++ , x++) printf("\nElement %d holds %d",n,*x); } To read or assign a value to the address held by a pointer you use the indirection operator `*'. Thus in the above example, to print the value at the memory address pointed to by variable x I have used `*x'. Pointers may be incremented and decremented and have other mathematics applied to them also. For example in the above program to move variable x along the array one element at a time we put the statement `x++' in the for loop. We could move x along two elements by saying `x += 2'. Notice that this doesn't mean "add 2 bytes to the value of x", but rather it means "add 2 of the pointer's data type size units to the value of x". Pointers are used extensively in dynamic memory allocation. When a program is running it is often necessary to temporarily allocate a block of data, say a table, in memory. C provides the function malloc() for this purpose that follows the general form; any pointer type = malloc(number_of_bytes); malloc() actually returns a void pointer type, which means it can be any type; integer, character, floating point or whatever. This example allocates a table in memory for 1000 integers; #include #include main() { int *x; int n; /* x is a pointer to an integer data type */ /* Create a 1000 element table, sizeof() returns the compiler */ /* specific number of bytes used to store an integer */ x = malloc(1000 * sizeof(int)); /* Check to see if the memory allocation succeeded */ if (x == NULL) { printf("\nUnable to allocate a 1000 element integer table"); exit(0); } /* Assign values to each table element */ for(n = 0; n < 1000; n++) { *x = n; x++; } /* Return x to the start of the table */ x -= 1000; /* Display the values in the table */ for(n = 0; n < 1000; n++){ printf("\nElement %d holds a value of %d",n,*x); x++; } /* Deallocate the block of memory now it's no longer required */ free(x); } Pointers are also extensively used with character arrays, called strings. Since all C program strings are terminated by a zero byte we can count the letters in a string using a pointer; #include #include main() { char *p; char text[100]; int len; /* Initialise variable 'text' with some writing */ strcpy(text,"This is a string of data"); /* Set variable p to the start of variable text */ p = text; /* Initialise variable len to zero */ len = 0; /* Count the characters in variable text */ while(*p) { len++; p++; } /* Display the result */ printf("\nThe string of data has %d characters in it",len); } The 8088/8086 group of CPUs, as used in the IBM PC, divide memory into 64K segments. To address all 1Mb of memory a 20 bit number is used comprised of an `offset' to and a 64K `segment'. The IBM PC uses special registers called "segment registers" to record the segments of addresses. This leads the C language on the IBM PC to have three new keywords; near, far and huge. near pointers are 16 bits wide and access only data within the current segment. far pointers are comprised of an offset and a segment address allowing them to access data any where in memory, but arithmetic with far pointers is fraught with danger! When a value is added or subtracted from a far pointer it is only actualy the segment part of the pointer that is affected, thus leading to the situation where a far pointer being incremented wraps around on its own offset 64K segment. huge pointers are a variation on the far pointer that can be successfuly incremented and decremented through the entire 1Mb range since the compiler generates code to amend the offset as applicable. It will come as no surprise that code using near pointers is executed faster than code using far pointers, which in turn is faster than code using huge pointers. to give a literal address to a far pointer IBM PC C compilers provide a macro MK-FP() that has the prototype; void far *MK_FP(unsigned segment, unsigned offset); For example, to create a far pointer to the start of video memory on a colour IBM PC system you could use; screen = MK_FP(0xB800,0); Two coressponding macros provided are; FP_SEG() and FP_OFF() Which return the segment and offset respectively of a far pointer. The following example uses FP_SEG() and FP_OFF() to send segment and offset addresses to a DOS call to create a new directory path; #include int makedir(char *path) { /* Make sub directory, returning non zero on success */ union REGS inreg,outreg; struct SREGS segreg; inreg.h.ah = 0x39; segreg.ds = FP_SEG(path); inreg.x.dx = FP_OFF(path); intdosx(&inreg,&outreg,&segreg); return(1 - outreg.x.cflag); } Finally, the CPU's segment registers can be read with the function `segread()'. This is a function unique to C compilers for the the 8086 family of processors. segread() has the function prototype: void segread(struct SREGS *segp); It returns the current values of the segment registers in the SREGS type structure pointed to by the pointer `segp'. For example: #include main() { struct SREGS sregs; segread(&sregs); printf("\nCode segment is currently at %04X, Data segment is at %04X",sregs.cs,sregs.ds); printf("\nExtra segment is at %04X, Stack segment is at %04X",sregs.es,sregs.ss); } STRUCTURES C provides the means to group together variables under one name providing a convenient means of keeping related information together and providing a structured approach to data. The general form for a structure definition is; typedef struct { variable_type variable_name; variable_type variable_name; } structure_name; When accessing data files, with a fixed record structure, the use of a structure variable becomes essential. The following example shows a record structure for a very simple name and address file. It declares a data structure called `data', and comprised of six fields; `name', `address', `town', `county' , `post' and `telephone'. typedef struct { char name[30]; char address[30]; char town[30]; char county[30]; char post[12]; char telephone[15]; } data; The structure 'data' may then be used in the program as a variable data type for declaring variables; data record; Thus declares a variable called 'record' to be of type 'data'. The individual fields of the structure variable are accessed by the general form; structure_variable.field_name; Thus, the name field of the structure variable record is accessed with; record.name There is no limit to the number of fields that may comprise a structure, nor do the fields have to be of the same types, for example; typedef struct { char name[30]; int age; char *notes; } dp; Declares a structure 'dp' that is comprised of a character array field, an integer field and a character pointer field. A structure variable may be passed as a parameter by passing the address of the variable as the parameter with the & operator thus; func(&my_struct) The following is an example program that makes use of a structure to provide the basic access to the data in a simple name and address file; /* A VERY simple address book written in ANSI C */ #include #include #include #include #include #include /* num_lines is the number of screen display lines */ #define num_lines 25 typedef struct { char name[30]; char address[30]; char town[30]; char county[30]; char post[12]; char telephone[15]; } data; data record; int handle; /* Function prototypes */ void ADD_REC(void); void CLS(void); void DISPDATA(void); void FATAL(char *); void GETDATA(void); void MENU(void); void OPENDATA(void); int SEARCH(void); void CLS() { int n; for(n = 0; n < num_lines; n++) puts(""); } void FATAL(char *error) { printf("\nFATAL ERROR: %s",error); exit(0); } void OPENDATA() { /* Check for existence of data file and if not create it */ /* otherwise open it for reading/writing at end of file */ handle = open("address.dat",O_RDWR|O_APPEND,S_IWRITE); if (handle == -1) { handle = open("address.dat",O_RDWR|O_CREAT,S_IWRITE); if (handle == -1) FATAL("Unable to create data file"); } } void GETDATA() { /* Get address data from operator */ CLS(); printf("Name "); gets(record.name); printf("\nAddress "); gets(record.address); printf("\nTown "); gets(record.town); printf("\nCounty "); gets(record.county); printf("\nPost Code "); gets(record.post); printf("\nTelephone "); gets(record.telephone); } void DISPDATA() { /* Display address data */ char text[5]; CLS(); printf("Name %s",record.name); printf("\nAddress %s",record.address); printf("\nTown %s",record.town); printf("\nCounty %s",record.county); printf("\nPost Code %s",record.post); printf("\nTelephone %s\n\n",record.telephone); puts("Press RETURN to continue"); gets(text); } void ADD_REC() { /* Insert or append a new record to the data file */ int result; result = write(handle,&record,sizeof(data)); if (result == -1) FATAL("Unable to write to data file"); } int SEARCH() { char text[100]; int result; printf("Enter data to search for "); gets(text); if (*text == 0) return(-1); /* Locate start of file */ lseek(handle,0,SEEK_SET); do { /* Read record into memory */ result = read(handle,&record,sizeof(data)); if (result > 0) { /* Scan record for matching data */ if (strstr(record.name,text) != NULL) return(1); if (strstr(record.address,text) != NULL) return(1); if (strstr(record.town,text) != NULL) return(1); if (strstr(record.county,text) != NULL) return(1); if (strstr(record.post,text) != NULL) return(1); if (strstr(record.telephone,text) != NULL) return(1); } } while(result > 0); return(0); } void MENU() { int option; char text[10]; do { CLS(); puts("\n\t\t\tSelect Option"); puts("\n\n\t\t\t1 Add new record"); puts("\n\n\t\t\t2 Search for data"); puts("\n\n\t\t\t3 Exit"); puts("\n\n\n\n\n"); gets(text); option = atoi(text); switch(option) { case 1 : GETDATA(); /* Go to end of file to append new record */ lseek(handle,0,SEEK_END); ADD_REC(); break; case 2 : if (SEARCH()) DISPDATA(); else { puts("NOT FOUND!"); puts("Press RETURN to continue"); gets(text); } break; case 3 : break; } } while(option != 3); } void main() { CLS(); OPENDATA(); MENU(); } Bit Fields C allows the inclusion of variables with a size of less than eight bits to be included in structures. These variables are known as bit fields, and may be any declared size from one bit upwards. The general form for declaring a bit field is; type name : number_of_bits; For example, to declare a set of status flags, each occupying one bit; typedef struct { unsigned carry : 1; unsigned zero : 1; unsigned over : 1; unsigned parity : 1; } df; df flags; The variable `flags' then occupies only four bits in memory, and yet is comprised of four variables that may be accessed like any other structure field. Uunions Another facility provided by C for efficient use of available memory is the union structure. A union structure is a collection of variables that all share the same memory storage address. As such only one of the variables is ever accessible at a time. The general form of a union definition is; union name { type variable_name; type variable_name; . . . type variable_name; }; Thus, to declare a union structure for two integer variables; union data { int vara; int varb; }; and to declare a variable of type 'data'; data my_var; The variable 'my_var' is then comprised of the two variables 'vara' and 'varb' that are accessed like with any form of structure, eg; my_var.vara = 5; Assigns a value of 5 to the variable 'vara' of union 'my_var'. Enumerations An enumeration assigns ascending integer values to a list of symbols. An enumeration declaration takes the general form; enum name { enumeration list } variable_list; Thus to define a symbol list of colours, you can say; enum COLOURS { BLACK, BLUE, GREEN, CYAN, RED, MAGENTA, BROWN, LIGHTGRAY, DARKGRAY, LIGHTBLUE, LIGHTGREEN, LIGHTCYAN, LIGHTRED, LIGHTMAGENTA, YELLOW, WHITE }; Then, the number zero may be referred to by the symbol BLACK, the number one by the symbol BLUE, the number two by the symbol GREEN and so on. The following program illustrates the use of an enumeration list to symbolise integers; #include enum COLOURS { BLACK, BLUE, GREEN, CYAN, RED, MAGENTA, BROWN, LIGHTGRAY, DARKGRAY, LIGHTBLUE, LIGHTGREEN, LIGHTCYAN, LIGHTRED, LIGHTMAGENTA, YELLOW, WHITE }; void main() { int x; x = RED; printf("\nVariable 'x' holds %d",x); } FILE I/O C provides buffered file streams for file access. Some C platforms, such as Unix and DOS provide unbuffered file handles as well. Buffered streams Buffered streams are accessed through a variable of type 'file pointer'. The data type FILE is defined in the header file stdio.h. Thus to declare a file pointer; #include FILE *ptr; To open a stream C provides the function fopen(), which accepts two parameters, the name of the file to be opened, and the access mode for the file to be opened with. The access mode may be any one of; Mode Description r Open for reading w Create for writing, destroying any existing file a Open for append, creating a new file if it doesn't exist r+ Open an existing file for reading and writing w+ Create for reading and writing, destroying any existing file a+ Open for append, creating a new file if it doesn't exist. Optionaly either `b' or `t' may be appended for binary or text mode. If neither is appended, then the file stream will be opened in the mode described by the global variable _fmode. Data read or written from file streams opened in text mode undergoes conversion, that is the characters CR and LF are converted to CR LF pairs on writing, and the CR LF pair is converted to a single LF on reading. File streams opened in binary mode do not undergo conversion. If fopen() fails to open the file, it returns a value of NULL (defined in stdio.h) to the file pointer. Thus, the following program will create a new file called "data.txt" and open it for reading and writing; #include void main() { FILE *fp; fp = fopen("data.txt","w+"); } To close a stream C provides the function fclose(), which accepts the stream's file pointer as a parameter; fclose(fp); If an error occurs in closing the file stream, fclose() returns non zero. There are four basic functions for receiving and sending data to and from streams; fgetc(), fputc(), fgets() and fputs(). fgetc() simply reads a single character from the specified input stream; char fgetc(FILE *fp); Its opposite number is fputc(), which simply writes a single character to the specified input stream; char fputc(char c, FILE *fp); fgets() reads a string from the input stream; char *fgets(char s, int numbytes, FILE *fp); It stops reading when either numbytes - 1 bytes have been read, or a newline character is read in. A null terminating byte is appended to the read string, s. If an error occurs, fgets() returns NULL. fputs() writes a null terminated string to a stream; int fputs(char *s, FILE *fp); Excepting fgets(), which returns a NULL pointer if an error occurs, all the other functions described above return EOF (defined in stdio.h) if an error occurs during the operation. The following program creates a copy of the file "data.dat" as "data.old" and illustrates the use of fopen(), fgetc(), fputc() and fclose(); #include int main() { FILE *in; FILE *out; in = fopen("data.dat","r"); if (in == NULL) { puts("\nUnable to open file data.dat for reading"); return(0); } out = fopen("data.old","w+"); if (out == NULL) { puts("\nUnable to create file data.old"); return(0); } /* Loop reading and writing one byte at a time until end-of- file */ while(!feof(in)) fputc(fgetc(in),out); /* Close the file streams */ fclose(in); fclose(out); return(0); } Example program using fputs() to copy text from stream stdin (usually typed in at the keyboard) to a new file called "data.txt". #include int main() { FILE *fp; char text[100]; fp = fopen("data.txt","w+"); do { gets(text); fputs(text,fp); } while(*text); fclose(fp); } Random access using streams Random file access for streams is provided for by the fseek() function that has the following prototype; int fseek(FILE *fp, long numbytes, int fromwhere); fseek() repositions a file pointer associated with a stream previously opened by a call to fopen(). The file pointer is positioned `numbytes' from the location `fromwhere', which may be the file beginning, the current file pointer position, or the end of the file, symbolised by the constants SEEK_SET, SEEK_CUR and SEEK_END respectively. If a call to fseek() succeeds, a value of zero is returned. Associated with fseek() is ftell(), which reports the current file pointer position of a stream, and has the following function prototype; long int ftell(FILE *fp); ftell() returns either the position of the file pointer, measured in bytes from the start of the file, or -1 upon an error occurring. Handles File handles are opened with the open() function that has the prototype; int open(char *filename,int access[,unsigned mode]); If open() is successful, the number of the file handle is returned. Otherwise open() returns -1. The access integer is comprised from bitwise oring together of the symbolic constants declared in fcntl.h. These vary from compiler to compiler but may be; O_APPEND If set, the file pointer will be set to the end of the file prior to each write. O_CREAT If the file does not exist it is created. O_TRUNC Truncates the existing file to a length of zero bytes. O_EXCL Used with O_CREAT O_BINARY Opens the file in binary mode O_TEXT Opens file in text mode The optional mode parameter is comprised by bitwise oring of the symbolic constants defined in stat.h. These vary from C compiler to C compiler but may be; S_IWRITE Permission to write S_IREAD Permission to read Once a file handle has been assigned with open(), the file may be accessed with read() and write(). Read() has the function prototype; int read(int handle, void *buf, unsigned num_bytes); It attempts to read 'num_bytes' and returns the number of bytes actually read from the file handle 'handle', and stores these bytes in the memory block pointed to by 'buf'. Write() is very similar to read() and has the same function prototype and return values, but writes 'num_bytes' from the memory block pointed to by 'buf'. Files opened with open() are closed using close() that has the function prototype; int close(int handle); close() returns zero on success, and -1 if an error occurs trying to close the handle. Random access is provided by lseek(), which is very similar to fseek(), except that it accepts an integer file handle as the first parameter rather than a stream FILE pointer. This example uses file handles to read data from stdin (usually the keyboard) and copy the text to a new file called "data.txt". #include #include #include int main() { int handle; char text[100]; handle = open("data.txt",O_RDWR|O_CREAT|O_TRUNC,S_IWRITE); do { gets(text); write(handle,&text,strlen(text)); } while(*text); close(handle); } Advanced File I/O The ANSI standard on C defines file IO as by way of file streams, and defines various functions for file access; fopen() has the prototype; FILE *fopen(const char *name,const char *mode); fopen() attempts to open a stream to a file name in a specified mode. If successful a FILE type pointer is returned to the file stream, if the call fails NULL is returned. The mode string can be one of the following; Mode Description r Open for reading only w Create for writing, overwriting any existing file with the same name. a Open for append (writing at end of file) or create the file if it does not exist. r+ Open an existing file for reading and writing. w+ Create a new file for reading and writing. a+ Open for append with read and write access. fclose() is used to close a file stream previously opened by a call to fopen(). It has the prototype; int fclose(FILE *fp); When a call to fclose() is successful, all buffers to the stream are flushed and a value of zero is returned. If the call fails fclose() returns EOF. Many host computers, and the IBM PC is no exception, use buffered file access, that is when writing to a file stream the data is stored in memory and only written to the stream when it exceeds a predefined number of bytes. A power failure occurring before the data has been written to the stream will result in the data never being written, so the function fflush() can be called to force all pending data to be written. fflush() has the prototype; int fflush(FILE *fp); When a call to fflush() is successful, the buffers connected with the stream are flushed and a value of zero is returned. On failure fflush() returns EOF. The location of the file pointer connected with a stream can be determined with the function ftell(). ftell() has the prototype; long int ftell(FILE *fp); and returns the offset of the file pointer in bytes from the start of the file, or -1L if the call fails. Similarly, you can move the file pointer to a new position with fseek(). fseek() has the prototype; int fseek(FILE *fp, long offset, int from_what_place); fseek() attempts to move the file pointer, 'fp' 'offset' bytes from the position 'from_what_place'. 'from_what_place' is predefined as one of; SEEK_SET The file's beginning SEEK_CUR The file pointer's current position SEEK_END End of file The offset may be a positive value to move the file pointer on through the file, or negative to move backwards. To move a file pointer quickly back to the start of a file, and clear any references to errors that have occurred C provides the function rewind() that has the prototype; void rewind(FILE *fp); rewind(fp) is similar to fseek(fp,0L,SEEK_SET) in that they both set the file pointer to the start of the file, but whereas fseek() clears the EOF error marker, rewind() clears all error indicators. Errors occurring with file functions can be checked with the function ferror() that has the prototype; int ferror(FILE *fp); ferror() returns a nonzero value if an error has occurred on the specified stream. After checking ferror() and reporting any errors you should clear the error indicators, and this can be done by a call to clearerr() that has the prototype; void clearerr(FILE *fp); The condition of reaching end of file (EOF) can be tested for with the predefined macro feof() that has the prototype; int feof(FILE *fp); feof() returns a nonzero value if an end of file error indicator was detected on the specified file stream, and zero if the end of file has not yet been reached. Reading data from a file stream can be achieved using several functions; A single character can be read with fgetc() that has the prototype; int fgetc(FILE *fp); fgetc() returns either the character read converted to an integer, or EOF if an error occurred. Reading a string of data is achieved with fgets(). fgets() attempts to read a string terminated by a newline character and of no more than a specified number of bytes from the file stream. It has the prototype; char *fgets(char s, int n, FILE *fp); A successful call to fgets() results in a string being stored in `s' that is either terminated by a newline character, or that is `n' - 1 characters long, which ever came first. The newline character is retained by fgets(), and a null bytes is appended to the string. If the call fails a NULL pointer is returned. Strings may be written to a stream using fputs() that has the prototype; int fputs(const char *s,FILE *fp); fputs() writes all the characters in the string `s' to the stream `fp' except the null terminating byte. On success, fputs() returns the last character written, on failure it returns EOF. To write a single character to a stream use fputc() that has the prototype; int fputc(int c,FILE *fp); If successful, fputc() returns the character written, otherwise it returns EOF. To read a large block of data, or a record from a stream you can use fread() that has the prototype; size_t fread(void *ptr,size_t size, size_t n, FILE *fp); fread() attempts to read 'n' items, each of length 'size' from the file stream 'fp' into the block of memory pointed to by 'ptr'. To check the success or failure status of fread() use ferror(). The sister function to fread() is fwrite() that has the prototype; size_t fwrite(const void *ptr,size_t size, size_t n,FILE *fp); that writes 'n' items each of length 'size' from the memory area pointed to by 'ptr' to the specified stream 'fp'. Formatted input from a stream is achieved with fscanf() that has the prototype; int fscanf(FILE *fp, const char *format[,address ...]); fscanf() returns the number of fields successfully stored, and EOF on end of file. This short example shows how fscanf() is quite useful for reading numbers from a stream, but hopeless when it comes to strings! #include void main() { FILE *fp; int a; int b; int c; int d; int e; char text[100]; fp = fopen("data.txt","w+"); if(!fp) { perror("Unable to create file"); exit(0); } fprintf(fp,"1 2 3 4 5 \"A line of numbers\""); fflush(fp); if (ferror(fp)) { fputs("Error flushing stream",stderr); exit(1); } rewind(fp); if (ferror(fp)) { fputs("Error rewind stream",stderr); exit(1); } fscanf(fp,"%d %d %d %d %d %s",&a,&b,&c,&d,&e,text); if (ferror(fp)) { fputs("Error reading from stream",stderr); exit(1); } printf("\nfscanf() returned %d %d %d %d %d %s",a,b,c,d,e,text); } As you can see from the example, fprintf() can be used to write formatted data to a stream. If you wish to store the position of a file pointer on a stream, and then later restore it to the same position you can use the functions fgetpos() and fsetpos(). fgetpos() reads the current location of the file pointer and has the prototype; int fgetpos(FILE *fp, fpos_t *pos); fsetpos() repositions the file pointer and has the prototype; int fsetpos(FILE *fp, const fpos_t *fpos); fpos_t is defined in stdio.h. These functions are more convenient than doing an ftell() followed by an fseek(). An open stream can have a new file associated with it in place of the existing file by using the function freopen() that has the prototype; FILE *freopen(const char *name,const char *mode,FILE *fp); freopen() closes the existing stream and then attempts to reopens it with the specified file name. This is useful for redirecting the predefined streams stdin, stdout and stderr to a file or device. For example; if you wish to redirect all output intended to stdout (usually the host computer's display device) to a printer you might use; freopen("LPT1","w",stdout); where LPT1 is the name of the printer device (on a PC host, LPT1 is the name of the parallel port). Predefined I/O Streams There are three predefined I/O streams; stdin, stdout, and stderr. The streams stdin and stdout default to the keyboard and display respectively, but can be redirected on some hardware platforms, such as the PC and under UNIX. The stream stderr defaults to the display and is not usually redirected by the operator. Thus it can be used for the display of error messages even when program output has been redirected, such as with; fputs("Error message",stderr); The functions printf() and puts(), output data to the stream stdout, and can therefore be redirected by the operator of the program. scanf() and gets() accept input from the stream stdin. As an example of file i/o with the PC consider the following short program that does a hex dump of a specified file to the predefined stream stdout, which may be redirected to a file using; dump filename.ext > target.ext #include #include #include #include main(int argc, char *argv[]) { unsigned counter; unsigned char v1[20]; int f1; int x; int n; if (argc != 2) { fputs("\nERROR: Syntax is dump f1\n",stderr); return(1); } f1 = open(argv[1],O_RDONLY); if (f1 == -1) { fprintf(stderr,"\nERROR: Unable to open %s\n",argv[1]); return(1); } fprintf(stdout,"\nDUMP OF FILE %s\n\n",strupr(argv[1])); counter = 0; while(1) { /* Set buffer to zero bytes */ memset(v1,0,20); /* Read buffer from file */ x = _read(f1,&v1,16); /* x will be 0 on EOF or -1 on error */ if (x < 1) break; /* Print file offset to stdout */ fprintf(stdout,"%06d(%05x) ",counter,counter); counter += 16; /* print hex values of buffer to stdout */ for(n = 0; n < 16; n++) fprintf(stdout,"%02x ",v1[n]); /* Print ascii values of buffer to stdout */ for(n = 0; n < 16; n++) { if ((v1[n] > 31) && (v1[n] < 128)) fprintf(stdout,"%c",v1[n]); else fputs(".",stdout); } /* Finish the line with a new line */ fputs("\n",stdout); } /* successful termination */ return(0); } STRINGS The C language has one of the most powerful string handling capabilities of any general purpose computer language. A string is a single dimension array of characters terminated by a zero byte. Strings may be initialised in two ways. Either in the source code where they may be assigned a constant value, as in; int main() { char *p = "System 5"; char name[] = "Test Program" ; } or at run time by the function strcpy() that has the function prototype; char *strcpy(char *destination, char *source); strcpy() copies the string pointed to by source into the location pointed to by destination as in the following example; #include int main() { char name[50]; strcpy(name,"Servile Software"); printf("\nName equals %s",name); } C also allows direct access to each individual byte of the string, so the following is quite permissible; #include int main() { char name[50]; strcpy(name,"Servile Software"); printf("\nName equals %s",name); /* Replace first byte with lower case 's' */ name[0] = 's'; printf("\nName equals %s",name); } The ANSI standard on the C programming language defines the following functions for use with strings; char *strcat(char *dest, char *source) Appends string source to the end of string destination. char *strchr(char *s, int c) Returns a pointer to the first occurence of character 'c' within s. int strcmp(char *s1, char *s2) Compares strings s1 and s2 returning < 0 if s1 is less than s2 == 0 if s1 and s2 are the same > 0 if s1 is greater than s2 int strcoll(char *s1, char *s2) Compares strings s1 and s2 according to the collating sequence set by setlocale() returning < 0 if s1 is less than s2 == 0 if s1 and s2 are the same > 0 if s1 is greater than s2 char *strcpy(char *dest, char *src) Copies string src into string dest. unsigned strcspn(char *s1, char *s2) Returns the length of string s1 that consists entirely of characters not in string s2. unsigned strlen(char *s) Returns the length of string s. char *strncat(char *dest, char *src, unsigned len) Copies at most 'len' characters from string src into string dest. int strncmp(char *s1, char *s2, unsigned len) Compares at most 'len' characters from string s1 with string s2 returning < 0 if s1 is less than s2 == 0 if s1 and s2 are the same > 0 if s1 is greater than s2 char *strncpy(char *dest, char *src, unsigned len) Copies 'len' characters from string src into string dest, truncating or padding with zero bytes as required. char *strpbrk(char *s1, char *s2) Returns a pointer to the first character in string s1 that occurs in string s2. char *strrchr(char *s, int c) Returns a pointer to the last occurence of 'c' within string s. unsigned strspn(char *s1, char *s2) Returns the length of the initial segment of string s1 that consists entirely of characters in string s2. char *strstr(char *s1, char *s2) Returns a pointer to the first occurence of string s2 within string s1, or NULL if string s2 is not found in string s1. char *strtok(char *s1, char *s2) Returns a pointer to the token found in string s1 that is defined by delimiters in string s2. Returns NULLif no tokens are found. The ANSI standard also defines various functions for converting strings into numbers and numbers into strings. Some C compilers include functions to convert strings to upper and lower case, but these functions are not defined in the ANSI standard. However, the ANSI standard does define the functions; toupper() and tolower() that return an integer parameter converted to upper and lowercase respectively. By using these functions we can create our own ANSI compatible versions; #include void strupr(char *source) { char *p; p = source; while(*p) { *p = toupper(*p); p++; } } void strlwr(char *source) { char *p; p = source; while(*p) { *p = tolower(*p); p++; } } int main() { char name[50]; strcpy(name,"Servile Software"); printf("\nName equals %s",name); strupr(name); printf("\nName equals %s",name); strlwr(name); printf("\nName equals %s",name); } C does not impose a maximum length that a string may be, unlike other computer languages. However, some CPUs impose restrictions on the maximum size a block of memory can be. For example, the 8088 family of CPUs, as used by the IBM PC, impose a limit of 64K bytes on a segment of memory. An example program to reverse all the characters in a string. #include #include char *strrev(char *s) { /* Reverses the order of all characters in a string except the null */ /* terminating byte */ char *start; char *end; char tmp; /* Set pointer 'end' to last character in string */ end = s + strlen(s) - 1; /* Preserve pointer to start of string */ start = s; /* Swop characters */ while(end >= s) { tmp = *end; *end = *s; *s = tmp; end--; s++; } return(start); } main() { char text[100]; char *p; strcpy(text,"This is a string of data"); p = strrev(text); printf("\n%s",p); } Strtok() The function strtok() is a very powerful standard C feature for extracting substrings from within a single string. It is used where the substrings are separated by known delimiters, such as commas in the following example; #include #include main() { char data[50]; char *p; strcpy(data,"RED,ORANGE,YELLOW,GREEN,BLUE,INDIGO,VIOLET"); p = strtok(data,","); while(p) { puts(p); p = strtok(NULL,","); }; } Or this program can be written with a for() loop thus; #include #include main() { char data[50]; char *p; strcpy(data,"RED,ORANGE,YELLOW,GREEN,BLUE,INDIGO,VIOLET"); for(strtok(data,","); p; p = strtok(NULL,",")) { puts(p); }; } They both compile to the same code but follow different programming styles. Initially, you call strtok() with the name of the string variable to be parsed, and a second string that contains the known delimiters. Strtok() then returns a pointer to the start of the first substring and replaces the first token with a zero delimiter. Subsequent calls to strtok() can be made in a loop passing NULL as the string to be parsed, and strtok() will return the subsequent substrings. Since strtok() can accept many delimiter characters in the second parameter string we can use it as the basis of a simple word counting program; #include #include #include void main(int argc, char *argv[]) { FILE *fp; char buffer[256]; char *p; long count; if (argc != 2) { fputs("\nERROR: Usage is wordcnt \n",stderr); exit(0); } /* Open file for reading */ fp = fopen(argv[1],"r"); /* Check the open was okay */ if (!fp) { fputs("\nERROR: Cannot open source file\n",stderr); exit(0); } /* Initialise word count */ count = 0; do { /* Read a line of data from the file */ fgets(buffer,255,fp); /* check for an error in the read or EOF */ if (ferror(fp) || feof(fp)) continue; /* count words in received line */ /* Words are defined as separated by the characters */ /* \t(tab) \n(newline) , ; : . ! ? ( ) - and [space] */ p = strtok(buffer,"\t\n,;:.!?()- "); while(p) { count++; p = strtok(NULL,"\t\n,;:.!?()- "); } } while(!ferror(fp) && !feof(fp)); /* Finished reading. Was it due to an error? */ if (ferror(fp)) { fputs("\nERROR: Reading source file\n",stderr); fclose(fp); exit(0); } /* Reading finished due to EOF, quite valid so print count */ printf("\nFile %s contains %ld words\n",argv[1],count); fclose(fp); } Converting Numbers To And From Strings All C compilers provide a facility for converting numbers to strings. This being sprintf(). However, as happens sprintf() is a multi-purpose function that is therefore large and slow. The following function ITOS() accepts two parameters, the first being a signed integer and the second being a pointer to a character string. It then copies the integer into the memory pointed to by the character pointer. As with sprintf() ITOS() does not check that the target string is long enough to accept the result of the conversion. You should then ensure that the target string is long enough. Example function for copying a signed integer into a string; void ITOS(long x, char *ptr) { /* Convert a signed decimal integer to a string */ long pt[9] = { 100000000, 10000000, 1000000, 100000, 10000, 1000, 100, 10, 1 }; int n; /* Check sign */ if (x < 0) { *ptr++ = '-'; /* Convert x to absolute */ x = 0 - x; } for(n = 0; n < 9; n++) { if (x > pt[n]) { *ptr++ = '0' + x / pt[n]; x %= pt[n]; } } return; } To convert a string to a floating point number, C provides two functions; atof() and strtod(). atof() has the prototype; double atof(const char *s); strtod has the prototype; double strtod(const char *s,char **endptr); Both functions scan the string and convert it as far as they can, until they come across a character they don't understand. The difference between the two functions is that if strtod() is passed a character pointer for parameter `endptr', it sets that pointer to the first character in the string that terminated the conversion. Because of its better error reporting, by way of endptr, strtod() is often preferred to atof(). To convert a string to an integer use atoi() that has the prototype; int atoi(const char *s); atoi() does not check for an overflow, and the results are undefined! atol() is a similar function but returns a long. Alternatively, you can use strtol() and stroul() instead that have better error checking. TEXT HANDLING Human languages write information down as `text'. This is comprised of words, figures and punctuation. The words being made up of upper case and lower case letters. Processing text with a computer is a commonly required task, and yet quite a difficult one. The ANSI C definitions include string processing functions that are by their nature sensitive to case. That is the letter `A' is seen as distinct from the letter `a'. This is the first problem that must be overcome by the programmer. Fortunately both Borland's Turbo C compilers and Microsoft's C compilers include case insensitive forms of the string functions. stricmp() for example is the case insensitive form of strcmp(), and strnicmp() is the case insensitive form of strncmp(). If you are concerned about writing portable code, then you must restrict yourself to the ANSI C functions, and write your own case insensitive functions using the tools provided. Here is a simple implementation of a case insensitive version of strstr(). The function simply makes a copy of the parameter strings, converts those copies both to upper case and then does a standard strstr() on the copies. The offset of the target string within the source string will be the same for the copy as the original, and so it can be returned relative to the parameter string. char *stristr(char *s1, char *s2) { char c1[1000]; char c2[1000]; char *p; strcpy(c1,s1); strcpy(c2,s2); strupr(c1); strupr(c2); p = strstr(c1,c2); if (p) return s1 + (p - c1); return NULL; } This function scans a string, s1 looking for the word held in s2. The word must be a complete word, not simply a character pattern, for the function to return true. It makes use of the stristr() function described previously. int word_in(char *s1,char *s2) { /* return non-zero if s2 occurs as a word in s1 */ char *p; char *q; int ok; ok = 0; q = s1; do { /* Locate character occurence s2 in s1 */ p = stristr(q,s2); if (p) { /* Found */ ok = 1; if (p > s1) { /* Check previous character */ if (*(p - 1) >= 'A' && *(p - 1) <= 'z') ok = 0; } /* Move p to end of character set */ p += strlen(s2); if (*p) { /* Check character following */ if (*p >= 'A' && *p <= 'z') ok = 0; } } q = p; } while(p && !ok); return ok; } Some more useful functions for dealing with text are truncstr() that truncates a string; void truncstr(char *p,int num) { /* Truncate string by losing last num characters */ if (num < strlen(p)) p[strlen(p) - num] = 0; } trim() that removes trailing spaces from the end of a string; void trim(char *text) { /* remove trailing spaces */ char *p; p = &text[strlen(text) - 1]; while(*p == 32 && p >= text) *p-- = 0; } strlench() that changes the length of a string by adding or deleting characters; void strlench(char *p,int num) { /* Change length of string by adding or deleting characters */ if (num > 0) memmove(p + num,p,strlen(p) + 1); else { num = 0 - num; memmove(p,p + num,strlen(p) + 1); } } strins() that inserts a string into another string; void strins(char *p, char *q) { /* Insert string q into p */ strlench(p,strlen(q)); strncpy(p,q,strlen(q)); } strchg() that replaces all occurences of one sub-string with another within a target string; void strchg(char *data, char *s1, char *s2) { /* Replace all occurences of s1 with s2 */ char *p; char changed; do { changed = 0; p = strstr(data,s1); if (p) { /* Delete original string */ strlench(p,0 - strlen(s1)); /* Insert replacement string */ strins(p,s2); changed = 1; } } while(changed); } TIME C provides a function, time(), which reads the computer's system clock to return the system time as a number of seconds since midnight on January the first, 1970. However, this value can be converted to a useful string by the function ctime() as illustrated in the following example; #include #include int main() { /* Structure to hold time, as defined in time.h */ time_t t; /* Get system date and time from computer */ t = time(NULL); printf("Today's date and time: %s\n",ctime(&t)); } The string returned by ctime() is comprised of seven fields; Day of the week, Month of the year, Date of the day of the month, hour, minutes, seconds, century of the year terminated by a newline character and null terminating byte. Since the fields always occupy the same width, slicing operations can be carried out on the string with ease. The following program defines a structure `time' and a function gettime() that extracts the hours, minutes and seconds of the current time and places them in the structure; #include #include struct time { int ti_min; /* Minutes */ int ti_hour; /* Hours */ int ti_sec; /* Seconds */ }; void gettime(struct time *now) { time_t t; char temp[26]; char *ts; /* Get system date and time from computer */ t = time(NULL); /* Translate dat and time into a string */ strcpy(temp,ctime(&t)); /* Copy out just time part of string */ temp[19] = 0; ts = &temp[11]; /* Scan time string and copy into time structure */ sscanf(ts,"%2d:%2d:%2d",&now->ti_hour,&now->ti_min,&now- >ti_sec); } int main() { struct time now; gettime(&now); printf("\nThe time is %02d:%02d:%02d",now.ti_hour,now.ti_min,now.ti_sec); } The ANSI standard on C does actually provide a function ready made to convert the value returned by time() into a structure; #include #include int main() { time_t t; struct tm *tb; /* Get time into t */ t = time(NULL); /* Convert time value t into structure pointed to by tb */ tb = localtime(&t); printf("\nTime is %02d:%02d:%02d",tb->tm_hour,tb->tm_min,tb- >tm_sec); } The structure 'tm' is defined in time.h as; struct tm { int tm_sec; int tm_min; int tm_hour; int tm_mday; int tm_mon; int tm_year; int tm_wday; int tm_yday; int tm_isdst; }; Timers Often a program must determine the date and time from the host computer's non-volatile RAM. There are several time functions provided by the ANSI standard on C that allow a program to retrieve, from the host computer, the current date and time; time() returns the number of seconds that have elapsed since midnight on January the 1st 1970. It has the prototype; time_t time(time_t *timer); time() fills in the time_t variable sent as a parameter and returns the same value. You can call time() with a NULL parameter and just collect the return value thus; #include void main() { time_t now; now = time(NULL); } asctime() converts a time block to a twenty six character string of the format; Wed Oct 14 10:23:45 1992\n\0 asctime() has the prototype; char *asctime(const struct tm *tblock); ctime() converts a time value (as returned by time()) into a twenty six chracter string of the same format as asctime(). For example; #include #include void main() { time_t now; char date[30]; now = time(NULL); strcpy(date,ctime(&now)); } difftime() returns the difference, in seconds, between two values (as returned by time()). This can be useful for testing the elapsed time between two events, the time a function takes to execute, and for creating consistent delays that are irrelevant of the host computer. An example delay program; #include #include void DELAY(int period) { time_t start; start = time(NULL); while(time(NULL) < start + period) ; } void main() { printf("\nStarting delay now....(please wait 5 seconds)"); DELAY(5); puts("\nOkay, I've finished!"); } gmtime() converts a local time value (as returned by time()) to the GMT time and stores it in a time block. This function depends upon the global variable timezone being set. The time block is a predefined structure (declared in time.h) as follows; struct tm { int tm_sec; int tm_min; int tm_hour; int tm_mday; int tm_mon; int tm_year; int tm_wday; int tm_yday; int tm_isdst; }; tm_mday records the day of the month, ranging from 1 to 31; tm_wday is the day of the week with Sunday being represented by 0; the year is recorded less 1900; tm_isdst is a flag to show whether daylight saving time is in effect. The actual names of the structure and its elements may vary from compiler to compiler, but the structure should be the same in essence. mktime() converts a time block to a calendar format. It follows the prototype; time_t mktime(struct tm *t); The following example allows entry of a date, and uses mktime() to calculate the day of the week appropriate to that date. Only dates from the first of January 1970 are recognisable by the time functions. #include #include #include void main() { struct tm tsruct; int okay; char data[100]; char *p; char *wday[] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" , "prior to 1970, thus not known" }; do { okay = 0; printf("\nEnter a date as dd/mm/yy "); p = fgets(data,8,stdin); p = strtok(data,"/"); if (p != NULL) tsruct.tm_mday = atoi(p); else continue; p = strtok(NULL,"/"); if (p != NULL) tsruct.tm_mon = atoi(p); else continue; p = strtok(NULL,"/"); if (p != NULL) tsruct.tm_year = atoi(p); else continue; okay = 1; } while(!okay); tsruct.tm_hour = 0; tsruct.tm_min = 0; tsruct.tm_sec = 1; tsruct.tm_isdst = -1; /* Now get day of the week */ if (mktime(&tsruct) == -1) tsruct.tm_wday = 7; printf("That was %s\n",wday[tsruct.tm_wday]); } mktime() also makes the neccessary adjustments for values out of range, this can be utilised for discovering what the date will be in n number of days time thus; #include #include #include void main() { struct tm *tsruct; time_t today; today = time(NULL); tsruct = localtime(&today); tsruct->tm_mday += 10; mktime(tsruct); printf("In ten days it will be %02d/%02d/%2d\n", tsruct- >tm_mday,tsruct->tm_mon + 1,tsruct->tm_year); } This program uses Julian Dates to decide any day of the week since the 1st of October 1582 when the Gregorian calendar was introduced. char *WDAY(int day, int month, int year) { /* Returns a pointer to a string representing the day of the week */ static char *cday[] = { "Saturday","Sunday","Monday","Tuesday", "Wednesday","Thursday","Friday" }; double yy; double yt; double j; int y1; int y4; int x; yy = year / 100; y1 = (int)(yy); yt = year / 400; y4 = (int)(yt); x = 0; if (month < 3) { year--; x = 12; } j = day + (int)(365.25*year); j += (int)(30.6001 * (month + 1 + x)) - y1 + y4; if (yy == y1 && yt != y4 && month < 3) j++; j = 1 + j - 7 * (int)(j/7); if (j > 6) j -= 7; return(cday[j]); } With time() and difftime() we can create a timer for testing the execution times of functions thus; #include #include main() { time_t now; time_t then; double elapsed; int n; now = time(NULL); /* This loop is adjustable for multiple passes */ for(n = 0; n < 5000; n++) /* Call the function to test */ func(); then = time(NULL); elapsed = difftime(then,now); printf("\nElapsed seconds==%lf\n",elapsed); } By way of time() and ctime() the current system date and time can be retrieved from the host computer thus; #include #include main() { time_t now; char *date; int n; /* Get system time */ now = time(NULL); /* Convert system time to a string */ date = ctime(&now); /*Display system time */ printf("\nIt is %s",date); } time_t is a type defined in time.h as the type of variable returned by time(). This type may vary from compiler to compiler, and therefore is represented by the type "time_t". HEADER FILES Function prototypes for library functions supplied with the C compiler, and standard macros are declared in header files. The ANSI standard on the C programming language lists the following header files; Header file Description assert.h Defines the assert debugging macro ctype.h Character classification and conversion macros errno.h Constant mnemonics for error codes float.h Defines implementation specific macros for dealing with floating point mathematics limits.h Defines implementation specific limits on type values locale.h Country specific parameters math.h Prototypes for mathematics functions setjmp.h Defines typedef and functions for setjmp/longjmp signal.h Constants and declarations for use by signal() and raise() stdarg.h Macros for dealing with argument lists stddef.h Common data types and macros stdio.h Types and macros required for standard I/O stdlib.h Prototypes of commonly used functions and miscellany string.h String manipulation function prototypes time.h Structures for time conversion routines DEBUGGING The ANSI standard on C includes a macro function for debugging called assert(). This expands to an if() statement, which if it returns true terminates the program and outputs to the standard error stream a message comprised of: Assertion failed: , file , line Abnormal program termination For example, the following program accidentally assigns a zero value to a pointer! #include #include main() { /* Demonstration of assert */ int *ptr; int x; x = 0; /* Whoops! error in this line! */ ptr = x; assert(ptr != NULL); } When run, this program terminates with the message: Assertion failed: ptr != 0, file TEST.C, line 16 Abnormal program termination When a program is running okay, the assert() functions can be removed from the compiled program by simply adding the line; #define NDEBUG before the #include line. Effectively the assert functions are commented out in the preprocessed source before compilation, this means that the assert expressions are not evaluated, and thus cannot cause any side effects. FLOAT ERRORS Floating point numbers are decimal fractions, decimal fractions do not accurately equate to normal fractions as not every number will divide precisely by ten. This creates the potential for rounding errors in calculations that use floating point numbers. The following program illustrates one such example of rounding error problems; #include void main() { float number; for(number = 1; number > 0.4; number -= 0.01) printf("\n%f",number); } At about 0.47 (depending upon the host computer and compiler) the program starts to store an inaccurate value for 'number'. This problem can be minimised by using longer floating point numbers, doubles or long doubles that have larger storage space allocated to them. For really accurate work though, you should use integers and only convert to a floating point number for display. You also should notice that most C compilers default floating point numbers to `doubles' and when using `float' types have to convert the double down to a float! ERROR HANDLING When a system error occurs within a program, for example when an attempt to open a file fails, it is helpful to the program's user to display a message reporting the failure. Equally it is useful to the program's developer to know why the error occurred, or at least as much about it as possible. To this end the ANSI standard on C describes a function, perror(), which has the prototype; void perror(const char *s); and is used to display an error message. The program's own prefixed error message is passed to perror() as the string parameter. This error message is displayed by perror() followed by the host's system error separated by a colon. The following example illustrates a use for perror(); #include void main() { FILE *fp; char fname[] = "none.xyz"; fp = fopen(fname,"r"); if(!fp) perror(fname); return; } If the fopen() operation fails, a message similar to; none.xyz: No such file or directory is displayed. You should note, perror() sends its output to the predefined stream `stderr', which is usually the host computer's display unit. perror() finds its message from the host computer via the global variable 'errno' that is set by most, but not all system functions. Unpleasant errors might justify the use of abort(). abort() is a function that terminates the running program with a message; "Abnormal program termination" and returns an exit code of 3 to the parent process or operating system. Critical Error Handling With The IBM PC AND DOS The IBM PC DOS operating system provides a user amendable critical error handling function. This function is usually discovered by attempting to write to a disk drive that does not have a disk in it, in which case the familiar; Not ready error writing drive A Abort Retry Ignore? Message is displayed on the screen. Fine when it occurs from within a DOS program, not so fine from within your own program! The following example program shows how to redirect the DOS critical error interrupt to your own function; /* DOS critical error handler test */ #include #include void interrupt new_int(); void interrupt (*old_int)(); char status; main() { FILE *fp; old_int = getvect(0x24); /* Set critical error handler to my function */ setvect(0x24,new_int); /* Generate an error by not having a disc in drive A */ fp = fopen("a:\\data.txt","w+"); /* Display error status returned */ printf("\nStatus == %d",status); } void interrupt new_int() { /* set global error code */ status = _DI; /* ignore error and return */ _AL = 0; } When the DOS critical error interrupt is called, a status message is passed in the low byte of the DI register. This message is one of; Code Meaning 00 Write-protect error 01 Unknown unit 02 Drive not ready 03 Unknown command 04 Data error, bad CRC 05 Bad request structure length 06 Seek error 07 Unknown media type 08 Sector not found 09 Printer out of paper 0A Write error 0B Read error 0C General failure Your critical error interrupt handler can transfer this status message into a global variable, and then set the result message held in register AL to one of; Code Action 00 Ignore error 01 Retry 02 Terminate program 03 Fail (Available with DOS 3.3 and above) If you choose to set AL to 02, terminate program, you should ensure ALL files are closed first since DOS will terminate the program abruptly, leaving files open and memory allocated, not a pretty state to be in! The example program shown returns an ignore status from the critical error interrupt, and leaves the checking of any errors to the program itself. So, in this example after the call to fopen() we could check the return value in fp, and if it reveals an error (NULL in this case) we could then check the global variable status and act accordingly, perhaps displaying a polite message to the user to put a disk in the floppy drive and ensure that the door is closed. The following is a practical function for checking whether a specified disc drive can be accessed. It should be used with the earlier critical error handler and global variable `status'. int DISCOK(int drive) { /* Checks for whether a disc can be read */ /* Returns false (zero) on error */ /* Thus if(!DISCOK(drive)) */ /* error(); */ unsigned char buffer[25]; /* Assume okay */ status = 0; /* If already logged to disc, return okay */ if ('A' + drive == diry[0]) return(1); /* Attempt to read disc */ memset(buffer,0,20); sprintf(buffer,"%c:$$$.$$$",'A'+drive); _open(buffer,O_RDONLY); /* Check critical error handler status */ if (status == 0) return(1); /* Disc cannot be read */ return(0); } CAST Casting tells the compiler what a data type is, and can be used to change a data type. For example, consider the following; #include void main() { int x; int y; x = 10; y = 3; printf("\n%lf",x / y); } The printf() function has been told to expect a double. However, the compiler sees the variables `x' and `y' as integers, and an error occurs! To make this example work we must tell the compiler that the result of the expression x / y is a double, this is done with a cast thus; #include void main() { int x; int y; x = 10; y = 3; printf("\n%lf",(double)(x / y)); } Notice the data type `double' is enclosed by parenthesis, and so is the expression to convert. But now, the compiler knows that the result of the expression is a double, but it still knows that the variables `x' and `y' are integers and so an integer division will be carried out. We have to cast the constants thus; #include void main() { int x; int y; x = 10; y = 3; printf("\n%lf",(double)(x) / (double)(y)); } Because both of the constants are doubles, the compiler knows that the outcome of the expression will also be a double. THE IMPORTANCE OF PROTOTYPING Prototyping a function involves letting the compiler know in advance what type of values a function will receive and return. For example, lets look at strtok(). This has the prototype; char *strtok(char *s1, const char *s2); This prototype tells the compiler that strtok() will return a character pointer, the first received parameter will be a pointer to a character string, and that string can be changed by strtok(), and the last parameter will be a pointer to a character string that strtok() cannot change. The compiler knows how much space to allocate for the return parameter, sizeof(char *), but without a prototype for the function the compiler will assume that the return value of strtok() is an integer, and will allocate space for a return type of int, that is sizeof(int). If an integer and a character pointer occupy the same number of bytes on the host computer no major problems will occur, but if a character pointer occupies more space than an integer, then the compiler wont have allocated enough space for the return value and the return from a call to strtok() will overwrite some other bit of memory. If, as so often happens the return value is returned via the stack, the results of confusing the compiler can be disastrous! Thankfully most C compilers will warn the programmer if a call to a function has been made without a prototype, so that you can add the required function prototypes. Consider the following example that will not compile on most modern C compilers due to the nasty error in it; #include int FUNCA(int x, int y) { return(MULT(x,y)); } double MULT(double x, double y) { return(x * y); } main() { printf("\n%d",FUNCA(5,5)); } When the compiler first encounters the function MULT() it is as a call from within FUNCA(). In the absence of any prototype for MULT() the compiler assumes that MULT() returns an integer. When the compiler finds the definition for function MULT() it sees that a return of type double has been declared. The compiler then reports an error in the compilation saying something like; "Type mismatch in redclaration of function 'MULT'" What the compiler is really trying to say is, prototype your functions before using them! If this example did compile, and was then run it probably would crash the computer's stack and cause a system hang. POINTERS TO FUNCTIONS C allows a pointer to point to the address of a function, and this pointer to be called rather than specifying the function. This is used by interrupt changing functions and may be used for indexing functions rather than using switch statements. For example; #include #include double (*fp[7])(double x); void main() { double x; int p; fp[0] = sin; fp[1] = cos; fp[2] = acos; fp[3] = asin; fp[4] = tan; fp[5] = atan; fp[6] = ceil; p = 4; x = fp[p](1.5); printf("\nResult %lf",x); } This example program defines an array of pointers to functions, (*fp[])() that are then called dependant upon the value in the indexing variable p. This program could also be written; #include #include void main() { double x; int p; p = 4; switch(p) { case 0 : x = sin(1.5); break; case 1 : x = cos(1.5); break; case 2 : x = acos(1.5); break; case 3 : x = asin(1.5); break; case 4 : x = tan(1.5); break; case 5 : x = atan(1.5); break; case 6 : x = ceil(1.5); break; } puts("\nResult %lf",x); } The first example, using pointers to the functions, compiles into much smaller code and executes faster than the second example. The table of pointers to functions is a useful facility when writing language interpreters, the program compares an entered instruction against a table of key words that results in an index variable being set and then the program simply needs to call the function pointer indexed by the variable, rather than wading through a lengthy switch() statement. DANGEROUS PITFALLS One of the most dangerous pitfalls can occur with the use of gets(). This function accepts input from the stream stdin until it receives a newline character, which it does not pass to the program. All the data it receives is stored in memory starting at the address of the specified string, and quite happily overflowing into other variables! This danger can be avoided by using fgets() that allows a maximum number of characters to be specified, so you can avoid overflow problems. Notice though that fgets() does retain the newline character scanf() is another function best avoided. It accepts input from stdin and stores the received data at the addresses provided to it. If those addresses are not really addresses where the data ends up is anybodys guess! This example is okay, since scanf() has been told to store the data at the addresses occupied by the two variables `x' and `y'. void main() { int x; int y; scanf("%d%d",&x,&y); } But in this example scanf() has been told to store the data at the addresses suggested by the current values of `x' and `y'! An easy and common mistake to make, and yet one that can have very peculiar effects. void main() { int x; int y; scanf("%d%d",x,y); } The answer is, don't use scanf(), use fgets() and parse your string manually using the standard library functions strtod(), strtol() and strtoul(). Here is the basis of a simple input string parser that returns the individual input fields from an entered string; #include #include void main() { char input[80]; char *p; puts("\nEnter a string "); fgets(input,79,stdin); /* now parse string for input fields */ puts("The fields entered are:"); p = strtok(input,", "); while(p) { puts(p); p = strtok(NULL,", "); } } SIZEOF A preprocessor instruction, `sizeof', returns the size of an item, be it a structure, pointer, string or whatever. However! take care when using `sizeof'. Consider the following program; #include #include char string1[80]; char *text = "This is a string of data" ; void main() { /* Initialise string1 correctly */ memset(string1,0,sizeof(string1)); /* Copy some text into string1 ? */ memcpy(string1,text,sizeof(text)); /* Display string1 */ printf("\nString 1 = %s\n",string1); } What it is meant to do is initialise all 80 elements of string1 to zeroes, which it does alright, and then copy the constant string `text' into the variable `string1'. However, variable text is a pointer, and so the sizeof(text) instruction returns the size of the character pointer (perhaps two bytes) rather than the length of the string pointed to by the pointer! If the length of the string pointed to by `text' happened to be the same as the size of a character pointer then no error would be noticed. INTERRUPTS The IBM PC BIOS and DOS contain functions that may be called by a program by way of the function's interrupt number. The address of the function assigned to each interrupt is recorded in a table in RAM called the interrupt vector table. By changing the address of an interrupt vector a program can effectively disable the original interrupt function and divert any calls to it to its own function. This was done by the critical error handler described in the section on error handling. Borland's Turbo C provides two library functions for reading and changing an interrupt vector. These are: setvect() and getvect(). The corresponding Microsoft C library functions are: _dos_getvect() and _dos_setvect(). getvect() has the function prototype; void interrupt(*getvect(int interrupt_no))(); setvect() has the prototype; void setvect(int interrupt_no, void interrupt(*func)()); To read and save the address of an existing interrupt a program uses getvect() thus; /* Declare variable to record old interrupt */ void interrupt(*old)(void); main() { /* get old interrupt vector */ old = getvect(0x1C); . . . } Where 0x1C is the interrupt vector to be retrieved. To then set the interrupt vector to a new address, our own function, we use setvect() thus; void interrupt new(void) { . . /* New interrupt function */ . . . } main() { . . . setvect(0x1C,new); . . . . } There are two important points to note about interrupts; First, if the interrupt is called by external events then before changing the vector you MUST disable the interrupt callers using disable() and then re-enable the interrupts after the vector has been changed using enable(). If a call is made to the interrupt while the vector is being changed ANYTHING could happen! Secondly, before your program terminates and returns to DOS you must reset any changed interrupt vectors! The exception to this is the critical error handler interrupt vector that is restored automatically by DOS, so your program needn't bother restoring it. This example program hooks the PC clock timer interrupt to provide a background clock process while the rest of the program continues to run. If included with your own program that requires a constantly displayed clock on screen, you need only amend the display coordinates in the call to puttext(). Sincle the closk display code is called by a hardware issued interrupt, your program can start the clock and forget it until it terminates. /* Compile in LARGE memory model */ #include #include #include #include #include enum { FALSE, TRUE }; #define COLOUR (BLUE << 4) | YELLOW #define BIOS_TIMER 0x1C static unsigned installed = FALSE; static void interrupt (*old_tick) (void); static void interrupt tick (void) { int i; struct tm *now; time_t this_time; char time_buf[9]; static time_t last_time = 0L; static char video_buf[20] = { ' ', COLOUR, '0', COLOUR, '0', COLOUR, ':', COLOUR, '0', COLOUR, '0', COLOUR, ':', COLOUR, '0', COLOUR, '0', COLOUR, ' ', COLOUR }; enable (); if (time (&this_time) != last_time) { last_time = this_time; now = localtime(&this_time); sprintf(time_buf, "%02d:%02d.%02d",now->tm_hour,now- >tm_min,now->tm_sec); for (i = 0; i < 8; i++) { video_buf[(i + 1) << 1] = time_buf[i]; } puttext (71, 1, 80, 1, video_buf); } old_tick (); } void stop_clock (void) { if (installed) { setvect (BIOS_TIMER, old_tick); installed = FALSE; } } void start_clock (void) { static unsigned first_time = TRUE; if (!installed) { if (first_time) { atexit (stop_clock); first_time = FALSE; } old_tick = getvect (BIOS_TIMER); setvect (BIOS_TIMER, tick); installed = TRUE; } } SIGNAL Interrupts raised by the host computer can be trapped and diverted in several ways. A simple method is to use signal(). Signal() takes two parameters in the form; void (*signal (int sig, void (*func) (int))) (int); The first parameter, `sig' is the signal to be caught. These are often predefined by the header file `signal.h'. The second parameter is a pointer to a function to be called when the signal is raised. This can either be a user function, or a macro defined in the header file `signal.h' to do some arbitrary task, such as ignore the signal for example. On a PC platform, it is often useful to disable the `ctrl-break' key combination that is used to terminate a running program by the user. The following PC signal() call replaces the predefined signal `SIGINT', which equates to the ctrl-break interrupt request, with the predefined macro `SIG-IGN', ignore the request; signal(SIGINT,SIG_IGN); This example catches floating point errors on a PC, and zero divisions! #include #include void (*old_sig)(); void catch(int sig) { printf("Catch was called with: %d\n",sig); } void main() { int a; int b; old_sig = signal(SIGFPE,catch); a = 0; b = 10 / a; /* Restore original handler before exiting! */ signal(SIGFPE,old_sig); } SORTING AND SEARCHING The ANSI C standard defines qsort(), a function for sorting a table of data. The function follows the format; qsort(void *base,size_t elements,size_t width,int (*cmp)(void *, void *)); The following short program illustrates the use of qsort() with a character array. #include main() { int n; char data[10][20]; /* Initialise some arbirary data */ strcpy(data[0],"RED"); strcpy(data[1],"BLUE"); strcpy(data[2],"GREEN"); strcpy(data[3],"YELLOW"); strcpy(data[4],"INDIGO"); strcpy(data[5],"BROWN"); strcpy(data[6],"BLACK"); strcpy(data[7],"ORANGE"); strcpy(data[8],"PINK"); strcpy(data[9],"CYAN"); /* Sort the data table */ qsort(data[0],10,20,strcmp); /* Print the data table */ for(n = 0; n < 10; n++) puts(data[n]); } Here is a program that implements the shell sort algorithm (this one is based on the routine in K & R), which sorts arrays of pointers based upon the data pointed to by the pointers; #include #include #include #define LINELEN 80 #define MAXLINES 2000 char *lines[MAXLINES]; int lastone; void SHELL(void); void SHELL() { /* SHELL Sort Courtesy of K & R */ int gap; int i; int j; char temp[LINELEN]; for(gap = lastone / 2; gap > 0; gap /= 2) for(i = gap; i < lastone; i++) for(j = i - gap; j >= 0 && strcmp(lines[j] , lines[j + gap]) > 0; j -= gap) { strcpy(temp,lines[j]); strcpy(lines[j] , lines[j + gap]); strcpy(lines[j + gap] , temp); } } void main(int argc, char *argv[]) { FILE *fp; char buff[100]; int n; /* Check command line parameter has been given */ if (argc != 2) { printf("\nError: Usage is SERVSORT file"); exit(0); } /* Attempt to open file for updating */ fp = fopen(argv[1],"r+"); if (fp == NULL) { printf("\nError: Unable to open %s",argv[1]); exit(0); } /* Initialise element counter to zero */ lastone = 0; /* Read file to be sorted */ while((fgets(buff,100,fp)) != NULL) { /* Allocate memory block*/ lines[lastone] = malloc(LINELEN); if (lines[lastone] == NULL) { printf("\nError: Unable to allocate memory"); fclose(fp); exit(0); } strcpy(lines[lastone],buff); lastone++; if (lastone > MAXLINES) { printf("\nError: Too many lines in source file"); exit(0); } } /* Call sort function */ SHELL(); /* Close file */ fclose(fp); /* Reopen file in create mode */ fp = fopen(argv[1],"w+"); /* Copy sorted data from memory to disk */ for(n = 0; n < lastone; n++) fputs(lines[n],fp); /* Close file finally */ fclose(fp); /* Return to calling program */ return(1); } If we want to use qsort() with a table of pointers we have to be a bit more clever than usual. This example uses the colours again, but this time they are stored in main memory and indexed by a table of pointers. Because we have a table of pointers to sort there are two differences between this program's qsort() and the previous one; First we can't use strcmp() as the qsort() comparison function, secondly the width of the table being sorted is sizeof(char *), that is the size of a character pointer. Notice the comparison function cmp() that receives two parameters, both are pointers to a pointer. qsort() sends to this function the values held in data[], which are in turn pointers to the data. So we need to use this indirection to locate the data, otherwise we would be comparing the addresses at which the data is held rather than the data itself! #include #include /* Function prototype for comparison function */ int (cmp)(char **,char **); int cmp(char **s1, char **s2) { /* comparison function using pointers to pointers */ return(strcmp(*s1,*s2)); } main() { int n; char *data[10]; for(n = 0; n < 10; n++) data[n] = malloc(20); strcpy(data[0],"RED"); strcpy(data[1],"BLUE"); strcpy(data[2],"GREEN"); strcpy(data[3],"YELLOW"); strcpy(data[4],"INDIGO"); strcpy(data[5],"BROWN"); strcpy(data[6],"BLACK"); strcpy(data[7],"ORANGE"); strcpy(data[8],"PINK"); strcpy(data[9],"CYAN"); /* The data table is comprised of pointers */ /* so the call to qsort() must reflect this */ qsort(data,10,sizeof(char *),cmp); for(n = 0; n < 10; n++) puts(data[n]); } The quick sort is a fast sorting algorithm that works by subdividing the data table into two sub-tables and then subdividing the sub-tables. As it subdivides the table, so it compares the elements in the table and swaps them as required. The following program implements the quick sort algorithm, which is usually already used by qsort(); #include #define MAXELE 2000 char data[10][20]; int lastone; void QKSORT() { /* Implementation of QUICKSORT algorithm */ int i; int j; int l; int p; int r; int s; char temp[100]; static int sl[MAXELE][2]; /* sl[] is an index to the sub-table */ l = 0; r = lastone; p = 0; do { while(l < r) { i = l; j = r; s = -1; while(i < j) { if (strcmp(data[i],data[j]) > 0) { strcpy(temp,data[i]); strcpy(data[i],data[j]); strcpy(data[j],temp); s = 0 - s; } if (s == 1) i++; else j--; } if (i + 1 < r) { p++; sl[p][0] = i + 1; sl[p][1] = r; } r = i - 1; } if (p != 0) { l = sl[p][0]; r = sl[p][1]; p--; } } while(p > 0); } main() { int n; /* Initialise arbitrary data */ strcpy(data[0],"RED"); strcpy(data[1],"BLUE"); strcpy(data[2],"GREEN"); strcpy(data[3],"YELLOW"); strcpy(data[4],"INDIGO"); strcpy(data[5],"BROWN"); strcpy(data[6],"BLACK"); strcpy(data[7],"ORANGE"); strcpy(data[8],"PINK"); strcpy(data[9],"CYAN"); /* Set last element indicator */ lastone = 9; /* Call quick sort function */ QKSORT(); /* Display sorted list */ for(n = 0; n < 10; n++) puts(data[n]); } A table sorted into ascending order can be searched with bsearch(), this takes the format; bsearch(key,base,num_elements,width,int (*cmp)(void *, void *)); bsearch() returns a pointer to the first element in the table that matches the key, or zero if no match is found. Or you can write your own binary search function thus; int BSRCH(char *key, void *data, int numele, int width) { /* A binary search function returning one if found */ /* Zero if not found */ int bp; int tp; int mp; int result; char *p; bp = 0; tp = numele; mp = (tp + bp) / 2; /* Locate element mp in table by assigning pointer to start */ /* and incrementing it by width * mp */ p = data; p += width * mp; while((result = strcmp(p,key)) != 0) { if (mp >= tp) /* Not found! */ return(0); if (result < 0) bp = mp + 1; else tp = mp - 1; mp = (bp + tp) / 2; p = data; p += width * mp; } return(1); } void main() { int result; char data[10][20]; /* Initialise some arbirary data */ strcpy(data[0],"RED"); strcpy(data[1],"BLUE"); strcpy(data[2],"GREEN"); strcpy(data[3],"YELLOW"); strcpy(data[4],"INDIGO"); strcpy(data[5],"BROWN"); strcpy(data[6],"BLACK"); strcpy(data[7],"ORANGE"); strcpy(data[8],"PINK"); strcpy(data[9],"CYAN"); /* Sort the data table */ qsort(data[0],10,20,strcmp); result = BSRCH("CYAN",data[0],10,20); printf("\n%s\n",(result == 0) ? "Not found" : "Located okay"); } There are other sorting algorithms as well. This program incorporates the QUICK SORT, BUBBLE SORT, FAST BUBBLE SORT, INSERTION SORT and SHELL SORT for comparing how each performs on a random 1000 item string list; #include #include #include char data[1000][4]; char save[1000][4]; int lastone; void INITDATA(void); void QKSORT(void); void SHELL(void); void BUBBLE(void); void FBUBBLE(void); void INSERTION(void); void MKDATA(void); void QKSORT() { /* Implementation of QUICKSORT algorithm */ int i; int j; int l; int p; int r; int s; char temp[20]; static int sl[1000][2]; l = 0; r = lastone; p = 0; do { while(l < r) { i = l; j = r; s = -1; while(i < j) { if (strcmp(data[i],data[j]) > 0) { strcpy(temp,data[i]); strcpy(data[i],data[j]); strcpy(data[j],temp); s = 0 - s; } if (s == 1) i++; else j--; } if (i + 1 < r) { p++; sl[p][0] = i + 1; sl[p][1] = r; } r = i - 1; } if (p != 0) { l = sl[p][0]; r = sl[p][1]; p--; } } while(p > 0); } void SHELL() { /* SHELL Sort Courtesy of K & R */ int gap; int i; int j; char temp[20]; for(gap = lastone / 2; gap > 0; gap /= 2) for(i = gap; i < lastone; i++) for(j = i - gap; j >= 0 && strcmp(data[j] , data[j + gap]) > 0; j -= gap) { strcpy(temp,data[j]); strcpy(data[j] , data[j + gap]); strcpy(data[j + gap] , temp); } } void BUBBLE() { int a; int b; char temp[20]; for(a = lastone; a >= 0; a--) { for(b = 0; b < a; b++) { if(strcmp(data[b],data[b + 1]) > 0) { strcpy(temp,data[b]); strcpy(data[b] , data[b + 1]); strcpy(data[b + 1] , temp); } } } } void FBUBBLE() { /* bubble sort with swap flag*/ int a; int b; int s; char temp[20]; s = 1; for(a = lastone; a >= 0 && s == 1; a--) { s = 0; for(b = 0; b < a; b++) { if(strcmp(data[b],data[b + 1]) > 0) { strcpy(temp,data[b]); strcpy(data[b] , data[b + 1]); strcpy(data[b + 1] , temp); s = 1; } } } } void INSERTION() { int a; int b; char temp[20]; for(a = 0; a < lastone; a++) { b = a; strcpy(temp,data[a + 1]); while(b >= 0) { if (strcmp(temp,data[b]) < 0) { strcpy(data[b+1],data[b]); b--; } else break; } strcpy(data[b+1],temp); } } void MKDATA() { /* Initialise arbitrary data */ /* Uses random(), which is not ANSI C */ /* Returns a random number between 0 and n - 1 */ int n; for(n = 0; n < 1000; n++) sprintf(save[n],"%d",random(1000)); } void INITDATA() { int n; for(n = 0 ; n < 1000; n++) strcpy(data[n],save[n]); } void main() { MKDATA(); /* Initialise arbitrary data */ INITDATA(); /* Set last element indicator */ lastone = 999; /* Call quick sort function */ QKSORT(); /* Initialise arbitrary data */ INITDATA(); /* Set last element indicator */ lastone = 1000; /* Call shell sort function */ SHELL(); /* Initialise arbitrary data */ INITDATA(); /* Set last element indicator */ lastone = 999; /* Call bubble sort function */ BUBBLE(); /* Initialise arbitrary data */ INITDATA(); /* Set last element indicator */ lastone = 999; /* Call bubble sort with swap flag function */ FBUBBLE(); /* Initialise arbitrary data */ INITDATA(); /* Set last element indicator */ lastone = 999; /* Call insertion sort function */ INSERTION(); } Here are the profiler results of the above test program run on 1000 and 5000 random items; STRING SORT - 1000 RANDOM ITEMS FBUBBLE 26.436 sec 41% |******************************************** BUBBLE 26.315 sec 41% |******************************************* INSERTION 10.210 sec 15% |*************** SHELL 0.8050 sec 1% |* QKSORT 0.3252 sec <1% | STRING SORT - 5000 RANDOM ITEMS FBUBBLE 563.70 sec 41% |******************************************** BUBBLE 558.01 sec 41% |******************************************** INSERTION 220.61 sec 16% |*************** SHELL 5.2531 sec <1% | QKSORT 0.8379 sec <1% | Here is the same test program amended for sorting tables of integers; /* Integer sort test program */ #include #include void INITDATA(void); void QKSORT(void); void SHELL(void); void BUBBLE(void); void FBUBBLE(void); void INSERTION(void); void MKDATA(void); int data[1000]; int save[1000]; int lastone; void QKSORT() { /* Implementation of QUICKSORT algorithm */ int i; int j; int l; int p; int r; int s; int temp; static int sl[1000][2]; l = 0; r = lastone; p = 0; do { while(l < r) { i = l; j = r; s = -1; while(i < j) { if (data[i] > data[j]) { temp = data[i]; data[i] = data[j]; data[j] = temp; s = 0 - s; } if (s == 1) i++; else j--; } if (i + 1 < r) { p++; sl[p][0] = i + 1; sl[p][1] = r; } r = i - 1; } if (p != 0) { l = sl[p][0]; r = sl[p][1]; p--; } } while(p > 0); } void SHELL() { /* SHELL Sort Courtesy of K & R */ int gap; int i; int j; int temp; for(gap = lastone / 2; gap > 0; gap /= 2) for(i = gap; i < lastone; i++) for(j = i - gap; j >= 0 && data[j] > data[j + gap]; j -= gap) { temp = data[j]; data[j] = data[j + gap]; data[j + gap] = temp; } } void BUBBLE() { int a; int b; int temp; for(a = lastone; a >= 0; a--) { for(b = 0; b < a; b++) { if(data[b] > data[b + 1]) { temp = data[b]; data[b] = data[b + 1]; data[b + 1] = temp; } } } } void FBUBBLE() { /* bubble sort with swap flag */ int a; int b; int s; int temp; s = 1; for(a = lastone; a >= 0 && s == 1; a--) { s = 0; for(b = 0; b < lastone - a; b++) { if(data[b] > data[b + 1]) { temp = data[b]; data[b] = data[b + 1]; data[b + 1] = temp; s = 1; } } } } void INSERTION() { int a; int b; int temp; for(a = 0; a < lastone; a++) { b = a; temp = data[a + 1]; while(b >= 0) { if (temp < data[b]) { data[b+1] = data[b]; b--; } else break; } data[b+1] = temp; } } void MKDATA() { int n; for(n = 0; n < 1000; n++) save[n] = random(1000); } void INITDATA() { int n; for(n = 0; n < 1000; n++) data[n] = save[n]; } void main() { int n; /* Create 1000 random elements */ MKDATA(); /* Initialise arbitrary data */ INITDATA(); /* Set last element indicator */ lastone = 999; /* Call quick sort function */ QKSORT(); /* Initialise arbitrary data */ INITDATA(); /* Set last element indicator */ lastone = 1000; /* Call shell sort function */ SHELL(); /* Initialise arbitrary data */ INITDATA(); /* Set last element indicator */ lastone = 999; /* Call bubble sort function */ BUBBLE(); /* Initialise arbitrary data */ INITDATA(); /* Set last element indicator */ lastone = 999; /* Call bubble sort with swap flag function */ FBUBBLE(); /* Initialise arbitrary data */ INITDATA(); /* Set last element indicator */ lastone = 999; /* Call insertion sort function */ INSERTION(); } And here are the profiler results for this program; INTEGER SORTS - 1000 RANDOM NUMBERS (0 - 999) FBUBBLE 3.7197 sec 41% |******************************************** BUBBLE 3.5981 sec 39% |****************************************** INSERTION 1.4258 sec 15% |*************** SHELL 0.1207 sec 1% |* QKSORT 0.0081 sec <1% | INTEGER SORTS - 5000 RANDOM NUMBERS (0 - 999) FBUBBLE 92.749 sec 42% |******************************************** BUBBLE 89.731 sec 41% |******************************************** INSERTION 35.201 sec 16% |*************** SHELL 0.9838 sec <1% | QKSORT 0.0420 sec <1% | INTEGER SORTS - 5000 RANDOM NUMBERS (0 - 99) FBUBBLE 92.594 sec 42% |***************************************** BUBBLE 89.595 sec 40% |**************************************** INSERTION 35.026 sec 16% |*************** SHELL 0.7563 sec <1% | QKSORT 0.6018 sec <1% | INTEGER SORTS - 5000 RANDOM NUMBERS (0 - 9) FBUBBLE 89.003 sec 41% |******************************************* BUBBLE 86.921 sec 40% |******************************************* INSERTION 31.544 sec 14% |*************** QKSORT 6.0358 sec 2% |** SHELL 0.5424 sec <1% | INTEGER SORTS - 5000 DESCENDING ORDERED NUMBERS FBUBBLE 122.99 sec 39% |****************************************** BUBBLE 117.22 sec 37% |**************************************** INSERTION 70.595 sec 22% |********************** SHELL 0.6438 sec <1% | QKSORT 0.0741 sec <1% | INTEGER SORTS - 5000 ORDERED NUMBERS BUBBLE 62.908 sec 99% |****************************************** SHELL 0.3971 sec <1% | INSERTION 0.0510 sec <1% | QKSORT 0.0382 sec <1% | FBUBBLE 0.0251 sec <1% | INTEGER SORTS - 10000 RANDOM NUMBERS (0 - 999) FBUBBLE 371.18 sec 42% |**************************************** BUBBLE 359.06 sec 41% |*************************************** INSERTION 140.88 sec 16% |************** SHELL 2.0423 sec <1% | QKSORT 0.6183 sec <1% | Theory has it that the performance of a sorting algorithm is dependant upon; a) the number of items to be sorted and b) how unsorted the list is to start with. With this in mind it is worth testing the various sorting routines described here to determine which one will best suit your particular application. If you examine the above profiler results you will see that: 1) With an already sorted list FBUBBLE() executes fastest 2) With a random list of small variations between the values SHELL() executes fastest 3) With a random list of large variations between the values QKSORT() executes the fastest What the profiler does not highlight is that when the comparison aspect of a sort function takes a disproportionately long time to execute in relation to the rest of the sort function, then the bubble sort with a swap flag will execute faster than the bubble sort with out a swap flag. When considering a sort routine take into consideration memory constraints and the type of data to be sorted as well as the relative performances of the sort functions. Generally, the faster a sort operates, the more memory it requires. Compare the simple bubble sort with the quick sort, and you will see that the quick sort requires far more memory than the bubble sort. DYNAMIC MEMORY ALLOCATION If a program needs a table of data, but the size of the table is variable, perhaps for a list of all file names in the current directory, it is inefficient to waste memory by declaring a data table of the maximum possible size. Rather it is better to dynamically allocate the table as required. Turbo C allocates RAM as being available for dynamic allocation into an area called the "heap". The size of the heap varies with memory model. The tiny memory model defaults to occupy 64K of RAM. The small memory model allocates upto 64K for the program/code and heap with a far heap being available within the remainder of conventional memory. The other memory models make all conventional memory available to the heap. This is significant when programming in the tiny memory model when you want to reduce the memory overhead of your program to a minimum. The way to do this is to reduce the heap to a minimum size. The smallest is 1 byte. C provides a function malloc() which allocates a block of free memory of a specified size and returns a pointer to the start of the block; it also provides free() which deallocates a block of memory previously allocated by malloc(). Notice, however, that the IBM PC doesnot properly free blocks of memory, and contiuous use of malloc() and free() will fragmentise memory, eventually causing no memory to be available untill the program terminates. This program searches a specified file for a specified string (with case sensitivity). It uses malloc() to allocate just enough memory for the file to be read into memory. #include #include char *buffer; void main(int argc, char *argv[]) { FILE *fp; long flen; /* Check number of parameters */ if (argc != 3) { fputs("Usage is sgrep ",stderr); exit(0); } /* Open stream fp to file */ fp = fopen(argv[2],"r"); if (!fp) { perror("Unable to open source file"); exit(0); } /* Locate file end */ if(fseek(fp,0L,SEEK_END)) { fputs("Unable to determine file length",stderr); fclose(fp); exit(0); } /* Determine file length */ flen = ftell(fp); /* Check for error */ if (flen == -1L) { fputs("Unable to determine file length",stderr); fclose(fp); exit(0); } /* Set file pointer to start of file */ rewind(fp); /* Allocate memory buffer */ buffer = malloc(flen); if (!buffer) { fputs("Unable to allocate memory",stderr); fclose(fp); exit(0); } /* Read file into buffer */ fread(buffer,flen,1,fp); /* Check for read error */ if(ferror(fp)) { fputs("Unable to read file",stderr); /* Deallocate memory block */ free(buffer); fclose(fp); exit(0); } printf("%s %s in %s",argv[1],(strstr(buffer,argv[1])) ? "was found" : "was not found",argv[2]); /* Deallocate memory block before exiting */ free(buffer); fclose(fp); } VARIABLE ARGUMENT LISTS Some functions, such as printf(), accept a variable number and type of arguments. C provides a mechanism to write your own functions which can accept a variable argument list. This mechanism is the va_ family defined in the header file `stdarg.h'. There are three macros which allow implementation of variable argument lists; va_start(), va_arg() and va_end() and a variable type va_list which defines an array which holds the information required by the macros. va_start() takes two parameters, the first is the va_list variable and the second is the last fixed parameter sent to the function. va_start() must be called before attempting to access the variable argument list as it sets up pointers required by the other macros. va_arg() returns the next variable from the argument list. It is called with two parameters, the first is the va_list variable and the second is the type of the argument to be extracted. So, if the next variable in the argument list is an integer, it can be extracted with; = va_arg(,int); va_end() is called after extracting all required variables from the argument list, and simply tidies up the internal stack if appropriate. va_end() accepts a single parameter, the va_list variable. The following simple example program illustrates the basis for a printf() type implementation where the types of the arguments is not known, but can be determined from the fixed parameter. This example only caters for integer, string and character types, but could easily by extended to cater for other variable types as well by following the method illustrated; #include char *ITOS(long x, char *ptr) { /* Convert a signed decimal integer to a string */ long pt[9] = { 100000000, 10000000, 1000000, 100000, 10000, 1000, 100, 10, 1 }; int n; /* Check sign */ if (x < 0) { *ptr++ = '-'; /* Convert x to absolute */ x = 0 - x; } for(n = 0; n < 9; n++) { if (x > pt[n]) { *ptr++ = 48 + x / pt[n]; x %= pt[n]; } } return(ptr); } void varfunc(char *format, ...) { va_list arg_ptr; char output[1000]; char *ptr; int bytes; int x; char *y; char z; /* Initialise pointer to argument list */ va_start(arg_ptr, format); /* loop format string */ ptr = output; bytes = 0; while(*format) { /* locate next argument */ while(*format != '%') { *ptr++ = *format++; bytes++; } /* A % has been located */ format++; switch(*format) { case '%' : *ptr++ = '%'; break; case 'd' : /* integer expression follows */ x = va_arg(arg_ptr,int); ptr = ITOS(x,ptr); *ptr = 0; format++; bytes += strlen(output) - bytes; break; case 's' : /* String expression follows */ y = va_arg(arg_ptr,char *); strcat(output,y); x = strlen(y); format++; ptr += x; bytes += x; break; case 'c' : /* Char expression follows */ z = va_arg(arg_ptr,char); *ptr++ = z; format++; bytes++; break; } } /* Clean stack just in case! */ va_end(arg_ptr); /* Null terminate output string */ *ptr = 0; /* Display what we got */ printf("\nOUTPUT==%s",output); } void main() { varfunc("%d %s %c",5,"hello world",49); } A simpler variation is to use vsprintf() rather than implementing our own variable argument list access. However, it is beneficial to understand how variable argument lists behave. The following is a simplification of the same program, but leaving the dirty work to the compiler; #include #include void varfunc(char *format, ...) { va_list arg_ptr; char output[1000]; va_start(arg_ptr, format); vsprintf(output,format,arg_ptr); va_end(arg_ptr); /* Display what we got */ printf("\nOUTPUT==%s",output); } void main() { varfunc("%d %s %c",5,"hello world",49); } TRIGONOMETRY FUNCTIONS The ANSI standard on C defines a number of trigonometry functions, all of which accept an angle parameter in radians; FUNCTION PROTOTYPE DESCRIPTION acos double acos(double x) arc cosine of x asin double asin(double x) arc sine of x atan double atan(double x) arc tangent of x atan2 double atan2(double x, double y) arc tangent of y/x cos double cos(double x) cosine of x cosh double cosh(double x) hyperbolic cosine of x sin double sin(double x) sine of x sinh double sinh(double x) hyperbolic sine of x tan double tan(double x) tangent of x tanh double tanh(double x) hyperbolic tangent of x There are 2PI radians in a circle, therefore 1 radian is equal to 360/2PI degrees or approximately 57 degrees in 1 radian. The calculation of any of the above functions requires large floating point numbers to be used which is a very slow process. If you are going to use calls to a trig' function, it is a good idea to use a lookup table of values rather than keep on calling the function. This approach is used in the discussion on circle drawing later in this book. ATEXIT When ever a program terminates, it should close any open files (this is done for you by the C compiler's startup/termination code which it surrounds your program with), and restore the host computer to some semblance of order. Within a large program where exit may occur from a number of locations it is a pain to have to keep on writing calls to the cleanup routine. Fortunately we don't have to! The ANSI standard on C describes a function, atexit(), which registers the specified function, supplied as a parameter to atexit(), as a function which is called immediately before terminating the program. This function is called automatically, so the following program calls `leave()' whether an error occurs or not; #include void leave() { puts("\nBye Bye!"); } void main() { FILE *fp; int a; int b; int c; int d; int e; char text[100]; atexit(leave); fp = fopen("data.txt","w"); if(!fp) { perror("Unable to create file"); exit(0); } fprintf(fp,"1 2 3 4 5 \"A line of numbers\""); fflush(fp); if (ferror(fp)) { fputs("Error flushing stream",stderr); exit(1); } rewind(fp); if (ferror(fp)) { fputs("Error rewind stream",stderr); exit(1); } fscanf(fp,"%d %d %d %d %d %s",&a,&b,&c,&d,&e,text); if (ferror(fp)) { /* Unless you noticed the deliberate bug earlier */ /* The program terminates here */ fputs("Error reading from stream",stderr); exit(1); } printf("\nfscanf() returned %d %d %d %d %d %s",a,b,c,d,e,text); } INCREASING SPEED In order to reduce the time your program spends executing it is essential to know your host computer. Most computers are very slow at displaying information on the screen. And the IBM PC is no exception to this. C offers various functions for displaying data, printf() being one of the most commonly used and also the slowest. Whenever possible try to use puts(varname) in place of printf("%s\n",varname). Remembering that puts() appends a newline to the string sent to the screen. When multiplying a variable by a constant which is a factor of 2 many C compilers will recognise that a left shift is all that is required in the assembler code to carry out the multiplication rapidly. When multiplying by other values it is often faster to do a multiple addition instead, so; 'x * 3' becomes 'x + x + x' Don't try this with variable multipliers in a loop because it becomes very slow! But, where the multiplier is a constant it can be faster. (Sometimes!) Another way to speed up multiplication and division is with the shift commands, << and >>. The instruction x /= 2 can equally well be written x >>= 1, shift the bits of x right one place. Many compilers actually convert integer divisions by 2 into a shift right instruction. You can use the shifts for multiplying and dividing by 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024 &c. If you have difficulty understanding the shift commands consider the binary form of a number; 01001101 equal to 77 shifted right one place it becomes; 00100110 equal to 38 Try to use integers rather than floating point numbers where ever possible. Sometimes you can use integers where you didn't think you could! For example, to convert a fraction to a decimal one would normally say; percentage = x / y * 100 This requires floating point variables. However, it can also be written as; z = x * 100; percentage = z / y Which works fine with integers, so long as you don't mind the percentage being truncated. eg; 5 / 7 * 100 is equal to 71.43 with floating point but with integers; 5 * 100 / 7 is equal to 71 (Assuming left to right expression evaluation. You may need to force the multiplication to be done first as with `z = x * 100'). Here is a test program using this idea; float funca(double x, double y) { return(x / y * 100); } int funcb(int x,int y) { return(x * 100 / y); } void main() { int n; double x; int y; for(n = 0; n < 5000; n++) { x = funca(5,7); y = funcb(5,7); } } And here is the results of the test program fed through a profiler; funca 1.9169 sec 96% |********************************************** funcb 0.0753 sec 3% |* You can clearly see that the floating point function is 25 times slower than the integer equivalent! NB: Although it is normal practice for expressions to be evaluated left to right, the ANSI standard on C does not specify an order of preference for expression evaluation, and as such you should check your compiler manual. Another way of increasing speed is to use pointers rather than array indexing. When you access an array through an index, for example with; x = data[i]; the compiler has to calculate the offset of data[i] from the beginning of the array. A slow process. Using pointers can often improve things as the following two bubble sorts, one with array indexing and one with pointers illustrates; void BUBBLE() { /* Bubble sort using array indexing */ int a; int b; int temp; for(a = lastone; a >= 0; a--) { for(b = 0; b < a; b++) { if(data[b] > data[b + 1]) { temp = data[b]; data[b] = data[b + 1]; data[b + 1] = temp; } } } } void PTRBUBBLE() { /* Bubble sort using pointers */ int temp; int *ptr; int *ptr2; for(ptr = &data[lastone]; ptr >= data; ptr--) { for(ptr2 = data; ptr2 < ptr; ptr2++) { if(*ptr2 > *(ptr2 + 1)) { temp = *ptr2; *ptr2 = *(ptr2 + 1); *(ptr2 + 1) = temp; } } } } Here are the profiler results for the two versions of the same bubble sort operating on the same 1000 item, randomly sorted list; BUBBLE 3.1307 sec 59% |****************************************** PTRBUBBLE 2.1686 sec 40% |*************************** Here is another example of how to initialise an array using first the common indexing approach, and secondly the pointer approach; /* Index array initialisation */ int n; for(n = 0; n < 1000; n++) data[n] = random(1000); /* Pointer array initialisation */ int *n; for(n = data; n < &data[1000]; n++) *n = random(1000); Needless to say, the pointer approach is faster than the index. The pointer approach is only really of benefit when an array is going to be traversed, as in the above examples. In the case of say a binary search where a different and non-adjacent element is going to be tested each pass then the pointer approach is no better than using array indexing. The exception to this rule of using pointers rather than indexed access, comes with pointer to pointers. Say your program has declared a table of static data, such as: static char *colours[] = { "Black", "Blue", "Green", "Yellow", "Red", "White" }; It is faster to access the table with colours[n] than it is with a pointer, since each element in the table colours[], is a pointer. If you need to scan a string table for a value you can use this very fast approach instead; First the table is changed into a single string, with some delimiter between the elements. static char *colours = "Black/Blue/Green/Yellow/Red/White"; Then to confirm that a value is held in the table you can use strstr(); result = strstr(colours,"Cyan"); Using in-line assembler code can provide the greatest speed increase. Care must be taken however not to interfere with the compiled C code. It is usually safe to write a complete function with in-line assembler code, but mixing in-line assembler with C code can be hazardous. As a rule of thumb, get your program working without assembler code, and then if you want to use in-line assembler, convert small portions of the code at a time, testing the program at each stage. Video I/O is a very slow process with C, and usually benefits from in-line assembler, and we have used this principle quite widely in the example programs which follow later. PC GRAPHICS When programming graphics you should bear in mind that they are a machine dependant subject. Code to produce graphics on an IBM PC will not port to an Amiga, or VAX or any other type of computer. Introduction To PC Graphics The IBM PC and compatible range of computers display information on a visual display unit (VDU). To enable the computer to send information to the VDU a component is included within the computer called a "display card". There are various display cards and VDUs which have been produced for the IBM PC range of computers; monochrome display adapter (MDA), colour graphics adapter (CGA), Hercules graphics card (HGC), Enhanced graphics adapter (EGA), video graphics array (VGA), super video graphics array (SVGA), memory controller gate array (MCGA), 8514/A and the Txas Instruments Graphics Architecture (TIGA). For simplicity, this section will concern itself only with the three more common types of display; CGA, EGA and VGA Information about the VGA display is also relevant to the SVGA display which in simple terms can do the same and more. This section will not concern itself with monochrome displays since they are of limited use in graphics work. Display Modes When an IBM PC computer is first switched on is set to display 80 columns by 25 rows of writing. This measurement, 80 x 25 is called the "resolution". A display mode which is intended for displaying writing is called a "text" mode. Where as a display mode which is intended for displaying pictures is called a "graphics" mode. If you look closely at the display you will see that each displayed character is comprised of dots. In reality the entire display is comprised of dots, called "pixels", which may be set to different colours. In text display modes these pixels are not very relevant, however, in graphics display modes each pixel may be selected and set by a program. The size of the pixels varies with the display resolution. In 80 x 25 text mode the pixels are half the width they are in 40 x 25 text display mode. Depending upon the display card installed in the computer, there are a number of display modes which may be used; MODE TYPE RESOLUTION COLOURS 0 Text 40 x 25 4 (CGA), 16 (EGA, VGA) Shades of grey 1 Text 40 x 25 4 (CGA), 16 (EGA, VGA) 2 Text 80 x 25 4 (CGA), 16 (EGA, VGA) Shades of grey 3 Text 80 x 25 4 (CGA), 16 (EGA, VGA) 4 Graphics 320 x 200 4 5 Graphics 320 x 200 4 (grey on CGA and EGA) 6 Graphics 640 x 200 2 7 Text 80 x 25 Mono (EGA, VGA) 13 Graphics 320 x 200 16 (EGA, VGA) 14 Graphics 640 x 200 16 (EGA, VGA) 15 Graphics 640 x 350 Mono (EGA, VGA) 16 Graphics 640 x 350 16 (EGA, VGA) 17 Graphics 640 x 480 2 (VGA) 18 Graphics 640 x 480 16 (VGA) 19 Graphics 320 x 200 256 (VGA) The term resolution in graphics modes refers to the number of pixels across and down the VDU. The larger the number of pixels, the smaller each is and the sharper any displayed image appears. As you can see from the table, the VGA display can produce a higher resolution than the other display cards, resulting in sharper images being produced. The CGA display card can produce a maximum resolution of 320 x 200 pixels, where as the VGA display card can produce a resolution of 640 x 480 pixels. This is why writing on a VGA VDU looks so much sharper than the writing displayed on a CGA VDU. Accessing The Display Inside the IBM PC computer is a silicon chip called the BIOS ROM, this chip contains functions which may be called by an external computer program to access the display card, which in turn passes the information on to the VDU. The BIOS display functions are all accessed by generating interrupt 10 calls, with the number of the appropriate function stored in the assembly language AH register. A programmer interested in creating graphics displays must first be able to switch the display card to an appropriate graphics mode. This is achieved by calling the BIOS display function 0, with th number of the desired display mode from the table stored in the assembly language AL register thus the following assembly language code segment will switch the display card to CGA graphics mode 4, assuming that is that the display card is capable of this mode; mov ah , 00 mov al , 04 int 10h A C function for selecting video display modes can be written; #include void setmode(unsigned char mode) { /* Sets the video display mode */ union REGS inregs outreg; inreg.h.ah = 0; inreg.h.al = mode; int86(0x10,&inreg,&outregs); } Any graphics are created by setting different pixels to different colours, this is termed "plotting", and is achieved by calling BIOS display function 12 with the pixel's horizontal coordinate in the assembly language CX register and it's vertical coordinate in the assembly language DX register and the required colour in the assembly language AL register thus; mov ah, 12 mov al, colour mov bh, 0 mov cx, x_coord mov dx, y_coord int 10h The corresponding C function is; #include void plot(int x_coord, int y_coord, unsigned char colour) { /* Sets the colour of a pixel */ union REGS regs; regs.h.ah = 12; regs.h.al = colour; regs.h.bh = 0; regs.x.cx = x_coord; regs.x.dx = y_coord; int86(0x10,®s,®s); } The inverse function of plot is to read the existing colour setting of a pixel. This is done by calling BIOS ROM display function 13, again with the pixel's horizontal coordinate in the assembly language CX register and it's vertical coordinate in the assembly language DX register. This function then returns the pixel's colour in the assembly language AL register; #include unsigned char get_pixel(int x_coord, int y_coord) { /* Reads the colour of a pixel */ union REGS inreg, outreg; inreg.h.ah = 13; inreg.h.bh = 0; inreg.x.cx = x_coord; inreg.x.dx = y_coord; int86(0x10,&inreg,&outreg); return(outreg.h.al); } Colour And The CGA The CGA display card can display a maximum of 4 colours simultaneously at any time. However, the display card can generate a total of 8 colours. There are two sets of colours, called "palettes". The first palette contains the colours; background, cyan, magenta and white. the second palette contains the colours; background, green, red and yellow. Colour 0 is always the same as the background colour. The pixels displayed on the VDU take their colours from the currently active palette, and are continuously being refreshed. So, if the active palette changes, so to do the colours of the displayed pixels on the VDU. Selection of the active CGA palette is achieved by calling the BIOS display function 11 with the number of the desired palette (either 0 or 1) in the assembly language BH register; mov ah, 11 mov bh, palette int 10h The C function for selecting the CGA palette is; void palette(unsigned char palette) { union REGS inreg, outreg; inreg.h.ah = 11; inreg.h.bh = palette; int86(0x10,&inreg,&outreg); } The background colour may be selected independantly from any of the eight available colours by calling the same BIOS display function with a value of 0 stored in the assembly language BH register and the desired background colour in the assembly language BL register; mov ah, 11 mov bh, 0 mov bl, colour int 10h In C this function can be written; void background(unsigned char colour) { union REGS inreg, outreg; inreg.h.ah = 11; inreg.h.bh = 0; inreg.h.bl = colour; int86(0x10,&inreg,&outreg); } The background colours available are; 0 Black 1 Blue 2 Green 3 Cyan 4 Red 5 Magenta 6 Yellow 7 White Colour And The EGA The EGA display card can display a maximum of 16 colours simultaneously at any time. The colour of all pixels is continuously refreshed by the display card by reading the colour from the EGA palette. Unlike the CGA display card, the EGA display card allows you to redefine any or all of the colours in the palette. Unfortunately only the first 8 colours may be loaded into other palette colours. The colours are; 0 Black 1 Blue 2 Green 3 Cyan 4 Red 5 Magenta 6 Brown 7 Light grey 8 Dark grey 9 Light blue 10 Light green 11 Light cyan 12 Light red 13 Light magenta 14 Yellow 15 White Changing a palette colour is achieved by calling the BIOS display function 16 with a value of 0 in the assembly language AL register, the colour value (0 to 7) in the assembly language BH register and the number of the palette colour (0 to 15) in the assembly language BL register thus; mov ah,16 mov al,0 mov bl,palette mov bh,colour int 10h In C this function may be written; void ega_palette(unsigned char colour, unsigned char palette) { union REGS inreg,outreg; inreg.h.ah = 16; inreg.h.al = 0; inreg.h.bl = palette; inreg.h.bh = colour; int86(0x10,&inreg,&outreg); } Colour And The VGA The VGA display card can display a maximum of 256 colours on the VDU at any one time, these colours are defined by information held in 256 special registers called "DAC" registers. As with the CGA and EGA displays, the colour of displayed pixels is continuously being refreshed, and as such any change to a DAC register is immediately visible. Each DAC register has three component data parts which record the amount of green, blue and red colours which make up the displayed colour. Each of these seperate data components can hold a value between 0 and 63 giving the VGA display card the ability to display 262,144 colours! Although only a small subset of them can be displayed at any one time. Setting the value of a DAC register is achieved by calling the BIOS display function 16 with a value of 16 stored in the assembly language AL register, the green value stored in the assembly language CH register, the blue value stored in the assembly language CL register and the red value stored in the assembly language DH register and the number of the DAC register to be set stored in the assembly language BX register; mov ah,16 mov al,16 mov ch,green mov cl,blue mov dh,red mov bx,dac int 10h The C function to set a DAC register looks lik this; void set_dac(int dac, unsigned char green, unsigned char blue, unsigned char red) { union REGS regs; regs.h.ah = 16; regs.h.al = 16; regs.x.bx = dac; regs.h.ch = green; regs.h.cl = blue; regs.h.dh = red; int86(0x10,®s,®s); } Displaying Text The BIOS ROM provides three functions for displaying a single character. The first function to consider is the one used extensively by DOS for displaying messages, this is function 14 called "write text in teletype mode". This function interprets some control characters; bell (ascii 7), backspace (ascii 8), carriage return (ascii 10) and line feed (ascii 13) but all other ascii codes are displayed, and the current cursor position updated accordingly, moving down a row when a character is displayed in the far right column. To call this function the assembly language register AL holds the ascii code of the character to be displayed and assembly language register BL holds the foreground colour for the character to be displayed in if a graphics mode is active; mov ah,14 mov al,character mov bh,0 mov bl,foreground int 10h A C function for accessing the write text in teletype mode may be written like this; #include void teletype(unsigned char character, unsigned char foreground) { union REGS inreg, outreg; inreg.h.ah = 14; inreg.h.al = character; inreg.h.bh = 0; inreg.h.bl = foreground; int86(0x10,&inrg,&outreg); } The second BIOS ROM display function for displaying a character allows the foreground and background colours of the displayed character to be defined. It also allows multiple copies of the character to be displayed one after another automatically displaying subsequent characters at the next display position, although the current cursor position is not changed by this function. This function is called "write character and attribute", and is BIOS ROM display function number 9. It is called with the ascii code of the character to be displayed in the assembly language AL register, the display page in assembly language register BH, the foreground colour in the first four bits of the assembly language register BL and the background colour in the last four bits of the assembly language register BL, the number of times the character is to be displayed is stored in the assembly language CX register thus; mov ah,9 mov al,character mov bh,0 mov bl,foreground + 16 * background mov cx,number int 10h And in C; #include void char_attrib(unsigned char character, unsigned char foreground, unsigned char background, int number) { union REGS inreg,outreg; inreg.h.ah = 9; inreg.h.al = character; inreg.h.bh = 0; inreg.h.bl = (background << 4) + foreground; inreg.x.cx = number; int86(0x10,&inreg,&outreg); } The last BIOS ROM display function for displaying a character retains the foreground and background colours of the display position. This function is called "write character", and is BIOS ROM display function number 10. It is identical to BIOS ROM display function 9 except that the colours of the displayed character are those which are prevalent at the display position, except in graphics modes when the foreground colour of the character is determined by the value in the assembly language BL register. Its use is as follows; mov ah,10 mov al,character mov bh,0 mov bl,foreground ; For graphics modes ONLY mov cx,number int 10h And in C; #include void char_attrib(unsigned char character, unsigned char foreground, int number) { union REGS inreg,outreg; inreg.h.ah = 10; inreg.h.al = character; inreg.h.bh = 0; inreg.h.bl = foreground; /* For graphics modes ONLY */ inreg.x.cx = number; int86(0x10,&inreg,&outreg); } Positioning of the text cursor is provided for by the ROM BIOS display function number 2. It is called with the row number in the assembly language register DH and the column number in the assembly language register DL; mov ah,2 mov bh,0 mov dh,row mov dl,column int 10h The corresponding function in C looks like this; #include void at(unsigned char row, unsigned char column) { union REGS regs; regs.h.ah = 2; regs.h.bh = 0; regs.h.dh = row; regs.h.dl = column; int86(0x10,®s,®s); } From these basic functions a more useful replacement for the C language's "printf()" function can be written which allows data to be displayed at the current cursor position, previously set by a call to "at()", with prescribed attributes; #include #include void at(unsigned char row, unsigned char column) { union REGS regs; regs.h.ah = 2; regs.h.bh = 0; regs.h.dh = row; regs.h.dl = column; int86(0x10,®s,®s); } void xprintf(unsigned char foreground, unsigned char background, char *format,...) { union REGS inreg,outreg; va_list arg_ptr; static char output[1000]; unsigned char col; unsigned char row; unsigned char n; unsigned char p; unsigned char text; unsigned char attr; /* Convert foreground and background colours into a single attribute */ attr = (background << 4) + foreground; /* Copy data into a single string */ va_start(arg_ptr, format); vsprintf(output, format, arg_ptr); /* Determine number of display columns */ inreg.h.ah = 15; int86(0x10,&inreg,&outreg); n = outreg.h.ah; /* Determine current cursor position */ inreg.h.ah = 3; inreg.h.bh = 0; int86(0x10,&inreg,&outreg); row = outreg.h.dh; col = outreg.h.dl; /* Now display data */ p = 0; while (output[p]) { /* Display this character */ inreg.h.bh = 0; inreg.h.bl = attr; inreg.x.cx = 01; inreg.h.ah = 9; inreg.h.al = output[p++]; int86(0x10,&inreg,&outreg); /* Update cursor position */ /* moving down a row if required */ col++; if (col < (n - 1)) at(row, col); else { col = 0; at(++row, col); } } } This function, "xprintf()" illustrates two more functions of the BIOS ROM. The first is the call to function 15 which returns the number of text display columns for the currently active display mode. The other function illustrated, but not yet discussed, is BIOS ROM function 3 which returns information about the cursor. The cursor's row is returned in the assembly language register DH, and it's column in the assembly language register DL. ADVANCED GRAPHICS ON THE IBM PC This section aims to reveal more about the graphics facilities offered by the IBM PC, in particular topics which are of a more complex nature than those addressed in the previous section. Display Pages The information for display by the display card is stored in an area of memory called the "video RAM". The size of the video RAM varies from one display card to another, and the amount of video RAM required for a display varies with the selected display mode. Display modes which do not require all of the video RAM use the remaining video RAM for additional display pages. MODE PAGES 0 8 1 8 2 4 (CGA) 8 (EGA, VGA) 3 4 (CGA) 8 (EGA, VGA) 4 1 5 1 6 1 7 8 (EGA, VGA) 13 8 (EGA, VGA) 14 4 (EGA, VGA) 15 2 (EGA, VGA) 16 2 (EGA, VGA) 17 1 (VGA) 18 1 (VGA) 19 1 (VGA) Many of the BIOS ROM display functions allow selection of the display page to be written to, regardless of which page is currently being displayed. The display card continuously updates the VDU from the information in the active display page. Changing the active display page instantaneously changes the display. This provides the graphics programmer with the means to build a display on an undisplayed page, and to then change the active display page so that the viewer does not see the display being drawn. Selection of the active display page is achieved by calling BIOS ROM display function 5 with the number of the required display page stored in the assembly language register AL; mov ah,5 mov al,page int 10h Or, from C this function becomes; #include void set_page(unsigned char page) { union REGS inreg, outreg; inreg.h.ah = 5; inreg.h.al = page; int86(0x10,&inreg,&outreg); } The display page to which BIOS ROM display functions write is decided by the value stored in the assembly language BH register. The functions for setting a pixel's colour may be amended thus; mov ah, 12 mov al, colour mov bh, page mov cx, x_coord mov dx, y_coord int 10h And the corresponding C function becomes; #include void plot(int x_coord, int y_coord, unsigned char colour, unsigned char page) { /* Sets the colour of a pixel */ union REGS inreg, outreg; inreg.h.ah = 12; inreg.h.al = colour; inreg.h.bh = page; inreg.x.cx = x_coord; inreg.x.dx = y_coord; int86(0x10,&inreg,&outreg); } The currently active display page can be determined by calling BIOS ROM display function 15. This function returns the active display page in the assembly language register BH; mov ah,15 int 10h ; BH now holds active page number Advanced Text Routines When the IBM PC display is in a text mode a blinking cursor is displayed at the current cursor position. This cursor is formed of a rectangle which is one complete character width, but it's top and bottom pixel lines are definable within the limits of the character height by calling BIOS ROM display function 1. A CGA display has a character height of 8 pixel lines, an EGA display has a character height of 14 lines and a VGA display has a character height of 16 lines. BIOS ROM function 1 is called with the top pixel line number of the desired cursor shape in assembly language register CH and the bottom pixel line number in assembly language register CL; mov ah,1 mov ch,top mov cl,bottom int 10h A C function to set the cursor shape may be be written thus; #include void setcursor(unsigned char top, unsigned char bottom) { union REGS inreg, outreg; inreg.h.ch = start; inreg.h.cl = end; inreg.h.ah = 1; int86(0x10, &inreg, &outreg); } If the top pixel line is defined as larger than the bottom line, then the cursor will appear as a pair of parallel rectangles. The cursor may be removed from view by calling BIOS ROM display function 1 with a value of 32 in the assembly language CH register. The current shape of the cursor can be determined by calling BIOS ROM function 3, which returns the top pixel line number of the cursor shape in the assembly language CH register, and the bottom line number in the assembly language register CL. Two functions are provided by the BIOS ROM for scrolling of the currently active display page. These are function 6, which scrolls the display up and function 7 which scrolls the display down. Both functions accept the same parameters, these being the number of lines to scroll in the assembly language register AL, the colour attribute for the resulting blank line in the assembly language BH register, the top row of the area to be scrolled in the assembly language CH register, the left column of the area to be scrolled in the assembly language CL register, the bottom row to be scrolled in the assembly language DH register and the right most column to be scrolled in the assembly language DL register. If the number of lines being scrolled is greater than the number of lines in the specified area, then the result is to clear the specified area, filling it with spaces in the attribute specified in the assembly language BH register. Scrolling A C function to scroll the entire screen down one line can be written thus; #include void scroll_down(unsigned char attr) { union REGS inreg, outreg; inreg.h.al = 1; inreg.h.cl = 0; inreg.h.ch = 0; inreg.h.dl = 79; inreg.h.dh = 24; /* Assuming a 25 line display */ inreg.h.bh = attr; inreg.h.ah = 7; int86(0x10, &inreg, &outreg); } Clear Screen A simple clear screen function can be written in C based upon the "scroll_down()" function simply by changing the value assigned to inreg.h.al to 0; #include void cls(unsigned char attr) { union REGS inreg, outreg; inreg.h.al = 0; inreg.h.cl = 0; inreg.h.ch = 0; inreg.h.dl = 79; inreg.h.dh = 24; /* Assuming a 25 line display */ inreg.h.bh = attr; inreg.h.ah = 7; int86(0x10, &inreg, &outreg); } Windowing Windowing functions need to preserve the display they overwrite, and restore it when the window is removed from display. The BIOS ROM provides a display function which enables this to be done. Function 8 requires the appropriate display page number to be stored in assembly language register BH, and then when called it returns the ascii code of the character at the current cursor position of that display page in the assembly language AL register, and the display attribute of the character in the assembly language AH register. The following C functions allow an area of the display to be preserved, and later restored; #include void at(unsigned char row, unsigned char column, unsigned char page) { /* Position the cursor */ union REGS inreg,outreg; inreg.h.ah = 2; inreg.h.bh = page; inreg.h.dh = row; inreg.h.dl = column; int86(0x10,&inreg,&outreg); } void get_win(unsigned char left, unsigned char top, unsigned char right,unsigned char bottom, unsigned char page, char *buffer) { /* Read a text window into a variable */ union REGS inreg,outreg; unsigned char old_left; unsigned char old_row; unsigned char old_col; /* save current cursor position */ inreg.h.ah = 3; inreg.h.bh = page; int86(0x10,&inreg,&outreg); old_row = outreg.h.dh; old_col = outreg.h.dl; while(top <= bottom) { old_left = left; while(left <= right) { at(top,left,page); inreg.h.bh = page; inreg.h.ah = 8; int86(0x10,&inreg,&outreg); *buffer++ = outreg.h.al; *buffer++ = outreg.h.ah; left++; } left = old_left; top++; } /* Restore cursor to original location */ at(old_row,old_col,page); } void put_win(unsigned char left, unsigned char top, unsigned char right, unsigned char bottom, unsigned char page, char *buffer) { /* Display a text window from a variable */ union REGS inreg,outreg; unsigned char old_left; unsigned char chr; unsigned char attr; unsigned char old_row; unsigned char old_col; /* save current cursor position */ inreg.h.ah = 3; inreg.h.bh = page; int86(0x10,&inreg,&outreg); old_row = outreg.h.dh; old_col = outreg.h.dl; while(top <= bottom) { old_left = left; while(left <= right) { at(top,left,page); chr = *buffer++; attr = *buffer++; inreg.h.bh = page; inreg.h.ah = 9; inreg.h.al = chr; inreg.h.bl = attr; inreg.x.cx = 1; int86(0x10,&inreg,&outreg); left++; } left = old_left; top++; } /* Restore cursor to original location */ at(old_row,old_col,page); } DIRECT VIDEO ACCESS WITH THE IBM PC Accessing video RAM directly is much faster than using the BIOS ROM display functions. There are problems however. Different video modes arrange their use of video RAM in different ways so a number of functions are required for plotting using direct video access, where as only one function is required if use is made of the BIOS ROM display function. The following C function will set a pixel in CGA display modes 4 and 5 directly; void dplot4(int y, int x, int colour) { /* Direct plotting in modes 4 & 5 ONLY! */ union mask { char c[2]; int i; }bit_mask; int index; int bit_position; unsigned char t; char xor; char far *ptr = (char far *) 0xB8000000; bit_mask.i = 0xFF3F; if ( y < 0 || y > 319 || x < 0 || x > 199) return; xor = colour & 128; colour = colour & 127; bit_position = y % 4; colour <<= 2 * (3 - bit_position); bit_mask.i >>= 2 * bit_position; index = x * 40 + (y / 4); if (x % 2) index += 8152; if (!xor) { t = *(ptr + index) & bit_mask.c[0]; *(ptr + index) = t | colour; } else { t = *(ptr + index) | (char)0; *(ptr + index) = t ^ colour; } } Direct plotting in VGA mode 19 is very much simpler; void dplot19(int x, int y, unsigned char colour) { /* Direct plot in mode 19 ONLY */ char far *video; video = MK_FP(0xA000,0); video[x + y * 320] = colour; } ADVANCED GRAPHICS TECHNIQUES WITH THE IBM PC Increasing Colours The EGA display is limited displaying a maximum of 16 colours, however in high resolution graphics modes (such as mode 16) the small physical size of the pixels allows blending of adjacent colours to produce additional shades. If a line is drawn straight across a black background display in blue, and then a subsequent line is drawn beneath it also in blue but only plotting alternate pixels, the second line will appear in a darker shade of the same colour. The following C program illustrates this idea; #include union REGS inreg, outreg; void setvideo(unsigned char mode) { /* Sets the video display mode */ inreg.h.al = mode; inreg.h.ah = 0; int86(0x10, &inreg, &outreg); } void plot(int x, int y, unsigned char colour) { /* Sets a pixel at the specified coordinates */ inreg.h.al = colour; inreg.h.bh = 0; inreg.x.cx = x; inreg.x.dx = y; inreg.h.ah = 0x0C; int86(0x10, &inreg, &outreg); } void line(int a, int b, int c, int d, unsigned char colour) { /* Draws a straight line from (a,b) to (c,d) */ int u; int v; int d1x; int d1y; int d2x; int d2y; int m; int n; int s; int i; u = c - a; v = d - b; if (u == 0) { d1x = 0; m = 0; } else { m = abs(u); if (u < 0) d1x = -1; else if (u > 0) d1x = 1; } if ( v == 0) { d1y = 0; n = 0; } else { n = abs(v); if (v < 0) d1y = -1; else if (v > 0) d1y = 1; } if (m > n) { d2x = d1x; d2y = 0; } else { d2x = 0; d2y = d1y; m = n; n = abs(u); } s = m / 2; for (i = 0; i <= m; i++) { plot(a,b,colour); s += n; if (s >= m) { s -= m; a += d1x; b += d1y; } else { a += d2x; b += d2y; } } } void dot_line(int a, int b, int c, int d, int colour) { /* Draws a dotted straight line from (a,b) to (c,d) */ int u; int v; int d1x; int d1y; int d2x; int d2y; int m; int n; int s; int i; u = c - a; v = d - b; if (u == 0) { d1x = 0; m = 0; } else { m = abs(u); if (u < 0) d1x = -2; else if (u > 0) d1x = 2; } if (v == 0) { d1y = 0; n = 0; } else { n = abs(v); if (v < 0) d1y = -2; else if (v > 0) d1y = 2; } if (m > n) { d2x = d1x; d2y = 0; } else { d2x = 0; d2y = d1y; m = n; n = abs(u); } s = m / 2; for (i = 0; i <= m; i+=2) { plot(a,b,colour); s += n; if (s >= m) { s -= m; a += d1x; b += d1y; } else { a += d2x; b += d2y; } } } void main(void) { int n; /* Display different colour bands */ setvideo(16); for(n = 1; n < 16; n++) { line(0,n * 20,639,n * 20,n); line(0,1 + n * 20,639,1 + n * 20,n); line(0,2 + n * 20,639,2 + n * 20,n); dot_line(0,4 + n * 20,639,4 + n * 20,n); dot_line(1,5 + n * 20,639,5 + n * 20,n); dot_line(0,6 + n * 20,639,6 + n * 20,n); dot_line(0,8 + n * 20,639,8 + n * 20,n); dot_line(1,9 + n * 20,639,9 + n * 20,n); dot_line(0,10 + n * 20,639,10 + n * 20,n); dot_line(1,8 + n * 20,639,8 + n * 20,7); dot_line(0,9 + n * 20,639,9 + n * 20,7); dot_line(1,10 + n * 20,639,10 + n * 20,7); dot_line(1,12 + n * 20,639,12 + n * 20,n); dot_line(0,13 + n * 20,639,13 + n * 20,n); dot_line(1,14 + n * 20,639,14 + n * 20,n); dot_line(0,12 + n * 20,639,12 + n * 20,14); dot_line(1,13 + n * 20,639,13 + n * 20,14); dot_line(0,14 + n * 20,639,14 + n * 20,14); } } This technique can be put to good use for drawing three dimensional boxes; void box3d(int xa,int ya, int xb, int yb, int col) { /* Draws a box for use in 3d histogram graphs etc */ int xc; int xd; int n; xd = (xb - xa) / 2; xc = xa + xd; /* First draw the solid face */ for(n = xa; n < xb; n++) line(n,ya,n,yb,col); /* Now "shaded" top and side */ for(n = 0; n < xd; n++) { dotline(xa + n,yb - n ,xc + n,yb - n,col); dotline(xa + xd + n,yb - n ,xc + xd + n,yb - n,col); dotline(xb +n ,ya - n ,xb + n,yb - n,col); } } Displaying Text At Pixel Coordinates When using graphics display modes it is useful to be able to display text not at the fixed character boundaries, but at pixel coordinates. This can be achieved by implementing a print function which reads the character definition data from the BIOS ROM and uses it to plot pixels to create the shapes of the desired text. The following C function, "gr_print()" illustrates this idea using the ROM CGA (8x8) character set; void gr_print(char *output, int x, int y, unsigned char colour) { unsigned char far *ptr; unsigned char chr; unsigned char bmask; int i; int k; int oldy; int p; int height; /* The height of the characters in the font being accessed */ height = 8; /* Set pointer to start of font definition in the ROM */ ptr = getfontptr(3); oldy = y; p = 0; /* Loop output string */ while(output[p]) { /* Get first character to be displayed */ chr = output[p]; /* Loop pixel lines in character definition */ for(i = 0; i < height; i++) { /* Get pixel line definition from the ROM */ bmask = *(ptr + (chr * height) + i); /* Loop pixel columns */ for (k = 0; k < 8; ++k, bmask <<= 1) { /* Test for a set bit */ if(bmask & 128) /* Plot a pixel if appropriate */ plot(x,y,colour); else plot(x,y,0); x++; } /* Down to next row and left to start of character */ y++; x -= 8; } /* Next character to be displayed */ p++; /* Back to top row of the display position */ y = oldy; /* Right to next character display position */ x += 8; } } The following assembly language support function is required to retrieve the address of the ROM font by calling the BIOS ROM display function which will return the address; ; GET FONT POINTER ; Small memory model ; compile with tasm /mx _TEXT segment byte public 'CODE' assume cs:_TEXT,ds:NOTHING _getfontptr proc near push bp mov bp,sp mov ax,1130h mov bh, [bp+4] ; Number for font to be retrieved int 10h mov dx,es mov ax,bp pop bp ret _getfontptr endp _TEXT ends public _getfontptr end The font number supplied to "getfontptr()" can be one of; 2 ROM EGA 8 x 14 font 3 ROM CGA 8 x 8 font 6 ROM VGA 8 x 16 font A Graphics Function Library For Turbo C /* Graphics library for 'Turbo C' (V2.01) */ /* (C)1992 Copyright Servile Software */ #include #include #include #include #include #include /* Global variables */ static unsigned char attribute; int _dmode; int _lastx; int _lasty; /* Maximum coordinates for graphics screen */ static int maxx; static int maxy; /* Sprite structure */ struct SP { int x; int y; char data[256]; char save[256]; }; typedef struct SP SPRITE; /* Sine and cosine tables for trig' operations */ static double sintable[360] = {0.000000000000000001,0.017452406437283512, 0.034899496702500969,0.052335956242943835, 0.069756473744125302,0.087155742747658166, 0.104528463267653457,0.121869343405147462, 0.139173100960065438,0.156434465040230869, 0.173648177666930331,0.190808995376544804, 0.207911690817759315,0.224951054343864976, 0.241921895599667702,0.258819045102520739, 0.275637355816999163,0.292371704722736769, 0.309016994374947451,0.325568154457156755, 0.342020143325668824,0.358367949545300379, 0.374606593415912181,0.390731128489273882, 0.406736643075800375,0.422618261740699608, 0.438371146789077626,0.453990499739547027, 0.469471562785891028,0.484809620246337225, 0.500000000000000222,0.515038074910054489, 0.529919264233205234,0.544639035015027417, 0.559192903470747127,0.573576436351046381, 0.587785252292473470,0.601815023152048600, 0.615661475325658625,0.629320391049837835, 0.642787609686539696,0.656059028990507720, 0.669130606358858682,0.681998360062498921, 0.694658370458997698,0.707106781186548017, 0.719339800338651636,0.731353701619170904, 0.743144825477394688,0.754709580222772458, 0.766044443118978569,0.777145961456971346, 0.788010753606722458,0.798635510047293384, 0.809016994374947895,0.819152044288992243, 0.829037572555042179,0.838670567945424494, 0.848048096156426512,0.857167300702112778, 0.866025403784439152,0.874619707139396296, 0.882947592858927432,0.891006524188368343, 0.898794046299167482,0.906307787036650381, 0.913545457642601311,0.920504853452440819, 0.927183854566787868,0.933580426497202187, 0.939692620785908761,0.945518575599317179, 0.951056516295153975,0.956304755963035880, 0.961261695938319227,0.965925826289068645, 0.970295726275996806,0.974370064785235579, 0.978147600733805911,0.981627183447664198, 0.984807753012208353,0.987688340595137992, 0.990268068741570473,0.992546151641322205, 0.994521895368273512,0.996194698091745656, 0.997564050259824309,0.998629534754573944, 0.999390827019095762,0.999847695156391270, 1.000000000000000000,0.999847695156391159, 0.999390827019095651,0.998629534754573833, 0.997564050259824087,0.996194698091745323, 0.994521895368273179,0.992546151641321761, 0.990268068741570029,0.987688340595137437, 0.984807753012207687,0.981627183447663532, 0.978147600733805245,0.974370064785234802, 0.970295726275996029,0.965925826289067757, 0.961261695938318228,0.956304755963034880, 0.951056516295152865,0.945518575599316069, 0.939692620785907651,0.933580426497200966, 0.927183854566786536,0.920504853452439486, 0.913545457642599978,0.906307787036649048, 0.898794046299166149,0.891006524188367122, 0.882947592858926211,0.874619707139395186, 0.866025403784438042,0.857167300702111778, 0.848048096156425624,0.838670567945423717, 0.829037572555041513,0.819152044288991688, 0.809016994374947451,0.798635510047293051, 0.788010753606722236,0.777145961456971346, 0.766044443118978569,0.754709580222772680, 0.743144825477395132,0.731353701619171459, 0.719339800338652302,0.707106781186548794, 0.694658370458998808,0.681998360062500142, 0.669130606358860014,0.656059028990509274, 0.642787609686541472,0.629320391049839833, 0.615661475325660845,0.601815023152051043, 0.587785252292476135,0.573576436351049268, 0.559192903470750236,0.544639035015030637, 0.529919264233208676,0.515038074910058152, 0.500000000000004219,0.484809620246341444, 0.469471562785895413,0.453990499739551634, 0.438371146789082455,0.422618261740704715, 0.406736643075805704,0.390731128489279489, 0.374606593415918010,0.358367949545306430, 0.342020143325675152,0.325568154457163306, 0.309016994374954279,0.292371704722743819, 0.275637355817006491,0.258819045102528289, 0.241921895599675502,0.224951054343872997, 0.207911690817767558,0.190808995376553270, 0.173648177666939019,0.156434465040239751, 0.139173100960074542,0.121869343405156802, 0.104528463267663005,0.087155742747667922, 0.069756473744135267,0.052335956242954007, 0.034899496702511350,0.017452406437294093, 0.000000000000010781,-0.017452406437272534, -0.034899496702489805,-0.052335956242932476, -0.069756473744113756,-0.087155742747646453, -0.104528463267641564,-0.121869343405135402, -0.139173100960053198,-0.156434465040218462, -0.173648177666917786,-0.190808995376532092, -0.207911690817746464,-0.224951054343851986, -0.241921895599654574,-0.258819045102507472, -0.275637355816985785,-0.292371704722723225, -0.309016994374933796,-0.325568154457142933, -0.342020143325654891,-0.358367949545286335, -0.374606593415898026,-0.390731128489259616, -0.406736643075785997,-0.422618261740685175, -0.438371146789063082,-0.453990499739532427, -0.469471562785876373,-0.484809620246322570, -0.499999999999985512,-0.515038074910039723, -0.529919264233190468,-0.544639035015012540, -0.559192903470732361,-0.573576436351031616, -0.587785252292458593,-0.601815023152033834, -0.615661475325643859,-0.629320391049823069, -0.642787609686525041,-0.656059028990493065, -0.669130606358844027,-0.681998360062484377, -0.694658370458983265,-0.707106781186533584, -0.719339800338637314,-0.731353701619156804, -0.743144825477380699,-0.754709580222758580, -0.766044443118964691,-0.777145961456957690, -0.788010753606709025,-0.798635510047280062, -0.809016994374934795,-0.819152044288979364, -0.829037572555029412,-0.838670567945412060, -0.848048096156414188,-0.857167300702100676, -0.866025403784427272,-0.874619707139384750, -0.882947592858916108,-0.891006524188357352, -0.898794046299156713,-0.906307787036639945, -0.913545457642591208,-0.920504853452430938, -0.927183854566778320,-0.933580426497192972, -0.939692620785899990,-0.945518575599308742, -0.951056516295145871,-0.956304755963028108, -0.961261695938311900,-0.965925826289061651, -0.970295726275990256,-0.974370064785229362, -0.978147600733800138,-0.981627183447658869, -0.984807753012203468,-0.987688340595133552, -0.990268068741566587,-0.992546151641318764, -0.994521895368270514,-0.996194698091743103, -0.997564050259822310,-0.998629534754572390, -0.999390827019094763,-0.999847695156390714, -1.000000000000000000,-0.999847695156391714, -0.999390827019096761,-0.998629534754575388, -0.997564050259826307,-0.996194698091748099, -0.994521895368276398,-0.992546151641325647, -0.990268068741574470,-0.987688340595142433, -0.984807753012213349,-0.981627183447669860, -0.978147600733812128,-0.974370064785242240, -0.970295726276004022,-0.965925826289076417, -0.961261695938327665,-0.956304755963044872, -0.951056516295163523,-0.945518575599327393, -0.939692620785919530,-0.933580426497213511, -0.927183854566799748,-0.920504853452453253, -0.913545457642614411,-0.906307787036664148, -0.898794046299181804,-0.891006524188383331, -0.882947592858942976,-0.874619707139412506, -0.866025403784455916,-0.857167300702130208, -0.848048096156444498,-0.838670567945443146, -0.829037572555061497,-0.819152044289012227, -0.809016994374968434,-0.798635510047314479, -0.788010753606744219,-0.777145961456993772, -0.766044443119001550,-0.754709580222796106, -0.743144825477418891,-0.731353701619195773, -0.719339800338677060,-0.707106781186574107, -0.694658370459024455,-0.681998360062526232, -0.669130606358886548,-0.656059028990536253, -0.642787609686568784,-0.629320391049867478, -0.615661475325688934,-0.601815023152079465, -0.587785252292504890,-0.573576436351078467, -0.559192903470779767,-0.544639035015060502, -0.529919264233238985,-0.515038074910088794, -0.500000000000035083,-0.484809620246372641, -0.469471562785926888,-0.453990499739583386, -0.438371146789114541,-0.422618261740737022, -0.406736643075838289,-0.390731128489312296, -0.374606593415951039,-0.358367949545339737, -0.342020143325708625,-0.325568154457197001, -0.309016994374988196,-0.292371704722777903, -0.275637355817040797,-0.258819045102562761, -0.241921895599710085,-0.224951054343907719, -0.207911690817802419,-0.190808995376588242, -0.173648177666974129,-0.156434465040274973, -0.139173100960109847,-0.121869343405192190, -0.104528463267698463,-0.087155742747703435, -0.069756473744170822,-0.052335956242989604, -0.034899496702546981,-0.017452406437329739 }; static double costable[360] = { 1.000000000000000000,0.999847695156391270, 0.999390827019095762,0.998629534754573833, 0.997564050259824198,0.996194698091745545, 0.994521895368273290,0.992546151641321983, 0.990268068741570362,0.987688340595137770, 0.984807753012208020,0.981627183447663976, 0.978147600733805689,0.974370064785235246, 0.970295726275996473,0.965925826289068312, 0.961261695938318894,0.956304755963035436, 0.951056516295153531,0.945518575599316735, 0.939692620785908317,0.933580426497201743, 0.927183854566787313,0.920504853452440264, 0.913545457642600867,0.906307787036649826, 0.898794046299166927,0.891006524188367788, 0.882947592858926766,0.874619707139395630, 0.866025403784438486,0.857167300702112112, 0.848048096156425846,0.838670567945423828, 0.829037572555041513,0.819152044288991576, 0.809016994374947229,0.798635510047292607, 0.788010753606721681,0.777145961456970569, 0.766044443118977680,0.754709580222771681, 0.743144825477393911,0.731353701619170127, 0.719339800338650748,0.707106781186547129, 0.694658370458996810,0.681998360062498032, 0.669130606358857682,0.656059028990506721, 0.642787609686538697,0.629320391049836836, 0.615661475325657626,0.601815023152047601, 0.587785252292472471,0.573576436351045382, 0.559192903470746128,0.544639035015026307, 0.529919264233204124,0.515038074910053378, 0.499999999999999112,0.484809620246336115, 0.469471562785889862,0.453990499739545861, 0.438371146789076460,0.422618261740698442, 0.406736643075799154,0.390731128489272661, 0.374606593415910960,0.358367949545299158, 0.342020143325667547,0.325568154457155479, 0.309016994374946230,0.292371704722735493, 0.275637355816997887,0.258819045102519463, 0.241921895599666398,0.224951054343863643, 0.207911690817757955,0.190808995376543389, 0.173648177666928888,0.156434465040229398, 0.139173100960063939,0.121869343405145950, 0.104528463267651903,0.087155742747656584, 0.069756473744123679,0.052335956242942190, 0.034899496702499304,0.017452406437281822, -0.000000000000001715,-0.017452406437285253, -0.034899496702502732,-0.052335956242945618, -0.069756473744127107,-0.087155742747659998, -0.104528463267655317,-0.121869343405149350, -0.139173100960067325,-0.156434465040232784, -0.173648177666932274,-0.190808995376546747, -0.207911690817761285,-0.224951054343866974, -0.241921895599669701,-0.258819045102522793, -0.275637355817001217,-0.292371704722738768, -0.309016994374949450,-0.325568154457158698, -0.342020143325670822,-0.358367949545302322, -0.374606593415914124,-0.390731128489275825, -0.406736643075802318,-0.422618261740701329, -0.438371146789079125,-0.453990499739548303, -0.469471562785892083,-0.484809620246338169, -0.500000000000000999,-0.515038074910054933, -0.529919264233205567,-0.544639035015027528, -0.559192903470747127,-0.573576436351046159, -0.587785252292473026,-0.601815023152048045, -0.615661475325657848,-0.629320391049836947, -0.642787609686538697,-0.656059028990506499, -0.669130606358857238,-0.681998360062497477, -0.694658370458996033,-0.707106781186546240, -0.719339800338649749,-0.731353701619168906, -0.743144825477392579,-0.754709580222770238, -0.766044443118976237,-0.777145961456968903, -0.788010753606719905,-0.798635510047290720, -0.809016994374945231,-0.819152044288989578, -0.829037572555039404,-0.838670567945421719, -0.848048096156423625,-0.857167300702109891, -0.866025403784436265,-0.874619707139393410, -0.882947592858924435,-0.891006524188365345, -0.898794046299164484,-0.906307787036647494, -0.913545457642598424,-0.920504853452437932, -0.927183854566784982,-0.933580426497199412, -0.939692620785906096,-0.945518575599314515, -0.951056516295151311,-0.956304755963033326, -0.961261695938316785,-0.965925826289066314, -0.970295726275994586,-0.974370064785233358, -0.978147600733803912,-0.981627183447662310, -0.984807753012206577,-0.987688340595136327, -0.990268068741569030,-0.992546151641320873, -0.994521895368272291,-0.996194698091744657, -0.997564050259823532,-0.998629534754573389, -0.999390827019095318,-0.999847695156391048, -1.000000000000000000,-0.999847695156391381, -0.999390827019096095,-0.998629534754574499, -0.997564050259825086,-0.996194698091746544, -0.994521895368274622,-0.992546151641323537, -0.990268068741572027,-0.987688340595139658, -0.984807753012210241,-0.981627183447666418, -0.978147600733808353,-0.974370064785238244, -0.970295726275999804,-0.965925826289071865, -0.961261695938322669,-0.956304755963039654, -0.951056516295157972,-0.945518575599321509, -0.939692620785913424,-0.933580426497207072, -0.927183854566793086,-0.920504853452446370, -0.913545457642607195,-0.906307787036656598, -0.898794046299173921,-0.891006524188375226, -0.882947592858934649,-0.874619707139403846, -0.866025403784447034,-0.857167300702120993, -0.848048096156435061,-0.838670567945433487, -0.829037572555051505,-0.819152044289001902, -0.809016994374957998,-0.798635510047303709, -0.788010753606733227,-0.777145961456982559, -0.766044443118990004,-0.754709580222784449, -0.743144825477407012,-0.731353701619183672, -0.719339800338664737,-0.707106781186561450, -0.694658370459011576,-0.681998360062513242, -0.669130606358873337,-0.656059028990522708, -0.642787609686555128,-0.629320391049853711, -0.615661475325674945,-0.601815023152065254, -0.587785252292490457,-0.573576436351063812, -0.559192903470765002,-0.544639035015045625, -0.529919264233223886,-0.515038074910073473, -0.500000000000019651,-0.484809620246357043, -0.469471562785911123,-0.453990499739567510, -0.438371146789098498,-0.422618261740720869, -0.406736643075822024,-0.390731128489295920, -0.374606593415934497,-0.358367949545323083, -0.342020143325691917,-0.325568154457180181, -0.309016994374971266,-0.292371704722760861, -0.275637355817023644,-0.258819045102545497, -0.241921895599692793,-0.224951054343890372, -0.207911690817784989,-0.190808995376570756, -0.173648177666956560,-0.156434465040257376, -0.139173100960092194,-0.121869343405174496, -0.104528463267680741,-0.087155742747685686, -0.069756473744153044,-0.052335956242971805, -0.034899496702529162,-0.017452406437311916, -0.000000000000028605,0.017452406437254715, 0.034899496702471985,0.052335956242914670, 0.069756473744095979,0.087155742747628689, 0.104528463267623842,0.121869343405117708, 0.139173100960035545,0.156434465040200865, 0.173648177666900216,0.190808995376514606, 0.207911690817729033,0.224951054343834611, 0.241921895599637282,0.258819045102490264, 0.275637355816968632,0.292371704722706127, 0.309016994374916809,0.325568154457126058, 0.342020143325638126,0.358367949545269682, 0.374606593415881484,0.390731128489243240, 0.406736643075769733,0.422618261740669021, 0.438371146789047095,0.453990499739516551, 0.469471562785860608,0.484809620246306972, 0.499999999999970080,0.515038074910024402, 0.529919264233175369,0.544639035014997663, 0.559192903470717484,0.573576436351016961, 0.587785252292444271,0.601815023152019624, 0.615661475325629759,0.629320391049809191, 0.642787609686511385,0.656059028990479520, 0.669130606358830815,0.681998360062471387, 0.694658370458970387,0.707106781186521038, 0.719339800338624991,0.731353701619144592, 0.743144825477368709,0.754709580222746812, 0.766044443118953255,0.777145961456946477, 0.788010753606698033,0.798635510047269292, 0.809016994374924359,0.819152044288969150, 0.829037572555019531,0.838670567945402290, 0.848048096156404752,0.857167300702091572, 0.866025403784418391,0.874619707139376090, 0.882947592858907782,0.891006524188349247, 0.898794046299148941,0.906307787036632395, 0.913545457642583991,0.920504853452423943, 0.927183854566771659,0.933580426497186644, 0.939692620785893884,0.945518575599302968, 0.951056516295140320,0.956304755963022890, 0.961261695938306904,0.965925826289056988, 0.970295726275985926,0.974370064785225365, 0.978147600733796474,0.981627183447655538, 0.984807753012200360,0.987688340595130776, 0.990268068741564034,0.992546151641316543, 0.994521895368268627,0.996194698091741548, 0.997564050259821089,0.998629534754571502, 0.999390827019094097,0.999847695156390381 int dtoi(double x) { /* Rounds a floating point number to an integer */ int y; y = x; if (y < (x + 0.5)) y++; return(y); } int getmode(int *ncols) { /* Returns current video mode and number of columns in ncols */ union REGS inreg,outreg; inreg.h.ah = 0x0F; int86(0x10, &inreg, &outreg); *ncols = outreg.h.ah; return(outreg.h.al); } void setvideo(unsigned char mode) { /* Sets the video display mode and clears the screen */ /* (modes above 127 on VGA monitors do not clear the screen) */ asm mov ax , mode; asm int 10h; /* Set global variables */ switch(mode) { case 0: case 1: case 2: case 3: break; case 4: case 9: case 13: case 19: case 5: maxx = 320; maxy = 200; break; case 14: case 10: case 6: maxx = 640; maxy = 200; break; case 7: maxx = 720; maxy = 400; break; case 8: maxx = 160; maxy = 200; break; case 15: case 16: maxx = 640; maxy = 350; break; case 17: case 18: maxx = 640; maxy = 480; break; } _dmode = mode; } void getcursor(int *row, int *col) { /* Returns the cursor position and size for the currently active video display page */ union REGS inreg,outreg; inreg.h.bh = 0; inreg.h.ah = 0x03; int86(0x10, &inreg, &outreg); *row = outreg.h.dh; *col = outreg.h.dl; } void setcursor(char status) { /* Set cursor size, if status is 0x20 the cursor is not displayed */ asm mov ah,1 asm mov ch,status asm mov cl,7 asm int 10h } void at(int row, int col) { /* Position text cursor on current screen */ asm mov bh , 0; asm mov dh , row; asm mov dl , col; asm mov ah , 02h; asm int 10h; } void clsc(unsigned char attrib) { /* Clear display and fill with new attribute */ asm mov ax , 073dh; asm mov bh , attrib; asm mov cx , 0; asm mov dx , 3c4fh; asm int 10h; /* Now move text cursor to origin (0,0) */ asm mov bh , 0; asm mov dx , 0; asm mov ah , 02h; asm int 10h; } void clrwindow(int x1, int y1,int x2, int y2, unsigned char attrib) { /* Clear a text window */ union REGS inreg,outreg; inreg.h.al = (unsigned char)(y2 - y1 + 1); inreg.h.bh = attrib; inreg.h.cl = (unsigned char)x1; inreg.h.ch = (unsigned char)y1; inreg.h.dl = (unsigned char)x2; inreg.h.dh = (unsigned char)y2; inreg.h.ah = 0x06; int86(0x10, &inreg, &outreg); } void scrollsc(void) { /* Scroll the text screen */ asm mov al,01h; asm mov cx,0 asm mov dx,3c4fh; asm mov bh,attribute; asm mov ah, 06h; asm int 10h; } void winscr(unsigned char left,unsigned char top, unsigned char right,unsigned char bottom, unsigned char direction, unsigned char numlines) { /* Scroll a text window */ asm mov al , numlines; asm mov cl , left; asm mov ch , top; asm mov dl , right; asm mov dh , bottom; asm mov bh , attribute; asm mov ah , direction; asm int 10h; } unsigned char attr(int foregrnd, int backgrnd) { /* Convert a colour pair into a single attribute */ return((char)((backgrnd << 4) + foregrnd)); } void shadewin(unsigned char left, unsigned char top, unsigned char right, unsigned char bottom) { /* Shade a text window */ int row; int col; int oldrow; int oldcol; /* Preserve existing coords */ getcursor(&oldrow,&oldcol); col = right + 1; for (row = top + 1; row <= bottom + 1; row++) { /* Move to row,col */ asm mov bh , 0; asm mov dh , row; asm mov dl , col; asm mov ah , 02h; asm int 10h; /* Get character */ asm mov ah , 0Fh; asm int 10h; asm mov ah , 08h; asm int 10h; /* Write in attribute 7 */ asm mov ah , 09h; asm mov bl, 07h; asm mov cx, 01h; asm int 10h; } bottom++; for (col = left + 1; col <= right + 1; col++) { /* Move to row,col */ asm mov bh , 0; asm mov dh , bottom; asm mov dl , col; asm mov ah , 02h; asm int 10h; /* Get character */ asm mov ah , 0Fh; asm int 10h; asm mov ah , 08h; asm int 10h; /* Write in attribute 7 */ asm mov ah , 09h; asm mov bl, 07h; asm mov cx, 01h; asm int 10h; } /* Return to original position */ /* Move to row,col */ asm mov bh , 0; asm mov dh , oldrow; asm mov dl , oldcol; asm mov ah , 02h; asm int 10h; } void bprintf(char *format, ...) { /* print text to graphics screen correctly */ va_list arg_ptr; char output[1000]; int c, r, n, p = 0; unsigned char text; unsigned char page; va_start(arg_ptr, format); vsprintf(output, format, arg_ptr); if (strcmp(output,"\r\n") == 0) fputs(output,stdout); else { asm mov ah , 0Fh; asm int 10h; asm mov page, bh; getmode(&n); getcursor(&r,&c); while (output[p]) { text = output[p++]; asm mov bh , page; asm mov bl , attribute; asm mov cx , 01h; asm mov ah , 09h; asm mov al , text; asm int 10h; c++; if (c < (n-1)) at( r, c); else { c = 0; at(++r,0); } } } } void wrtstr(char *output) { /* TTY text output. The original screen attributes remain unaffected */ int p = 0; unsigned char page; unsigned char text; asm mov ah , 0Fh; asm int 10h; asm mov page, bh; while (output[p]) { text = output[p++]; asm mov bh , page; asm mov ah , 0Eh; asm mov al , text; asm int 10h; } } void getwin(int left, int top, int right, int bottom, char *buffer) { /* Read a text window into a variable */ int oldleft; unsigned char page; asm mov ah , 0Fh; asm int 10h; asm mov page, bh; while(top <= bottom) { oldleft = left; while(left <= right) { at(top,left); asm mov bh , page; asm mov ah , 08h; asm int 10h; *buffer++ = _AL; *buffer++ = _AH; left++; } left = oldleft; top++; } } void putwin(int left, int top, int right, int bottom,char *buffer) { /* Display a text window from a variable */ int oldleft; unsigned char chr; unsigned char attr; unsigned char page; asm mov ah , 0Fh; asm int 10h; asm mov page, bh; while(top <= bottom) { oldleft = left; while(left <= right) { at(top,left); chr = *buffer++; attr = *buffer++; asm mov bh , page; asm mov ah , 09h; asm mov al, chr; asm mov bl, attr; asm mov cx,1; asm int 10h; left++; } left = oldleft; top++; } } void setpalette(unsigned char palno) { /* Sets the video palette */ asm mov bh,01h; asm mov bl,palno; asm mov ah,0Bh; asm int 10h; } void setborder(unsigned char x) { /* Set border colour */ asm mov bh, x; asm mov ax ,1001h; asm int 10h; } void setlines(unsigned char x) { /* Set text display number of lines */ asm mov ah,11h; asm mov al,x; asm mov bl,0; asm int 10h; } void graphbackground(unsigned char colour) { /* Selects the background colour for a graphics mode */ asm mov bh,0; asm mov bl,colour; asm mov ah, 0Bh; asm int 10h; } void plot(int x, int y, unsigned char colour) { /* Sets a pixel at the specified coordinates to the specified colour. The variables _lastx and _lasty are updated. */ unsigned char far *video; _lastx = x; _lasty = y; if (_dmode == 19) { video = MK_FP(0xA000,0); video[x+y*320] = colour; } else { asm mov al , colour; asm mov bh , 00; asm mov cx , x; asm mov dx , y; asm mov ah , 0Ch; asm int 10h; } } int pixset(int x, int y) { /* Returns the colour of the specified pixel */ asm mov cx ,x; asm mov dx ,y; asm mov ah ,0Dh; asm int 10h; return(_AL); } void move(int x, int y) { /* Sets the value of the variables _lastx and _lasty */ _lastx = x; _lasty = y; } void line(int a, int b, int c, int d, int col) { /* Draws a straight line from (a,b) to (c,d) in colour col */ int u; int v; int d1x; int d1y; int d2x; int d2y; int m; int n; int s; int i; if (a < 0) a = 0; else if (a > maxx) a = maxx; if (c < 0) c = 0; else if (c > maxx) c = maxx; if (b < 0) b = 0; else if (b > maxy) b = maxy; if (d < 0) d = 0; else if (d > maxy) d = maxy; u = c - a; v = d - b; if (u == 0) { d1x = 0; m = 0; } else { m = abs(u); if (u < 0) d1x = -1; else if (u > 0) d1x = 1; } if ( v == 0) { d1y = 0; n = 0; } else { n = abs(v); if (v < 0) d1y = -1; else if (v > 0) d1y = 1; } if (m > n) { d2x = d1x; d2y = 0; } else { d2x = 0; d2y = d1y; m = n; n = abs(u); } s = m / 2; for (i = 0; i <= m; i++) { asm mov al , col; asm mov bh , 0; asm mov ah ,0Ch; asm mov cx ,a; asm mov dx ,b; asm int 10h; s += n; if (s >= m) { s -= m; a += d1x; b += d1y; } else { a += d2x; b += d2y; } } _lastx = a; _lasty = b; } void ellipse(int x, int y, int xrad, int yrad,double incline,int col) { /* Draws an ellipse */ int f; float a; float b; float c; float d; int cols; double div; incline = 1 / sintable[(int)incline]; if (getmode(&cols) == 6) div = 2.2; else div = 1.3; /* Compensate for pixel shape */ a = x + xrad; b = y + sintable[0] * yrad + xrad/incline / div; for(f = 5; f < 360; f += 5) { c = x + costable[f] * xrad; d = y + sintable[f] * yrad + (costable[f] * xrad)/incline/div; line(a,b,c,d,col); a = c; b = d; } /* Ensure join */ line(a,b,x + xrad,y + sintable[0] * yrad + xrad/incline / div,col); } void polygon(int x, int y, int rad, int col, int sides, int start) { /* Draws a regular polygon */ double f; double div; double a; double b; double c; double d; double aa; double bb; int cols; double step; step = 360 / sides; if (getmode(&cols) == 6) div = 2.2; else div = 1.3; aa = a = x + costable[start] * rad; bb = b = y + sintable[start] * rad / div; for(f = start + step; f < start + 360; f += step) { c = x + costable[(int)f] * rad; d = y + sintable[(int)f] * rad / div; line(a,b,c,d,col); a = c; b = d; } line(a,b,aa,bb,col); } void arc(int x, int y, int rad, int start, int end,int col) { /* Draw an arc */ int f; float a; float b; float c; float d; int cols; float div; if (getmode(&cols) == 6) div = 2.2; else div = 1.3; a = x + costable[start] * rad; b = y + sintable[start] * rad / div; for(f = start; f <= end; f ++) { c = x + costable[f] * rad; d = y + sintable[f] * rad / div; line(a,b,c,d,col); a = c; b = d; } } void segm(int x, int y, int rad, int start, int end,int col) { /* Draw a segment of a circle */ int f; float a; float b; float c; float d; int cols; double div; if (getmode(&cols) == 6) div = 2.2; else div = 1.3; a = x + costable[start] * rad; b = y + sintable[start] * rad / div; line(x,y,a,b,col); for(f = start; f <= end ; f ++) { c = x + costable[f] * rad; d = y + sintable[f] * rad / div; line(a,b,c,d,col); a = c; b = d; } line(x,y,a,b,col); } void box(int xa,int ya, int xb, int yb, int col) { /* Draws a box for use in 2d histogram graphs etc */ line(xa,ya,xa,yb,col); line(xa,yb,xb,yb,col); line(xb,yb,xb,ya,col); line(xb,ya,xa,ya,col); } void tri(int xa,int ya, int xb, int yb, int xc, int yc,int col) { /* Draw a triangle */ line(xa,ya,xb,yb,col); line(xb,yb,xc,yc,col); line(xc,yc,xa,ya,col); } void fill(int x, int y, int col,int pattern) { /* Fill a boundered shape using a hatch pattern */ int xa; int ya; int bn; int byn; int hatch[10][8] = { 255,255,255,255,255,255,255,255, 128,64,32,16,8,4,2,1, 1,2,4,8,16,32,64,128, 1,2,4,8,8,4,2,1, 238,238,238,238,238,238,238,238, 170,85,170,85,170,85,170,85, 192,96,48,24,12,6,3,1, 62,62,62,0,227,227,227,0, 129,66,36,24,24,36,66,129, 146,36,146,36,146,36,146,36 }; /* Patterns for fill, each integer describes a row of dots */ xa = x; ya = y; /* Save Origin */ if(pixset(x,y)) return; bn = 1; byn = 0; do { if (hatch[pattern][byn] != 0) { /* If blank ignore */ do { if ((bn & hatch[pattern][byn]) == bn) { asm mov al , col; asm mov bh , 00; asm mov cx , x; asm mov dx , y; asm mov ah , 0Ch; asm int 10h; } x--; bn <<= 1; if (bn > 128) bn = 1; } while(!pixset(x,y) && (x > -1)); x = xa + 1; bn = 128; do { if ((bn & hatch[pattern][byn]) == bn) { asm mov al , col; asm mov bh , 00; asm mov cx , x; asm mov dx , y; asm mov ah , 0Ch; asm int 10h; } x++; bn >>=1; if (bn <1) bn = 128; } while((!pixset(x,y)) && (x <= maxx)); } x = xa; y--; bn = 1; byn++; if (byn > 7) byn = 0; } while(!pixset(x,y) && ( y > -1)); /* Now travel downwards */ y = ya + 1; byn = 7; bn = 1; do { /* Travel left */ if (hatch[pattern][byn] !=0) { do { if ( (bn & hatch[pattern][byn]) == bn) { asm mov al , col; asm mov bh , 00; asm mov cx , x; asm mov dx , y; asm mov ah , 0Ch; asm int 10h; } x--; bn <<= 1; if (bn > 128) bn = 1; } while(!pixset(x,y) && (x > -1)); /* Back to x origin */ x = xa + 1 ; bn = 128; /* Travel right */ do { if ((bn & hatch[pattern][byn]) == bn) { asm mov al , col; asm mov bh , 00; asm mov cx , x; asm mov dx , y; asm mov ah , 0Ch; asm int 10h; } x++; bn >>=1; if (bn <1) bn = 128; } while((!pixset(x,y)) && (x <= maxx)); } x = xa; bn = 1; y++; byn--; if (byn < 0) byn = 7; } while((!pixset(x,y)) && (y <= maxy)); } void invert(int xa,int ya, int xb, int yb, int col) { /* Invert a pixel window */ union REGS inreg,outreg; inreg.h.al = (unsigned char)(128 | col); inreg.h.ah = 0x0C; for(inreg.x.cx = (unsigned int)xa; inreg.x.cx <= (unsigned int)xb; inreg.x.cx++) for(inreg.x.dx = (unsigned)ya; inreg.x.dx <= (unsigned)yb; inreg.x.dx++) int86(0x10, &inreg, &outreg); } void circle(int x_centre , int y_centre, int radius, int colour) { int x,y,delta; int startx,endx,x1,starty,endy,y1; int asp_ratio; if (_dmode == 6) asp_ratio = 22; else asp_ratio = 13; y = radius; delta = 3 - 2 * radius; for(x = 0; x < y; ) { starty = y * asp_ratio / 10; endy = (y + 1) * asp_ratio / 10; startx = x * asp_ratio / 10; endx = (x + 1) * asp_ratio / 10; for(x1 = startx; x1 < endx; ++x1) { plot(x1+x_centre,y+y_centre,colour); plot(x1+x_centre,y_centre - y,colour); plot(x_centre - x1,y_centre - y,colour); plot(x_centre - x1,y + y_centre,colour); } for(y1 = starty; y1 < endy; ++y1) { plot(y1+x_centre,x+y_centre,colour); plot(y1+x_centre,y_centre - x,colour); plot(x_centre - y1,y_centre - x,colour); plot(x_centre - y1,x + y_centre,colour); } if (delta < 0) delta += 4 * x + 6; else { delta += 4*(x-y)+10; y--; } x++; } if(y) { starty = y * asp_ratio / 10; endy = (y + 1) * asp_ratio / 10; startx = x * asp_ratio / 10; endx = (x + 1) * asp_ratio / 10; for(x1 = startx; x1 < endx; ++x1) { plot(x1+x_centre,y+y_centre,colour); plot(x1+x_centre,y_centre - y,colour); plot(x_centre - x1,y_centre - y,colour); plot(x_centre - x1,y + y_centre,colour); } for(y1 = starty; y1 < endy; ++y1) { plot(y1+x_centre,x+y_centre,colour); plot(y1+x_centre,y_centre - x,colour); plot(x_centre - y1,y_centre - x,colour); plot(x_centre - y1,x + y_centre,colour); } } } void draw(int x, int y, int colour) { /* Draws a line from _lastx,_lasty to x,y */ line(_lastx,_lasty,x,y,colour); } void psprite(SPRITE *sprite,int x,int y) { int origx; int origy; int z; int count; int col; int pos; unsigned char far *video; if (_dmode == 19) { /* Super fast direct video write in mode 19 for sprites */ video = MK_FP(0xA000,0); origx = x; origy = y; if (sprite->x != -1) { /* This sprite has been displayed before */ /* replace background */ /* This must be done first in case the sprite overlaps itself */ x = sprite->x; y = sprite->y; col = 0; pos = x + y * 320; for(count = 0; count < 256; count++) { video[pos] = sprite->save[count]; col++; if (col == 16) { pos += 305; col = 0; } else pos++; } } x = origx; y = origy; col = 0; pos = x + y * 320; for(count = 0; count < 256; count++) { sprite->save[count] = video[pos]; z = sprite->data[count]; if (z != 255) video[pos] = z; col++; if (col == 16) { pos += 305; col = 0; } else pos++; } sprite->x = origx; sprite->y = origy; return; } origx = x; origy = y; if (sprite->x != -1) { /* This sprite has been displayed before */ /* replace background */ /* This must be done first in case the sprite overlaps itself */ x = sprite->x; y = sprite->y; col = 0; for(count = 0; count < 256; count++) { if ((x >= 0) && (y >= 0) && (x < maxx) && (y < maxy)) { z = sprite->save[count]; asm mov al , z; asm mov bh , 00; asm mov cx , x; asm mov dx , y; asm mov ah , 0Ch; asm int 10h; } col++; if (col == 16) { y++; x = sprite->x; col = 0; } else x++; } } x = origx; y = origy; col = 0; for(count = 0; count < 256; count++) { if ((x >= 0) && (y >= 0) && (x < maxx) && (y < maxy)) { asm mov cx , x; asm mov dx , y; asm mov ah , 0Dh; asm int 10h; asm mov z ,al; sprite->save[count] = z; z = sprite->data[count]; if (z != 255) { asm mov al , z; asm mov bh , 0; asm mov cx , x; asm mov dx , y; asm mov ah , 0Ch; asm int 10h; } } col++; if (col == 16) { y++; x = origx; col = 0; } else x++; } sprite->x = origx; sprite->y = origy; return; } Displaying A PCX File The following program is offered as a practical example of graphics with the IBM PC. It reads a file of the `PCX' format and displays the image on the screen. /* Read a PCX file and display image */ #include #include #include typedef struct { unsigned char man; unsigned char version; unsigned char encoding; unsigned char bpp; int xmin; int ymin; int xmax; int ymax; int hdpi; int vdpi; int colormap[24]; char reserved; unsigned char planes; int bpl; int palette; int hss; int vsize; char pad[54]; } PCX_HEADER; PCX_HEADER header; int x; int y; union REGS inreg,outreg; void setvideo(unsigned char mode) { /* Sets the video display mode and clears the screen */ inreg.h.al = mode; inreg.h.ah = 0x00; int86(0x10, &inreg, &outreg); } void plot(int x, int y, unsigned char colour) { if (x < header.xmax && y < header.ymax) { /* Direct video plot in modes 16 & 18 only! */ asm mov ax,y; asm mov dx,80; asm mul dx; asm mov bx,x; asm mov cl,bl; asm shr bx,1; asm shr bx,1; asm shr bx,1; asm add bx,ax; asm and cl,7; asm xor cl,7; asm mov ah,1; asm shl ah,cl; asm mov dx,3ceh; asm mov al,8; asm out dx,ax; asm mov ax,(02h shl 8) + 5; asm out dx,ax; asm mov ax,0A000h; asm mov es,ax; asm mov al,es:[bx]; asm mov al,byte ptr colour; asm mov es:[bx],al; asm mov ax,(0FFh shl 8 ) + 8; asm out dx,ax; asm mov ax,(00h shl 8) + 5; asm out dx,ax; } } void DISPLAY(unsigned char data) { int n; int bit; bit = 32; for (n = 0; n < 6; n++) { if (data & bit) plot(x,y,1); else plot(x,y,0); bit >>= 1; x++; } } main(int argc, char *argv[]) { int fp; int total_bytes; int n; unsigned char data; int count; int scan; if (argc != 2) { puts("USAGE IS getpcx "); exit(0); } setvideo(16); x = 0; y = 0; fp = open(argv[1],O_RDONLY|O_BINARY); _read(fp,&header,128); total_bytes = header.planes * header.bpl; for(scan = 0; scan <= header.ymax; scan++) { x = 0; /* First scan line */ for(n = 0; n < total_bytes; n++) { /* Read byte */ _read(fp,&data,1); count = data & 192; if (count == 192) { count = data & 63; n += count - 1; _read(fp,&data,1); while(count) { DISPLAY(data); count--; } } else DISPLAY(data); } x = 0; y++; } } Drawing Circles What has drawing circles got to do with advanced C programming? Well quite a lot, it is a task which is often desired by modern programmers, and it is a task which can be attacked from a number of angles. This example illustrates some of the ideas already discussed for replacing floating point numbers with integers, and using lookup tables rather than repeat calls to maths functions. A circle may be drawn by plotting each point on its circumference. The location of any point is given by; Xp = Xo + Sine(Angle) * Radius Yp = Yo + Cosine(Angle) * Radius Where Xp,Yp is the point to be plotted, and Xo,Yo is the centre of the circle. Thus, the simplest way to draw a circle is to calculate Xp and Yp for each angle between 1 and 360 degrees and to plot these points. There is however one fundamental error with this. As the radius of the circle increases, so also does the length of the arc between each angular point. Thus a circle of sufficient radius will be drawn with a dotted line rather than a solid line. The problem of the distance between the angular points may be tackled in two ways; 1)The number of points to be plotted can be increased, to say every 30 minutes. 2)A straight line may be drawn between each angular point. The simplest circle drawing pseudo-code may then look like this; FOR angle = 1 TO 360 PLOT Xo + SINE(angle) * radius, Yo + COSINE(angle) * radius NEXT angle This code has two major disadvantages; 1) It uses REAL numbers for the sine and cosine figures 2) It makes numerous calculations of sine and cosine values Both of these disadvantages result in a very slow piece of code. Since a circle is a regular figure with two axis of symmetry, one in both the X and Y axis, one only needs to calculate the relative offsets of points in one quadrant of the circle and then these offsets may be applied to the other three quadrants to produce a faster piece of code. Faster because the slow sine and cosine calculations are only done 90 times instead of 360 times; FOR angle = 1 TO 90 Xa = SINE(angle) * radius Ya = COSINE(angle) * radius PLOT Xo + Xa, Yo + Ya PLOT Xo + Xa, Yo - Ya PLOT Xo - Xa, Yo + Ya PLOT Xo - Xa, Yo - Ya NEXT angle A further enhancement may be made by making use of sine and cosine lookup tables instead of calculating them. This means calculating the sine and cosine values for each required angle and storing them in a table. Then, instead of calculating the values for each angle the circle drawing code need only retrieve the values from a table; FOR angle = 1 TO 90 Xa = SINE[angle] * radius Ya = COSINE[angle] * radius PLOT Xo + Xa, Yo + Ya PLOT Xo + Xa, Yo - Ya PLOT Xo - Xa, Yo + Ya PLOT Xo - Xa, Yo - Ya NEXT angle Most computer languages work in RADIANS rather than DEGREES. There being approximately 57 degrees in one radian, 2 * PI radians in one circle. This implies that to calculate sine and cosine values of sufficient points to draw a reasonable circle using radians one must again use real numbers, that is numbers which have figures following a decimal point. Real number arithmetic, also known as floating point arithmetic, is horrendously slow to calculate. Integer arithmetic on the other hand is very quick to calculate, but less precise. To use integer arithmetic in circle drawing code requires ingenuity. If one agrees to use sine and cosine lookup tables for degrees, rather than radians. Then the sine value of an angle of 1 degree is; 0.0175 Which, truncated to an integer becomes zero! To overcome this the sine and cosine values held in the table should be multiplied by some factor, say 10000. Then, the integer value of the sine of an angle of 1 degree becomes; 175 If the sine and cosine values have been multiplied by a factor, then when the calculation of the point's offset is carried out one must remember to divide the result by the same factor. Thus the calculation becomes; Xa = SINE[angle] * radius / factor Ya = COSINE[angle] * radius / factor The final obstacle to drawing circles on a computer is the relationship between the width of the display screen and its height. This ratio between width and height is known as the "aspect ratio" and varies upon video display mode. The IBM VGA 256 colour mode for example can display 320 pixels across and 200 up the screen. This equates to an aspect ratio of 1:1.3. If the circle drawing code ignores the aspect ratio, then the shape displayed will often be ovalar to a greater or lesser degree due to the rectangular shape of the display pixels. Thus in order to display a true circle, the formulae to calculate each point on the circumference must be amended to calculate a slight ellipse in compensation of the distorting factor of the display. The offset formulae then become; Xa = SINE[angle] * radius / factor Ya = COSINE[angle] * radius / (factor * aspect ratio) The following short C program illustrates a practical circle drawing code segment, in a demonstrable form; /* Circles.c A demonstration circle drawing program for the IBM PC */ #include int sintable[91] = {0,175,349,523,698, 872,1045,1219,1392, 1564,1736,1908,2079, 2250,2419,2588,2756, 2924,3090,3256,3420, 3584,3746,3907,4067, 4226,4384,4540,4695, 4848,5000,5150,5299, 5446,5592,5736,5878, 6018,6157,6293,6428, 6561,6691,6820,6947, 7071,7193,7314,7431, 7547,7660,7771,7880, 7986,8090,8192,8290, 8387,8480,8572,8660, 8746,8829,8910,8988, 9063,9135,9205,9272, 9336,9397,9455,9511, 9563,9613,9659,9703, 9744,9781,9816,9848, 9877,9903,9925,9945, 9962,9976,9986,9994, 9998,10000 }; int costable[91] = { 10000,9998,9994,9986,9976, 9962,9945,9925,9903, 9877,9848,9816,9781, 9744,9703,9659,9613, 9563,9511,9455,9397, 9336,9272,9205,9135, 9063,8988,8910,8829, 8746,8660,8572,8480, 8387,8290,8192,8090, 7986,7880,7771,7660, 7547,7431,7314,7193, 7071,6947,6820,6691, 6561,6428,6293,6157, 6018,5878,5736,5592, 5446,5299,5150,5000, 4848,4695,4540,4384, 4226,4067,3907,3746, 3584,3420,3256,3090, 2924,2756,2588,2419, 2250,2079,1908,1736, 1564,1392,1219,1045, 872,698,523,349, 175,0 }; void setvideo(unsigned char mode) { /* Sets the video display mode for an IBM PC */ asm mov al , mode; asm mov ah , 00; asm int 10h; } void plot(int x, int y, unsigned char colour) { /* Code for IBM PC BIOS ROM */ /* Sets a pixel at the specified coordinates to a specified colour */ /* Return if out of range */ if (x < 0 || y < 0 || x > 320 || y > 200) return; asm mov al , colour; asm mov bh , 0; asm mov cx , x; asm mov dx , y; asm mov ah, 0Ch; asm int 10h; } void Line(int a, int b, int c, int d, int col) { /* Draws a straight line from point a,b to point c,d in colour col */ int u; int v; int d1x; int d1y; int d2x; int d2y; int m; int n; double s; /* The only real number variable, but it's essential */ int i; u = c - a; v = d - b; if (u == 0) { d1x = 0; m = 0; } else { m = abs(u); if (u < 0) d1x = -1; else if (u > 0) d1x = 1; } if ( v == 0) { d1y = 0; n = 0; } else { n = abs(v); if (v < 0) d1y = -1; else if (v > 0) d1y = 1; } if (m > n) { d2x = d1x; d2y = 0; } else { d2x = 0; d2y = d1y; m = n; n = abs(u); } s = m / 2; for (i = 0; i <= m; i++) { plot(a,b,col); s += n; if (s >= m) { s -= m; a += d1x; b += d1y; } else { a += d2x; b += d2y; } } } void Circle(int x, int y, int rad, int col) { /* Draws a circle about origin x,y */ /* With a radius of rad */ /* The col parameter defines the colour for plotting */ int f; long xa; long ya; int a1; int b1; int a2; int b2; int a3; int b3; int a4; int b4; /* Calculate first point in each segment */ a1 = x + ((long)(costable[0]) * (long)(rad) + 5000) / 10000; b1 = y + ((long)(sintable[0]) * (long)(rad) + 5000) / 13000; a2 = x - ((long)(costable[0]) * (long)(rad) + 5000) / 10000; b2 = y + ((long)(sintable[0]) * (long)(rad) + 5000) / 13000; a3 = x - ((long)(costable[0]) * (long)(rad) + 5000) / 10000; b3 = y - ((long)(sintable[0]) * (long)(rad) + 5000) / 13000; a4 = x + ((long)(costable[0]) * (long)(rad) + 5000) / 10000; b4 = y - ((long)(sintable[0]) * (long)(rad) + 5000) / 13000; /* Start loop at second point */ for(f = 1; f <= 90; f++) { /* Calculate offset to new point */ xa = ((long)(costable[f]) * (long)(rad) + 5000) / 10000; ya = ((long)(sintable[f]) * (long)(rad) + 5000) / 13000; /* Draw a line from the previous point to the new point in each segment */ Line(a1,b1,x + xa, y + ya,col); Line(a2,b2,x - xa, y + ya,col); Line(a3,b3,x - xa, y - ya,col); Line(a4,b4,x + xa, y - ya,col); /* Update the previous point in each segment */ a1 = x + xa; b1 = y + ya; a2 = x - xa; b2 = y + ya; a3 = x - xa; b3 = y - ya; a4 = x + xa; b4 = y - ya; } } main() { int n; /* Select VGA 256 colour 320 x 200 video mode */ setvideo(19); /* Draw some circles */ for(n = 0; n < 100; n++) Circle(160,100,n,n + 20); } Vesa Mode The VESA BIOS provides a number of new, and exciting video modes not supported by the standard BIOS. These modes vary from one video card to another, but most support the following modes: Mode Display 0x54 Text 16 colours 132 x 43 0x55 Text 16 colours 132 x 25 0x58 Graphics 16 colours 800 x 600 0x5C Graphics 256 colours 800 x 600 0x5D Graphics 16 colours 1024 x 768 0x5F Graphics 256 colours 640 x 480 0x60 Graphics 256 colours 1024 x 768 0x64 Graphics 64k colours 640 x 480 0x65 Graphics 64k colours 800 x 600 0x6A Graphics 16 colours 800 x 600 0x6C Graphics 16 colours 1280 x 1024 0x70 Graphics 16m colours 320 x 200 0x71 Graphics 16m colours 640 x 480 These modes are in addition to the standard BIOS video modes described earlier. Setting a VESA video mode requires a call to a different BIOS function than the standard BIOS, as illustrated in the following example which enables any VESA or standard display mode to be selected from the DOS command line. #include #include void setvideo(int mode) { /* Sets the video display to a VESA or normal mode and clears the screen */ union REGS inreg,outreg; inreg.h.ah = 0x4f; inreg.h.al = 0x02; inreg.x.bx = mode; int86(0x10, &inreg, &outreg); } main(int argc, char *argv[]) { setvideo(atoi(argv[1])); } Plotting pixels in a VESA mode graphics display can be acgieved with the standard BIOS plot functiona call, as illustrated here; void plot(int x, int y, unsigned char colour) { asm mov al , colour; asm mov bh , 00; asm mov cx , x; asm mov dx , y; asm mov ah , 0Ch; asm int 10h; } Or, in a 800 x 600 resolution mode you can use this fast direct video access plot function; void plot( int x, int y, unsigned char pcolor) { /* Fast 800 x 600 mode (0x58 or 0x102) plotting */ asm mov ax,y; asm mov dx,800/8; asm mul dx; asm mov bx,x; asm mov cl,bl; asm shr bx,1; asm shr bx,1; asm shr bx,1; asm add bx,ax; asm and cl,7; asm xor cl,7; asm mov ah,1; asm shl ah,cl; asm mov dx,03CEh; asm mov al,8; asm out dx,ax; asm mov ax,(02h shl 8) + 5; asm out dx,ax; asm mov ax,0A000h; asm mov es,ax; asm mov al,es:[bx]; asm mov al,byte ptr pcolor; asm mov es:[bx],al; asm mov ax,(0FFh shl 8 ) + 8; asm out dx,ax; asm mov ax,(00h shl 8) + 5; asm out dx,ax; } There are lots more functions supported by the VESA BIOS, but this will get you going with the basic operations. Remember though that when using VESA display modes, that the direct console I/O functions in the C compiler library may not function correctly. DIRECTORY SEARCHING WITH THE IBM PC AND DOS Amongst the many functions provided by DOS for programmers, are a pair of functions; "Find first" and "Find next" which are used to search a DOS directory for a specified file name or names. The first function, "Find first" is accessed via DOS interrupt 21, function 4E. It takes an ascii string file specification, which can include wildcards, and the required attribute for files to match. Upon return the function fills the disk transfer area (DTA) with details of the located file and returns with the carry flag clear. If an error occurs, such as no matching files are located, the function returns with the carry flag set. Following a successful call to "Find first", a program can call "Find next", DOS interrupt 21, function 4F, to locate the next file matching the specifications provided by the initial call to "Find first". If this function succeeds, then the DTA is filled in with details of the next matching file, and the function returns with the carry flag clear. Otherwise a return is made with the carry flag set. Most C compilers for the IBM PC provide non standard library functions for accessing these two functions. Turbo C provides "findfirst()" and "findnext()". Making use of the supplied library functions shields the programmer from the messy task of worrying about the DTA. Microsoft C programmers should substitue findfirst() with _dos_findfirst() and findnext() with _dos_findnext(). The following Turbo C example imitates the DOS directory command, in a basic form; #include #include #include void main(void) { /* Display directory listing of current directory */ int done; int day; int month; int year; int hour; int min; char amflag; struct ffblk ffblk; struct fcb fcb; /* First display sub directory entries */ done = findfirst("*.",&ffblk,16); while (!done) { year = (ffblk.ff_fdate >> 9) + 80; month = (ffblk.ff_fdate >> 5) & 0x0f; day = ffblk.ff_fdate & 0x1f; hour = (ffblk.ff_ftime >> 11); min = (ffblk.ff_ftime >> 5) & 63; amflag = 'a'; if (hour > 12) { hour -= 12; amflag = 'p'; } printf("%-11.11s %02d-%02d-%02d %2d:%02d%c\n", ffblk.ff_name,day,month,year,hour,min,amflag); done = findnext(&ffblk); } /* Now all files except directories */ done = findfirst("*.*",&ffblk,231); while (!done) { year = (ffblk.ff_fdate >> 9) + 80; month = (ffblk.ff_fdate >> 5) & 0x0f; day = ffblk.ff_fdate & 0x1f; hour = (ffblk.ff_ftime >> 11); min = (ffblk.ff_ftime >> 5) & 63; amflag = 'a'; if (hour > 12) { hour -= 12; amflag = 'p'; } parsfnm(ffblk.ff_name,&fcb,1); printf("%-8.8s %-3.3s %8ld %02d-%02d-%02d %2d:%02d%c\n", fcb.fcb_name,fcb.fcb_ext,ffblk.ff_fsize, day,month,year,hour,min,amflag); done = findnext(&ffblk); } } The function "parsfnm()" is a Turbo C library command which makes use of the DOS function for parsing an ascii string containing a file name, into its component parts. These component parts are then put into a DOS file control block (fcb), from where they may be easily retrieved for displaying by printf(). The DOS DTA is comprised as follows; Offset Length Contents 00 15 Reserved 15 Byte Attribute of matched file 16 Word File time 18 Word File date 1A 04 File size 1E 0D File name and extension as ascii string The file time word contains the time at which the file was last written to disc and is comprised as follows; Bits Contents 0 - 4 Seconds divided by 2 5 - 10 Minutes 11 - 15 Hours The file date word holds the date on which the file was last written to disc and is comprised of; Bits Contents 0 - 4 Day 5 - 8 Month 9 - 15 Years since 1980 To extract these details from the DTA requires a little manipulation, as illustrated in the above example. The DTA attribute flag is comprised of the following bits being set or not; Bit Attribute 0 Read only 1 Hidden 2 System 3 Volume label 4 Directory 5 Archive ACCESSING EXPANDED MEMORY Memory (RAM) in an IBM PC comes in three flavours; Conventional, Expanded and Extended. Conventional memory is the 640K of RAM which the operating system DOS can access. This memory is normally used. However, it is often insufficient for todays RAM hungry systems. Expanded memory is RAM which is addressed outside of the area of conventional RAM not by DOS but by a second program called a LIM EMS driver. Access to this device driver is made through interrupt 67h. The main problem with accessing expanded memory is that no matter how much expanded memory is fitted to the computer, it can only be accessed through 16K blocks refered to as pages. So for example. If you have 2mB of expanded RAM fitted to your PC then that is comprised of 128 pages (128 * 16K = 2mB). A program can determine whether a LIM EMS driver is installed by attempting to open the file `EMMXXXX0' which is guarranteed by the LIM standard to be present as an IOCTL device when the device driver is active. The following source code illustrates some basic functions for testing for and accessing expanded memory. /* Various functions for using Expanded memory */ #include #define EMM 0x67 char far *emmbase; emmtest() { /* Tests for the presence of expnaded memory by attempting to open the file EMMXXXX0. */ union REGS regs; struct SREGS sregs; int error; long handle; /* Attempt to open the file device EMMXXXX0 */ regs.x.ax = 0x3d00; regs.x.dx = (int)"EMMXXXX0"; sregs.ds = _DS; intdosx(®s,®s,&sregs); handle = regs.x.ax; error = regs.x.cflag; if (!error) { regs.h.ah = 0x3e; regs.x.bx = handle; intdos(®s,®s); } return error; } emmok() { /* Checks whether the expanded memory manager responds correctly */ union REGS regs; regs.h.ah = 0x40; int86(EMM,®s,®s); if (regs.h.ah) return 0; regs.h.ah = 0x41; int86(EMM,®s,®s); if (regs.h.ah) return 0; emmbase = MK_FP(regs.x.bx,0); return 1; } long emmavail() { /* Returns the number of available (free) 16K pages of expanded memory or -1 if an error occurs. */ union REGS regs; regs.h.ah = 0x42; int86(EMM,®s,®s); if (!regs.h.ah) return regs.x.bx; return -1; } long emmalloc(int n) { /* Requests 'n' pages of expanded memory and returns the file handle assigned to the pages or -1 if there is an error */ union REGS regs; regs.h.ah = 0x43; regs.x.bx = n; int86(EMM,®s,®s); if (regs.h.ah) return -1; return regs.x.dx; } emmmap(long handle, int phys, int page) { /* Maps a physical page from expanded memory into the page frame in the conventional memory 16K window so that data can be transfered between the expanded memory and conventional memory. */ union REGS regs; regs.h.ah = 0x44; regs.h.al = page; regs.x.bx = phys; regs.x.dx = handle; int86(EMM,®s,®s); return (regs.h.ah == 0); } void emmmove(int page, char *str, int n) { /* Move 'n' bytes from conventional memory to the specified expanded memory page */ char far *ptr; ptr = emmbase + page * 16384; while(n-- > 0) *ptr++ = *str++; } void emmget(int page, char *str, int n) { /* Move 'n' bytes from the specified expanded memory page into conventional memory */ char far *ptr; ptr = emmbase + page * 16384; while(n-- > 0) *str++ = *ptr++; } emmclose(long handle) { /* Release control of the expanded memory pages allocated to 'handle' */ union REGS regs; regs.h.ah = 0x45; regs.x.dx = handle; int86(EMM,®s,®s); return (regs.h.ah == 0); } /* Test function for the EMM routines */ void main() { long emmhandle; long avail; char teststr[80]; int i; if(!emmtest()) { printf("Expanded memory is not present\n"); exit(0); } if(!emmok()) { printf("Expanded memory manager is not present\n"); exit(0); } avail = emmavail(); if (avail == -1) { printf("Expanded memory manager error\n"); exit(0); } printf("There are %ld pages available\n",avail); /* Request 10 pages of expanded memory */ if((emmhandle = emmalloc(10)) < 0) { printf("Insufficient pages available\n"); exit(0); } for (i = 0; i < 10; i++) { sprintf(teststr,"%02d This is a test string\n",i); emmmap(emmhandle,i,0); emmmove(0,teststr,strlen(teststr) + 1); } for (i = 0; i < 10; i++) { emmmap(emmhandle,i,0); emmget(0,teststr,strlen(teststr) + 1); printf("READING BLOCK %d: %s\n",i,teststr); } emmclose(emmhandle); } ACCESSING EXTENDED MEMORY Extended memory has all but taken over from Expanded Memory now (1996). It is faster and more useable than expanded memory. As with Expanded memory, Extended memory cannot be directly accessed through the standard DOS mode, and so a transfer buffer in conventional or "real-mode" memory needs to be used. The process to write data to Extended memory then involves copying the data to the transfer buffer in conventional memory, and from there copying it to Extended memory. Before any use may be made of Extended memory, a program should test to see if Extended memory is available. The following function, XMS_init(), tests for the presence of Extended memory, and if available calls another function, GetXMSEntry() to initialise the program for using Extended Memory. The function also allocates a conventional memory transfer buffer. /* BLOCKSIZE will be the size of our real-memory buffer that we'll swap XMS through (must be a multiple of 1024, since XMS is allocated in 1K chunks.) */ #ifdef __SMALL__ #define BLOCKSIZE (16L * 1024L) #endif #ifdef __MEDIUM__ #define BLOCKSIZE (16L * 1024L) #endif #ifdef __COMPACT__ #define BLOCKSIZE (64L * 1024L) #endif #ifdef __LARGE__ #define BLOCKSIZE (64L * 1024L) #endif char XMS_init() { /* returns 0 if XMS present, 1 if XMS absent 2 if unable to allocate conventional memory transfer buffer */ unsigned char status; _AX=0x4300; geninterrupt(0x2F); status = _AL; if(status==0x80) { GetXMSEntry(); XMSBuf = (char far *) farmalloc(BLOCKSIZE); if (XMSBuf == NULL) return 2; return 0; } return 1; } void GetXMSEntry(void) { /* GetXMSEntry sets XMSFunc to the XMS Manager entry point so we can call it later */ _AX=0x4310; geninterrupt(0x2F); XMSFunc= (void (far *)(void)) MK_FP(_ES,_BX); } Once the presence of Extended memory has been confirmed, a program can find out how much Extended memory is available; void XMSSize(int *kbAvail, int *largestAvail) { /* XMSSize returns the total kilobytes available, and the size in kilobytes of the largest available block */ _AH=8; (*XMSFunc)(); *largestAvail=_DX; *kbAvail=_AX; } The following function may be called to allocate a block of Extended memory, like you would allocate a block of conventional memory. char AllocXMS(unsigned long numberBytes) { /* Allocate a block of XMS memory numberBytes long Returns 1 on success 0 on failure */ _DX = (int)(numberBytes / 1024); _AH = 9; (*XMSFunc)(); if (_AX==0) { return 0; } XMSHandle=_DX; return 1; } Allocated Extended memory is not freed by DOS. A program using Extended memory must release it before terminating. This function frees a block of extended memory previously allocated by AllocXMS. Note, XMSHandle is a global variable of type int. void XMS_free(void) { /* Free used XMS */ _DX=XMSHandle; _AH=0x0A; (*XMSFunc)(); } Two functions are now given. One for writing data to Extended memory, and one for reading data from Extended memory into conventional memory. /* XMSParms is a structure for copying information to and from real-mode memory to XMS memory */ struct parmstruct { /* blocklength is the size in bytes of block to copy */ unsigned long blockLength; /* sourceHandle is the XMS handle of source; 0 means that sourcePtr will be a 16:16 real-mode pointer, otherwise sourcePtr is a 32-bit offset from the beginning of the XMS area that sourceHandle points to */ unsigned int sourceHandle; far void *sourcePtr; /* destHandle is the XMS handle of destination; 0 means that destPtr will be a 16:16 real-mode pointer, otherwise destPtr is a 32-bit offset from the beginning of the XMS area that destHandle points to */ unsigned int destHandle; far void *destPtr; } XMSParms; char XMS_write(unsigned long loc, char far *val, unsigned length) { /* Round length up to next even value */ length += length % 2; XMSParms.sourceHandle=0; XMSParms.sourcePtr=val; XMSParms.destHandle=XMSHandle; XMSParms.destPtr=(void far *) (loc); XMSParms.blockLength=length; /* Must be an even number! */ _SI = FP_OFF(&XMSParms); _AH=0x0B; (*XMSFunc)(); if (_AX==0) { return 0; } return 1; } void *XMS_read(unsigned long loc,unsigned length) { /* Returns pointer to data or NULL on error */ /* Round length up to next even value */ length += length % 2; XMSParms.sourceHandle=XMSHandle; XMSParms.sourcePtr=(void far *) (loc); XMSParms.destHandle=0; XMSParms.destPtr=XMSBuf; XMSParms.blockLength=length; /* Must be an even number */ _SI=FP_OFF(&XMSParms); _AH=0x0B; (*XMSFunc)(); if (_AX==0) { return NULL; } return XMSBuf; } And now putting it all together is a demonstration program. /* A sequential table of variable length records in XMS */ #include #include #include #include #include #define TRUE 1 #define FALSE 0 /* BLOCKSIZE will be the size of our real-memory buffer that we'll swap XMS through (must be a multiple of 1024, since XMS is allocated in 1K chunks.) */ #ifdef __SMALL__ #define BLOCKSIZE (16L * 1024L) #endif #ifdef __MEDIUM__ #define BLOCKSIZE (16L * 1024L) #endif #ifdef __COMPACT__ #define BLOCKSIZE (64L * 1024L) #endif #ifdef __LARGE__ #define BLOCKSIZE (64L * 1024L) #endif /* XMSParms is a structure for copying information to and from real-mode memory to XMS memory */ struct parmstruct { /* blocklength is the size in bytes of block to copy */ unsigned long blockLength; /* sourceHandle is the XMS handle of source; 0 means that sourcePtr will be a 16:16 real-mode pointer, otherwise sourcePtr is a 32-bit offset from the beginning of the XMS area that sourceHandle points to */ unsigned int sourceHandle; far void *sourcePtr; /* destHandle is the XMS handle of destination; 0 means that destPtr will be a 16:16 real-mode pointer, otherwise destPtr is a 32-bit offset from the beginning of the XMS area that destHandle points to */ unsigned int destHandle; far void *destPtr; } XMSParms; void far (*XMSFunc) (void); /* Used to call XMS manager (himem.sys) */ char GetBuf(void); void GetXMSEntry(void); char *XMSBuf; /* Conventional memory buffer for transfers */ unsigned int XMSHandle; /* handle to allocated XMS block */ char XMS_init() { /* returns 0 if XMS present, 1 if XMS absent 2 if unable to allocate transfer buffer */ unsigned char status; _AX=0x4300; geninterrupt(0x2F); status = _AL; if(status==0x80) { GetXMSEntry(); XMSBuf = (char far *) farmalloc(BLOCKSIZE); if (XMSBuf == NULL) return 2; return 0; } return 1; } void GetXMSEntry(void) { /* GetXMSEntry sets XMSFunc to the XMS Manager entry point so we can call it later */ _AX=0x4310; geninterrupt(0x2F); XMSFunc= (void (far *)(void)) MK_FP(_ES,_BX); } void XMSSize(int *kbAvail, int *largestAvail) { /* XMSSize returns the total kilobytes available, and the size in kilobytes of the largest available block */ _AH=8; (*XMSFunc)(); *largestAvail=_DX; *kbAvail=_AX; } char AllocXMS(unsigned long numberBytes) { /* Allocate a block of XMS memory numberBytes long */ _DX = (int)(numberBytes / 1024); _AH = 9; (*XMSFunc)(); if (_AX==0) { return FALSE; } XMSHandle=_DX; return TRUE; } void XMS_free(void) { /* Free used XMS */ _DX=XMSHandle; _AH=0x0A; (*XMSFunc)(); } char XMS_write(unsigned long loc, char far *val, unsigned length) { /* Round length up to next even value */ length += length % 2; XMSParms.sourceHandle=0; XMSParms.sourcePtr=val; XMSParms.destHandle=XMSHandle; XMSParms.destPtr=(void far *) (loc); XMSParms.blockLength=length; /* Must be an even number! */ _SI = FP_OFF(&XMSParms); _AH=0x0B; (*XMSFunc)(); if (_AX==0) { return FALSE; } return TRUE; } void *XMS_read(unsigned long loc,unsigned length) { /* Returns pointer to data or NULL on error */ /* Round length up to next even value */ length += length % 2; XMSParms.sourceHandle=XMSHandle; XMSParms.sourcePtr=(void far *) (loc); XMSParms.destHandle=0; XMSParms.destPtr=XMSBuf; XMSParms.blockLength=length; /* Must be an even number */ _SI=FP_OFF(&XMSParms); _AH=0x0B; (*XMSFunc)(); if (_AX==0) { return NULL; } return XMSBuf; } /* Demonstration code Read various length strings into a single XMS block (EMB) and write them out again */ int main() { int kbAvail,largestAvail; char buffer[80]; char *p; long pos; long end; if (XMS_init() == 0) printf("XMS Available ...\n"); else { printf("XMS Not Available\n"); return(1); } XMSSize(&kbAvail,&largestAvail); printf("Kilobytes Available: %d; Largest block: %dK\n",kbAvail,largestAvail); if (!AllocXMS(2000 * 1024L)) return(1); pos = 0; do { p = fgets(buffer,1000,stdin); if (p != NULL) { XMS_write(pos,buffer,strlen(buffer) + 1); pos += strlen(buffer) + 1; } } while(p != NULL); end = pos; pos = 0; do { memcpy(buffer,XMS_read(pos,100),70); printf("%s",buffer); pos += strlen(buffer) + 1; } while(pos < end); /* It is VERY important to free any XMS before exiting! */ XMS_free(); return 0; } TSR PROGRAMMING Programs which remain running and resident in memory while other programs are running are the most exciting line of programming for many PC developers. This type of program is known as a "Terminate and Stay Resident" or "TSR" program and they are very difficult to program sucessfuly. The difficulties in programming TSRs comes from the limitations of DOS which is not a multi-tasking operating system, and does not react well to re-enterant code. That is it's own functions (interrupts) calling themselves. In theory a TSR is quite simple. It is an ordinary program which terminates not through the usual DOS terminate function, but through the DOS "keep" function - interrupt 27h. This function reserves an area of memory, used by the program so that no other programs will overwrite it. This in itself is not a very difficult task, excepting that the program needs to tell DOS how much memory to leave it! The problems stem mainly from not being able to use DOS function calls within the TSR program once it has "gone resident". There are a few basic rules which help to clarify the problems encountered in programming TSRs: 1.Avoid DOS function calls 2.Monitor the DOS busy flag, when this flag is nonzero, DOS is executing an interrupt 21h function and MUST NOT be disturbed! 3.Monitor interrupt 28h. This reveals when DOS is busy waiting for console input. At this time you can disturb DOS regardless of the DOS busy flag setting. 4.Provide some way of checking whether the TSR is already loaded to prevent multiple copies occuring in memory. 5.Remember that other TSR programs may be chained to interrupts, and so you must chain any interrupt vectors that your program needs. 6.Your TSR program must use its own stack, and NOT that of the running process. 7.TSR programs must be compiled in a small memory model with stack checking turned off. 8.When control passes to your TSR program, it must tell DOS that the active process has changed. The following three source code modules describe a complete TSR program. This is a useful pop-up address book database which can be activated while any other program is running by pressing the key combination `Alt' and `.'. If the address book does not respond to the key press, it is probably because DOS cannot be disturbed, and you should try to pop-it-up again. /* A practical TSR program (a pop-up address book database) Compile in small memory model with stack checking OFF */ #include #include #include #include static union REGS rg; /* Size of the program to remain resident experimentation is required to make this as small as possible */ unsigned sizeprogram = 28000/16; /* Activate with Alt . */ unsigned scancode = 52; /* . */ unsigned keymask = 8; /* ALT */ char signature[]= "POPADDR"; char fpath[40]; /* Function prototypes */ void curr_cursor(int *x, int *y); int resident(char *, void interrupt(*)()); void resinit(void); void terminate(void); void restart(void); void wait(void); void resident_psp(void); void exec(void); /* Entry point from DOS */ main(int argc, char *argv[]) { void interrupt ifunc(); int ivec; /* For simplicity, assume the data file is in the root directory of drive C: */ strcpy(fpath,"C:\\ADDRESS.DAT"); if ((ivec = resident(signature,ifunc)) != 0) { /* TSR is resident */ if (argc > 1) { rg.x.ax = 0; if (strcmp(argv[1],"quit") == 0) rg.x.ax = 1; else if (strcmp(argv[1],"restart") == 0) rg.x.ax = 2; else if (strcmp(argv[1],"wait") == 0) rg.x.ax = 3; if (rg.x.ax) { int86(ivec,&rg,&rg); return; } } printf("\nPopup Address Book is already resident"); } else { /* Initial load of TSR program */ printf("Popup Address Book Resident.\nPress Alt . To Activate....\n"); resinit(); } } void interrupt ifunc(bp,di,si,ds,es,dx,cx,bx,ax) { if(ax == 1) terminate(); else if(ax == 2) restart(); else if(ax == 3) wait(); } popup() { int x,y; curr_cursor(&x,&y); /* Call the TSR C program here */ exec(); cursor(x,y); } /* Second source module */ #include #include static union REGS rg; static struct SREGS seg; static unsigned mcbseg; static unsigned dosseg; static unsigned dosbusy; static unsigned enddos; char far *intdta; static unsigned intsp; static unsigned intss; static char far *mydta; static unsigned myss; static unsigned stack; static unsigned ctrl_break; static unsigned mypsp; static unsigned intpsp; static unsigned pids[2]; static int pidctr = 0; static int pp; static void interrupt (*oldtimer)(); static void interrupt (*old28)(); static void interrupt (*oldkb)(); static void interrupt (*olddisk)(); static void interrupt (*oldcrit)(); void interrupt newtimer(); void interrupt new28(); void interrupt newkb(); void interrupt newdisk(); void interrupt newcrit(); extern unsigned sizeprogram; extern unsigned scancode; extern unsigned keymask; static int resoff = 0; static int running = 0; static int popflg = 0; static int diskflag = 0; static int kbval; static int cflag; void dores(void); void pidaddr(void); void resinit() { segread(&seg); myss = seg.ss; rg.h.ah = 0x34; intdos(&rg,&rg); dosseg = _ES; dosbusy = rg.x.bx; mydta = getdta(); pidaddr(); oldtimer = getvect(0x1c); old28 = getvect(0x28); oldkb = getvect(9); olddisk = getvect(0x13); setvect(0x1c,newtimer); setvect(9,newkb); setvect(0x28,new28); setvect(0x13,newdisk); stack = (sizeprogram - (seg.ds - seg.cs)) * 16 - 300; rg.x.ax = 0x3100; rg.x.dx = sizeprogram; intdos(&rg,&rg); } void interrupt newdisk(bp,di,si,ds,es,dx,cx,bx,ax,ip,cs,flgs) { diskflag++; (*olddisk)(); ax = _AX; newcrit(); flgs = cflag; --diskflag; } void interrupt newcrit(bp,di,si,ds,es,dx,cx,bx,ax,ip,cs,flgs) { ax = 0; cflag = flgs; } void interrupt newkb() { if (inportb(0x60) == scancode) { kbval = peekb(0,0x417); if (!resoff && ((kbval & keymask) ^ keymask) == 0) { kbval = inportb(0x61); outportb(0x61,kbval | 0x80); outportb(0x61,kbval); disable(); outportb(0x20,0x20); enable(); if (!running) popflg = 1; return; } } (*oldkb)(); } void interrupt newtimer() { (*oldtimer)(); if (popflg && peekb(dosseg,dosbusy) == 0) if(diskflag == 0) { outportb(0x20,0x20); popflg = 0; dores(); } } void interrupt new28() { (*old28)(); if (popflg && peekb(dosseg,dosbusy) != 0) { popflg = 0; dores(); } } resident_psp() { intpsp = peek(dosseg,*pids); for(pp = 0; pp < pidctr; pp++) poke(dosseg,pids[pp],mypsp); } interrupted_psp() { for(pp = 0; pp < pidctr; pp++) poke(dosseg,pids[pp],intpsp); } void dores() { running = 1; disable(); intsp = _SP; intss = _SS; _SP = stack; _SS = myss; enable(); oldcrit = getvect(0x24); setvect(0x24,newcrit); rg.x.ax = 0x3300; intdos(&rg,&rg); ctrl_break = rg.h.dl; rg.x.ax = 0x3301; rg.h.dl = 0; intdos(&rg,&rg); intdta = getdta(); setdta(mydta); resident_psp(); popup(); interrupted_psp(); setdta(intdta); setvect(0x24,oldcrit); rg.x.ax = 0x3301; rg.h.dl = ctrl_break; intdos(&rg,&rg); disable(); _SP = intsp; _SS = intss; enable(); running = 0; } static int avec = 0; unsigned resident(char *signature,void interrupt(*ifunc)()) { char *sg; unsigned df; int vec; segread(&seg); df = seg.ds-seg.cs; for(vec = 0x60; vec < 0x68; vec++) { if (getvect(vec) == NULL) { if (!avec) avec = vec; continue; } for(sg = signature; *sg; sg++) if (*sg != peekb(peek(0,2+vec*4)+df,(unsigned)sg)) break; if (!*sg) return vec; } if (avec) setvect(avec,ifunc); return 0; } static void pidaddr() { unsigned adr = 0; rg.h.ah = 0x51; intdos(&rg,&rg); mypsp = rg.x.bx; rg.h.ah = 0x52; intdos(&rg,&rg); enddos = _ES; enddos = peek(enddos,rg.x.bx-2); while(pidctr < 2 && (unsigned)((dosseg<<4) + adr) < (enddos <<4)) { if (peek(dosseg,adr) == mypsp) { rg.h.ah = 0x50; rg.x.bx = mypsp + 1; intdos(&rg,&rg); if (peek(dosseg,adr) == mypsp + 1) pids[pidctr++] = adr; rg.h.ah = 0x50; rg.x.bx = mypsp; intdos(&rg,&rg); } adr++; } } static resterm() { setvect(0x1c,oldtimer); setvect(9,oldkb); setvect(0x28,old28); setvect(0x13,olddisk); setvect(avec,(void interrupt (*)()) 0); rg.h.ah = 0x52; intdos(&rg,&rg); mcbseg = _ES; mcbseg = peek(mcbseg,rg.x.bx-2); segread(&seg); while(peekb(mcbseg,0) == 0x4d) { if(peek(mcbseg,1) == mypsp) { rg.h.ah = 0x49; seg.es = mcbseg+1; intdosx(&rg,&rg,&seg); } mcbseg += peek(mcbseg,3) + 1; } } terminate() { if (getvect(0x13) == (void interrupt (*)()) newdisk) if (getvect(9) == newkb) if(getvect(0x28) == new28) if(getvect(0x1c) == newtimer) { resterm(); return; } resoff = 1; } restart() { resoff = 0; } wait() { resoff = 1; } void cursor(int y, int x) { rg.x.ax = 0x0200; rg.x.bx = 0; rg.x.dx = ((y << 8) & 0xff00) + x; int86(16,&rg,&rg); } void curr_cursor(int *y, int *x) { rg.x.ax = 0x0300; rg.x.bx = 0; int86(16,&rg,&rg); *x = rg.h.dl; *y = rg.h.dh; } /* Third module, the simple pop-up address book with mouse support */ #include #include #include #include #include #include #include #include #include #include /* left cannot be less than 3 */ #define left 4 /* Data structure for records */ typedef struct { char name[31]; char company[31]; char address[31]; char area[31]; char town[31]; char county[31]; char post[13]; char telephone[16]; char fax[16]; } data; extern char fpath[]; static char scr[4000]; static char sbuff[2000]; char stext[30]; data rec; int handle; int recsize; union REGS inreg,outreg; /* Function prototypes */ void FATAL(char *); void OPENDATA(void); void CONTINUE(void); void EXPORT_MULTI(void); void GETDATA(int); int GETOPT(void); void DISPDATA(void); void ADD_REC(void); void PRINT_MULTI(void); void SEARCH(void); void MENU(void); int GET_MOUSE(int *buttons) { inreg.x.ax = 0; int86(0x33,&inreg,&outreg); *buttons = outreg.x.bx; return outreg.x.ax; } void MOUSE_CURSOR(int status) { /* Status = 0 cursor off */ /* 1 cursor on */ inreg.x.ax = 2 - status; int86(0x33,&inreg,&outreg); } int MOUSE_LOCATION(int *x, int *y) { inreg.x.ax = 3; int86(0x33,&inreg,&outreg); *x = outreg.x.cx / 8; *y = outreg.x.dx / 8; return outreg.x.bx; } int GETOPT() { int result; int x; int y; do { do { result = MOUSE_LOCATION(&x,&y); if (result & 1) { if (x >= 52 && x <= 53 && y >= 7 && y <= 15) return y - 7; if (x >= 4 && x <= 40 && y >= 7 && y <= 14) return y + 10; if (x >= 4 && x <= 40 && y == 15) return y + 10; } } while(!bioskey(1)); result = bioskey(0); x = result & 0xff; if (x == 0) { result = result >> 8; result -= 60; } } while(result < 0 || result > 8); return result; } void setvideo(unsigned char mode) { /* Sets the video display mode and clears the screen */ inreg.h.al = mode; inreg.h.ah = 0x00; int86(0x10, &inreg, &outreg); } int activepage(void) { /* Returns the currently selected video display page */ union REGS inreg,outreg; inreg.h.ah = 0x0F; int86(0x10, &inreg, &outreg); return(outreg.h.bh); } void print(char *str) { /* Prints characters only directly to the current display page starting at the current cursor position. The cursor is not advanced. This function assumes a colour display card. For use with a monochrome display card change 0xB800 to read 0xB000 */ int page; int offset; unsigned row; unsigned col; char far *ptr; page = activepage(); curr_cursor(&row,&col); offset = page * 4000 + row * 160 + col * 2; ptr = MK_FP(0xB800,offset); while(*str) { *ptr++= *str++; ptr++; } } void TRUESHADE(int lef, int top, int right, int bottom) { int n; /* True Shading of a screen block */ gettext(lef,top,right,bottom,sbuff); for(n = 1; n < 2000; n+= 2) sbuff[n] = 7; puttext(lef,top,right,bottom,sbuff); } void DBOX(int l, int t, int r, int b) { /* Draws a double line box around the described area */ int n; cursor(t,l); print("�"); for(n = 1; n < r - l; n++) { cursor(t,l + n); print("I"); } cursor(t,r); print("�"); for (n = t + 1; n < b; n++) { cursor(n,l); print("�"); cursor(n,r); print("�"); } cursor(b,l); print("E"); for(n = 1; n < r - l; n++) { cursor(b,l+n); print("I"); } cursor(b,r); print("�"); } int INPUT(char *text,unsigned length) { /* Receive a string from the operator */ unsigned key_pos; int key; unsigned start_row; unsigned start_col; unsigned end; char temp[80]; char *p; curr_cursor(&start_row,&start_col); key_pos = 0; end = strlen(text); for(;;) { key = bioskey(0); if ((key & 0xFF) == 0) { key = key >> 8; if (key == 79) { while(key_pos < end) key_pos++; cursor(start_row,start_col + key_pos); } else if (key == 71) { key_pos = 0; cursor(start_row,start_col); } else if ((key == 75) && (key_pos > 0)) { key_pos--; cursor(start_row,start_col + key_pos); } else if ((key == 77) && (key_pos < end)) { key_pos++; cursor(start_row,start_col + key_pos); } else if (key == 83) { p = text + key_pos; while(*(p+1)) { *p = *(p+1); p++; } *p = 32; if (end > 0) end--; cursor(start_row,start_col); cprintf(text); cprintf(" "); if ((key_pos > 0) && (key_pos == end)) key_pos--; cursor(start_row,start_col + key_pos); } } else { key = key & 0xFF; if (key == 13 || key == 27) break; else if ((key == 8) && (key_pos > 0)) { end--; key_pos--; text[key_pos--] = '\0'; strcpy(temp,text); p = text + key_pos + 2; strcat(temp,p); strcpy(text,temp); cursor(start_row,start_col); cprintf("%-*.*s",length,length,text); key_pos++; cursor(start_row,start_col + key_pos); } else if ((key > 31) && (key_pos < length) && (start_col + key_pos < 80)) { if (key_pos <= end) { p = text + key_pos; memmove(p+1,p,end - key_pos); if (end < length) end++; text[end] = '\0'; } text[key_pos++] = (char)key; if (key_pos > end) { end++; text[end] = '\0'; } cursor(start_row,start_col); cprintf("%-*.*s",length,length,text); cursor(start_row,start_col + key_pos); } } } text[end] = '\0'; return key; } void FATAL(char *error) { /* A fatal error has occured */ printf("\nFATAL ERROR: %s",error); exit(0); } void OPENDATA() { /* Check for existence of data file and if not create it */ /* otherwise open it for reading/writing at end of file */ handle = open(fpath,O_RDWR,S_IWRITE); if (handle == -1) { handle = open(fpath,O_RDWR|O_CREAT,S_IWRITE); if (handle == -1) FATAL("Unable to create data file"); } /* Read in first rec */ read(handle,&rec,recsize); } void CLOSEDATA() { close(handle); } void GETDATA(int start) { /* Get address data from operator */ textcolor(BLACK); textbackground(GREEN); gotoxy(left,8); print("Name "); gotoxy(left,9); print("Company "); gotoxy(left,10); print("Address "); gotoxy(left,11); print("Area "); gotoxy(left,12); print("Town "); gotoxy(left,13); print("County "); gotoxy(left,14); print("Post Code "); gotoxy(left,15); print("Telephone "); gotoxy(left,16); print("Fax "); switch(start) { case 0: gotoxy(left + 10,8); if(INPUT(rec.name,30) == 27) break; case 1: gotoxy(left + 10,9); if(INPUT(rec.company,30) == 27) break; case 2: gotoxy(left + 10,10); if(INPUT(rec.address,30) == 27) break; case 3: gotoxy(left + 10,11); if(INPUT(rec.area,30) == 27) break; case 4: gotoxy(left + 10,12); if(INPUT(rec.town,30) == 27) break; case 5: gotoxy(left + 10,13); if(INPUT(rec.county,30) == 27) break; case 6: gotoxy(left + 10,14); if(INPUT(rec.post,12) == 27) break; case 7: gotoxy(left + 10,15); if(INPUT(rec.telephone,15) == 27) break; case 8: gotoxy(left + 10,16); INPUT(rec.fax,15); break; } textcolor(WHITE); textbackground(RED); gotoxy(left + 23,21); print(" "); } void DISPDATA() { /* Display address data */ textcolor(BLACK); textbackground(GREEN); cursor(7,3); cprintf("Name %-30.30s",rec.name); cursor(8,3); cprintf("Company %-30.30s",rec.company); cursor(9,3); cprintf("Address %-30.30s",rec.address); cursor(10,3); cprintf("Area %-30.30s",rec.area); cursor(11,3); cprintf("Town %-30.30s",rec.town); cursor(12,3); cprintf("County %-30.30s",rec.county); cursor(13,3); cprintf("Post Code %-30.30s",rec.post); cursor(14,3); cprintf("Telephone %-30.30s",rec.telephone); cursor(15,3); cprintf("Fax %-30.30s",rec.fax); } int LOCATE(char *text) { int result; do { /* Read rec into memory */ result = read(handle,&rec,recsize); if (result > 0) { /* Scan rec for matching data */ if (strstr(strupr(rec.name),text) != NULL) return(1); if (strstr(strupr(rec.company),text) != NULL) return(1); if (strstr(strupr(rec.address),text) != NULL) return(1); if (strstr(strupr(rec.area),text) != NULL) return(1); if (strstr(strupr(rec.town),text) != NULL) return(1); if (strstr(strupr(rec.county),text) != NULL) return(1); if (strstr(strupr(rec.post),text) != NULL) return(1); if (strstr(strupr(rec.telephone),text) != NULL) return(1); if (strstr(strupr(rec.fax),text) != NULL) return(1); } } while(result > 0); return(0); } void SEARCH() { int result; gotoxy(left,21); textcolor(WHITE); textbackground(RED); cprintf("Enter data to search for "); strcpy(stext,""); INPUT(stext,30); if (*stext == 0) { gotoxy(left,21); cprintf("%70c",32); return; } gotoxy(left,21); textcolor(WHITE); textbackground(RED); cprintf("Searching for %s Please Wait....",stext); strupr(stext); /* Locate start of file */ lseek(handle,0,SEEK_SET); result = LOCATE(stext); if (result == 0) { gotoxy(left,21); cprintf("%70c",32); gotoxy(left + 27,21); cprintf("NO MATCHING RECORDS"); gotoxy(left + 24,22); cprintf("Press RETURN to Continue"); bioskey(0); gotoxy(left,21); cprintf("%70c",32); gotoxy(left,22); cprintf("%70c",32); } else { lseek(handle,0 - recsize,SEEK_CUR); read(handle,&rec,recsize); DISPDATA(); } textcolor(WHITE); textbackground(RED); gotoxy(left,21); cprintf("%70c",32); textcolor(BLACK); textbackground(GREEN); } void CONTINUE() { int result; long curpos; curpos = tell(handle) - recsize; result = LOCATE(stext); textcolor(WHITE); textbackground(RED); if (result == 0) { gotoxy(left + 24,21); cprintf("NO MORE MATCHING RECORDS"); gotoxy(left + 24,22); cprintf("Press RETURN to Continue"); bioskey(0); gotoxy(left,21); cprintf("%70c",32); gotoxy(left,22); cprintf("%70c",32); lseek(handle,curpos,SEEK_SET); read(handle,&rec,recsize); DISPDATA(); } else { lseek(handle,0 - recsize,SEEK_CUR); read(handle,&rec,recsize); DISPDATA(); } textcolor(WHITE); textbackground(RED); gotoxy(left,21); cprintf("%70c",32); gotoxy(left,22); cprintf(" "); textcolor(BLACK); textbackground(GREEN); } void PRINT_MULTI() { data buffer; char destination[60]; char text[5]; int result; int ok; int ok2; int blanks; int total_lines; char *p; FILE *fp; textcolor(WHITE); textbackground(RED); gotoxy(left + 23,21); cprintf("Enter selection criteria"); /* Clear existing rec details */ memset(&rec,0,recsize); DISPDATA(); GETDATA(0); textcolor(WHITE); textbackground(RED); gotoxy(left,21); cprintf("Enter report destination PRN"); strcpy(destination,"PRN"); gotoxy(left,22); cprintf("Enter Address length in lines 18"); strcpy(text,"18"); gotoxy(left + 25,21); INPUT(destination,40); gotoxy(left +30,22); INPUT(text,2); gotoxy(left,21); cprintf("%72c",32); gotoxy(left,22); cprintf("%72c",32); total_lines = atoi(text) - 6; if (total_lines < 0) total_lines = 0; fp = fopen(destination,"w+"); if (fp == NULL) { gotoxy(left,21); cprintf("Unable to print to %s",destination); gotoxy(left,22); cprintf("Press RETURN to Continue"); bioskey(0); gotoxy(left,21); cprintf("%78c",32); gotoxy(left,22); cprintf(" "); } /* Locate start of file */ lseek(handle,0,SEEK_SET); do { /* Read rec into memory */ result = read(handle,&buffer,recsize); if (result > 0) { ok = 1; /* Scan rec for matching data */ if (*rec.name) if (stricmp(buffer.name,rec.name)) ok = 0; if (*rec.company) if (stricmp(buffer.company,rec.company)) ok = 0; if (*rec.address) if (stricmp(buffer.address,rec.address)) ok = 0; if (*rec.area) if (stricmp(buffer.area,rec.area)) ok = 0; if (*rec.town) if (stricmp(buffer.town,rec.town)) ok = 0; if (*rec.county) if (stricmp(buffer.county,rec.county)) ok = 0; if (*rec.post) if (stricmp(buffer.post,rec.post)) ok = 0; if (*rec.telephone) if (stricmp(buffer.telephone,rec.telephone)) ok = 0; if (*rec.fax) if (stricmp(buffer.fax,rec.fax)) ok = 0; if (ok) { blanks = total_lines; p = buffer.name; ok2 = 0; while(*p) { if (*p != 32) { ok2 = 1; break; } p++; } if (!ok2) blanks++; else fprintf(fp,"%s\n",buffer.name); p = buffer.company; ok2 = 0; while(*p) { if (*p != 32) { ok2 = 1; break; } p++; } if (!ok2) blanks++; else fprintf(fp,"%s\n",buffer.company); p = buffer.address; ok2 = 0; while(*p) { if (*p != 32) { ok2 = 1; break; } p++; } if (!ok2) blanks++; else fprintf(fp,"%s\n",buffer.address); p = buffer.area; ok2 = 0; while(*p) { if (*p != 32) { ok2 = 1; break; } p++; } if (!ok2) blanks++; else fprintf(fp,"%s\n",buffer.area); p = buffer.town; ok2 = 0; while(*p) { if (*p != 32) { ok2 = 1; break; } p++; } if (!ok2) blanks++; else fprintf(fp,"%s\n",buffer.town); p = buffer.county; ok2 = 0; while(*p) { if (*p != 32) { ok2 = 1; break; } p++; } if (!ok2) blanks++; else fprintf(fp,"%s\n",buffer.county); p = buffer.post; ok2 = 0; while(*p) { if (*p != 32) { ok2 = 1; break; } p++; } if (!ok2) blanks++; else fprintf(fp,"%s\n",buffer.post); while(blanks) { fprintf(fp,"\n"); blanks--; } } } } while(result > 0); fclose(fp); lseek(handle,0,SEEK_SET); read(handle,&rec,recsize); DISPDATA(); } void EXPORT_MULTI() { data buffer; char destination[60]; int result; int ok; FILE *fp; textcolor(WHITE); textbackground(RED); gotoxy(left + 23,21); cprintf("Enter selection criteria"); /* Clear existing rec details */ memset(&rec,0,recsize); DISPDATA(); GETDATA(0); textcolor(WHITE); textbackground(RED); gotoxy(left,21); cprintf("Enter export file address.txt"); strcpy(destination,"address.txt"); gotoxy(left + 18,21); INPUT(destination,59); gotoxy(left,21); cprintf("%70c",32); fp = fopen(destination,"w+"); if (fp == NULL) { gotoxy(left,21); cprintf("Unable to print to %s",destination); gotoxy(left,22); cprintf("Press RETURN to Continue"); bioskey(0); gotoxy(left,21); cprintf("%78c",32); gotoxy(left,22); cprintf(" "); } /* Locate start of file */ lseek(handle,0,SEEK_SET); do { /* Read rec into memory */ result = read(handle,&buffer,recsize); if (result > 0) { ok = 1; /* Scan rec for matching data */ if (*rec.name) if (stricmp(buffer.name,rec.name)) ok = 0; if (*rec.company) if (stricmp(buffer.company,rec.company)) ok = 0; if (*rec.address) if (stricmp(buffer.address,rec.address)) ok = 0; if (*rec.area) if (stricmp(buffer.area,rec.area)) ok = 0; if (*rec.town) if (stricmp(buffer.town,rec.town)) ok = 0; if (*rec.county) if (stricmp(buffer.county,rec.county)) ok = 0; if (*rec.post) if (stricmp(buffer.post,rec.post)) ok = 0; if (*rec.telephone) if (stricmp(buffer.telephone,rec.telephone)) ok = 0; if (*rec.fax) if (stricmp(buffer.fax,rec.fax)) ok = 0; if (ok) { fprintf(fp,"\"%s\",",buffer.name); fprintf(fp,"\"%s\",",buffer.company); fprintf(fp,"\"%s\",",buffer.address); fprintf(fp,"\"%s\",",buffer.area); fprintf(fp,"\"%s\",",buffer.town); fprintf(fp,"\"%s\",",buffer.county); fprintf(fp,"\"%s\",",buffer.post); fprintf(fp,"\"%s\",",buffer.telephone); fprintf(fp,"\"%s\"\n",buffer.fax); } } } while(result > 0); fclose(fp); lseek(handle,0,SEEK_SET); read(handle,&rec,recsize); DISPDATA(); } void MENU() { int option; long result; long end; int new; do { cursor(21,26); print("Select option (F2 - F10)"); cursor(7,52); print("F2 Next record"); cursor(8,52); print("F3 Previous record"); cursor(9,52); print("F4 Amend record"); cursor(10,52); print("F5 Add new record"); cursor(11,52); print("F6 Search"); cursor(12,52); print("F7 Continue search"); cursor(13,52); print("F8 Print address labels"); cursor(14,52); print("F9 Export records"); cursor(15,52); print("F10 Exit"); MOUSE_CURSOR(1); option = GETOPT(); MOUSE_CURSOR(0); switch(option) { case 0 : /* Next rec */ result = read(handle,&rec,recsize); if (!result) { lseek(handle,0,SEEK_SET); result = read(handle,&rec,recsize); } DISPDATA(); break; case 1 : /* Previous rec */ result = lseek(handle,0 - recsize * 2,SEEK_CUR); if (result <= -1) lseek(handle,0 - recsize,SEEK_END); result = read(handle,&rec,recsize); DISPDATA(); break; case 3 : /* Add rec */ lseek(handle,0,SEEK_END); memset(&rec,0,recsize); DISPDATA(); case 2 : /* Amend current rec */ new = 1; if (*rec.name) new = 0; else if (*rec.company) new = 0; else if (*rec.address) new = 0; else if (*rec.area) new = 0; else if (*rec.town) new = 0; else if (*rec.county) new = 0; else if (*rec.post) new = 0; else if (*rec.telephone) new = 0; else if (*rec.fax) new = 0; result = tell(handle); lseek(handle,0,SEEK_END); end = tell(handle); /* Back to original position */ lseek(handle,result,SEEK_SET); /* If not at end of file, && !new rewind one rec */ if (result != end || ! new) result = lseek(handle,0 - recsize,SEEK_CUR); result = tell(handle); gotoxy(left + 22,21); print(" Enter address details "); GETDATA(0); if (*rec.name || *rec.company) result = write(handle,&rec,recsize); break; case 4 : /* Search */ gotoxy(left + 22,21); print(" "); SEARCH(); break; case 5 : /* Continue */ gotoxy(left + 22,21); print(" "); CONTINUE(); break; case 6 : /* Print */ gotoxy(left + 22,21); print(" "); PRINT_MULTI(); break; case 7 : /* Export */ gotoxy(left + 22,21); print(" "); EXPORT_MULTI(); break; case 8 : /* Exit */ break; default: /* Amend current rec */ new = 1; if (*rec.name) new = 0; else if (*rec.company) new = 0; else if (*rec.address) new = 0; else if (*rec.area) new = 0; else if (*rec.town) new = 0; else if (*rec.county) new = 0; else if (*rec.post) new = 0; else if (*rec.telephone) new = 0; else if (*rec.fax) new = 0; result = tell(handle); lseek(handle,0,SEEK_END); end = tell(handle); /* Back to original position */ lseek(handle,result,SEEK_SET); /* If not at end of file, && !new rewind one rec */ if (result != end || ! new) result = lseek(handle,0 - recsize,SEEK_CUR); result = tell(handle); gotoxy(left + 22,21); print(" Enter address details "); GETDATA(option - 17); if (*rec.name || *rec.company) result = write(handle,&rec,recsize); option = -1; break; } } while(option != 8); } void exec() { gettext(1,1,80,25,scr); setvideo(3); textbackground(WHITE); textcolor(BLACK); clrscr(); recsize = sizeof(data); OPENDATA(); TRUESHADE(left,3,79,5); window(left - 2,2 ,78, 4); textcolor(YELLOW); textbackground(MAGENTA); clrscr(); DBOX(left - 3, 1, 77, 3); gotoxy(3,2); print("Servile Software PC ADDRESS BOOK 5.2 (c) 1994"); TRUESHADE(left,8,left + 43,18); window(left - 2,7 , left + 42, 17); textcolor(BLACK); textbackground(GREEN); clrscr(); DBOX(left - 3, 6, left + 41, 16); TRUESHADE(left + 48,8,79,18); window(left + 46, 7 , 78, 17); textbackground(BLUE); textcolor(YELLOW); clrscr(); DBOX(left + 45,6,77,16); TRUESHADE(left ,21,79,24); window(left - 2, 20 , 78, 23); textbackground(RED); textcolor(WHITE); clrscr(); DBOX(left - 3,19,77,22); window(1,1,80,25); textcolor(BLACK); textbackground(GREEN); DISPDATA(); MENU(); CLOSEDATA(); puttext(1,1,80,25,scr); return; } INTERFACING C WITH CLIPPER The Clipper programming language is a popular xBase environment for the PC. However, it lacks many of the facilities available to programmers of other languages, and it is quite slow compared to C. Because of this there are a large number of third party add-on libraries available for Clipper which provide the facilities lacked. As a programmer you probably want to write your own library for Clipper, or perhaps individual functions to cater for circumstances which Clipper cannot handle, such as high resolution graphics. Throughout this section, Clipper refers to the Summer `87 Clipper compiler, although initial tests show that the functions described here work perfectly well with the new Clipper 5 compiler also, we are not in a position to guarrantee success! COMPILING AND LINKING The Clipper extend functions allow user defined functions to be written in C, linked with and used by the Clipper application. The first problem a programmer must address when writing functions in C to link with a Clipper application is that of the C compiler's run time libraries. If one is writing functions with Microsoft C, then most of the required run time library functions will be found in the Clipper.lib and Extend.lib libraries which are part of Clipper. If, however, one is using a different C compiler, such as Borland's Turbo C then the run time library routines must be supplied on the link line. All C functions must be compiled using the large memory model the following line is used with Microsoft C cl /c /AL /Zl /Oalt /FPa /Gs and this compile line may be used with Turbo C tcc -c -ml simply substitute for the program name to be compiled. Having compiled a C function it must be linked in with the application. If the C function was compiled with Microsoft C then the link line will look a little like this; LINK /SE:500 /NOE program.obj cfunc.obj,,,Clipper Extend If the C function was linked with another C compiler you will also need to link in the C run time libraries, for example to link in the Turbo C large memory mode library use the following link line; LINK /SE:500 /NOE program.obj cfunc.obj,,,Clipper Extend cl If one is using a number of separately compiled C functions it is a good idea to collect them in a library. If you are using Microsoft C then you can simply create the library by using Microsoft Lib.exe with the following command line; LIB mylib +prog1 +prog2, NUL, NUL This tells the librarian to add prog1.obj and prog2.obj to a library called mylib.lib, creating it if it does not exist. The NUL parameter is for supressing the listing file. If you have been using another C compiler you should copy the C large memory model run time library before adding your functions to it for example; COPY C:\TURBOC\LIB\cl.lib mylib.lib LIB mylib +prog1 +prog2, NUL, NUL Then when you link your Clipper application you will use a link line similar to; LINK /SE:500 /NOE myprog,,,Clipper Extend Mylib Often when linking C functions with Clipper applications link errors will occur such as those shown below; Microsoft (R) Overlay Linker Version 3.65 Copyright (C) Microsoft Corp 1983-1988. All rights reserved. LINK : error L2029: Unresolved externals: FIWRQQ in file(s): M:SLIB.LIB(TEST) FIDRQQ in file(s): M:SLIB.LIB(TEST) There were 2 errors detected Example Link Errors The errors shown here are `Unresolved externals', that is they are references to functions which are not found in any of the object modules or libraries specified on the link line. These occur because the C compilers often scatter functions and variables through a number of libraries. In tracking these functions down use may be made of the Microsoft librarian list file option. If you run Lib.Exe on the Turbo C `emu.lib' library file and specify a listing file as follows; LIB emu,emu.lst The librarian will create an ascii file which contains the names of each object module contained in the specified library file, and the names of each function and public variable declared in each object module, as shown in this listing of Borland's EMU.LIB library. e086_Entry........EMU086 e086_Shortcut.....EMU086 e087_Entry........EMU087 e087_Shortcut.....EMU087 FIARQQ............EMUINIT FICRQQ............EMUINIT FIDRQQ............EMUINIT FIERQQ............EMUINIT FISRQQ............EMUINIT FIWRQQ............EMUINIT FJARQQ............EMUINIT FJCRQQ............EMUINIT FJSRQQ............EMUINIT __EMURESET........EMUINIT EMUINIT Offset: 00000010H Code and data size: 1a2H FIARQQ FICRQQ FIDRQQ FIERQQ FISRQQ FIWRQQ FJARQQ FJCRQQ FJSRQQ __EMURESET EMU086 Offset: 00000470H Code and data size: 2630H e086_Entry e086_Shortcut EMU087 Offset: 00003200H Code and data size: 417H e087_Entry e087_Shortcut Receiving Parameters Clipper provides six different functions for receiving parameters in a C function. These functions are; Receive a string char * _parc(int,[int]) Receive a Date string char * _pards(int,[int]) Receive a logical int _parl(int,[int]) Receive an integer int _parni(int,[int]) Receive a long long _parnl(int,[int]) Receive a double double _parnd(int,[int]) To illustrate simple parameter receiving in a C function I offer the following simple C function which receives two numeric parameters from the calling Clipper program, and uses these two numeric parameters to set the size of the cursor. #include /* Clipper header files */ #include #include /* Header file to define REGS */ CLIPPER s_curset() { /* Demonstration function to set cursor shape */ union REGS inreg,outreg; inreg.h.ah = 0x01; inreg.h.ch = _parni(1); /* Get integer parameter 1 */ inreg.h.cl = _parni(2); /* Get integer parameter 2 */ int86(0x10,&inreg,&outreg); _ret(); /* Return to Clipper */ } Clipper provides four more functions for dealing with received parameters; _parclen(int,[int]) which returns the length of a string including imbedded `\0's, _parcsiz(int[int]) which returns the length of a character string passed by reference from Clipper, _parinfa(int,[int]) which returns the type of a specified array element or the length of an array, and finally _parinfo(int) whic returns the type of a parameter. The following example function uses _parinfa() to determine both the length of an array passed from a Clipper program, and the type of each element in the array. The function then returns to Clipper an integer representing the number of defined elements in the array. #include #include CLIPPER s_alen() { int total; int n; int defined; int type; /* Return the number of defined elements in an array */ /* From Clipper use defined = s_alen(arr) */ total = _parinfa(1,0); /* Get declared number of elements in array */ defined = 0; for (n = 1; n <= total; n++){ type = _parinfa(1,n); /* Get array parameter type */ if (type) defined++; } _retni(defined); /* Return an integer to Clipper */ } This function goes one step further to return the mean average of all numeric values in an array. Notice the use of _parnd() to retrieve the numeric values as doubles. You may find that because of the floating point arithmetic in this function that it will only work if compiled with Microsoft C. #include #include CLIPPER s_aave() { int total; int defined; int n; int type; double sum; /* Return the mean average value of numbers in array */ /* From Clipper use mean = s_aave(arr) total = _parinfa(1,0); /* Get declared number of elements */ defined = 0; for (n = 1; n <= total; n++){ /* Determine number of defined */ type = _parinfa(1,n); /* elements */ if (type == 2) defined++; } sum = 0; for (n = 1; n <= total; n++){ type = _parinfa(1,n); if (type == 2) /* Only sum numeric values */ sum += _parnd(1,n); } _retnd(sum / defined); /* Return a double to Clipper */ } Returning Values The Clipper manual lists seven functions for returning from a function written in another language. These return functions for C are as follows; character _retc(char *) date _retds(char *) logical _retl(int) numeric (int) _retni(int) numeric (long) _retnl(long) numeric (double) _retnd(double) nothing _ret(void) Omitted from the Clipper manual is the information that you may return different types of value back from a function! For example, you may wish to return a character string under normal circumstances, fine use _retc(). On error occurences however you can return a logical using _retl(). The Clipper program will assign the received value to the receiving variable in the correct manner. The following simple C function returns a random number. Notice the use of integers which limits the range of the function to +-32767. For larger values you should use longs instead of integers. #include #include #include CLIPPER s_random() { /* Returns a random number between 0 and param1 - 1 */ /* From Clipper use x = s_random(param1) */ int param1; int x; param1 = _parni(1); x = rand() % param1; _retni(x); } This function receives a string from Clipper, and passes back an upper case copy of the string, leaving the original unchanged. The maximum length of the string which can be processed is determined by the size of target[], here set to 5000 characters. #include #include CLIPPER s_upper() { /* Returns an upper case copy of string */ /* From Clipper use ? s_upper("this is a string") char *p; char *q; char *string; char target[5000]; int n; string = _parc(1); p = string; q = target; while(*string){ *q++ = toupper(*string); string++; } *q = '\0'; string = p; _retc(target); } This function may be used to change the current DOS directory. If it is successful it returns .T. to the calling Clipper program, otherwise it returns .F. #include #include #include CLIPPER s_chdir() { /* Attempts to change the current DOS directory */ /* From Clipper use result = s_chdir(path) */ union REGS inreg,outreg; struct SREGS segreg; char *path; int x; path = _parc(1); /* Retrieve string from Clipper */ inreg.h.ah = 0x3b; segreg.ds = FP_SEG(path); inreg.x.dx = FP_OFF(path); intdosx(&inreg,&outreg,&segreg); x = outreg.x.ax; if (x == 3) _retl(0); /* Return logical .F. back to Clipper */ else _retl(1); /* Return logical .T. back to Clipper */ } Avoiding Unresolved Externals As we have already seen, a common problem plaguing the programmer interfacing C functions with Clipper programs is Unresolved Externals. The following example C function called s_print() will not link with Clipper. #include #include #include CLIPPER s_print() { char *x; x = _parc(1); printf("\nI received %s from Clipper.\n",x); _ret(); } The linker gives you the following reply; Microsoft (R) Overlay Linker Version 3.65 Copyright (C) Microsoft Corp 1983-1988. All rights reserved. M:SLIB.LIB(IOERROR) : error L2025: __doserrno : symbol defined more than once pos: 16C6F Record type: 53C6 LINK : error L2029: Unresolved externals: __RealCvtVector in file(s): M:SLIB.LIB(REALCVT) _abort in file(s): M:SLIB.LIB(CVTFAK) There were 3 errors detected The error L2025 `symbol defined more than once' can in this case be ignored. However, the unresolved externals `RealCvtVector' and `abort' cannot be ignored. These two functions are referenced by the function printf() which has been included in the C function. The answer is to use as few of the compiler's run time library functions as possible, use ROM calls instead with INT86() and INTDOSX() etc. Adding High Resolution Graphics To Clipper With C The most annoying omission from Clipper, in my opinion, is the lack of high resolution graphics facilities. The following functions, written in Turbo C, provide high resolution graphics to Clipper. First we require a means to change the video display mode to a high resolution graphics mode, and back to text mode. The IBM PC BIOS provides the means for this and can be called from C as follows; /* Servile Software Library For Clipper */ #include #include #include CLIPPER s_smode() { /* Set Video Mode */ /* From Clipper use s_smode(mode) */ union REGS inreg,outreg; inreg.h.al = _parni(1); inreg.h.ah = 0x00; int86 (0x10, &inreg, &outreg); /* 1 40x25 colour text 2 40x25 bw text 3 80x25 colour text 4 320x200 4 colour graphics 5 320x200 4 colour graphics colour burst off 6 640x200 2 colour graphics etc */ _ret(); } Having set the computer into graphics mode, how about setting pixels to a specified colour? /* Servile Software Library For Clipper */ #include #include #include CLIPPER s_plot() { union REGS inreg,outreg; /* Sets a pixel at the specified coordinates to the specified colour. */ inreg.h.bh = 0x00; inreg.x.cx = _parni(1); inreg.x.dx = _parni(2); inreg.h.al = _parni(3); inreg.h.ah = 0x0C; int86(0x10, &inreg, &outreg); } Line drawing and circles are handled by these two functions; /* Servile Software Library For Clipper */ #include #include #include CLIPPER s_line() { union REGS inreg,outreg; /* Draws a straight line from (a,b) to (c,d) in colour col */ int a; int b; int c; int d; int col; int u; int v; int d1x; int d1y; int d2x; int d2y; int m; int n; int s; int i; a = _parni(1); b = _parni(2); c = _parni(3); d = _parni(4); col = _parni(5); u = c - a; v = d - b; if (u == 0) { d1x = 0; m = 0; } else { m = abs(u); if (u < 0) d1x = -1; else if (u > 0) d1x = 1; } if ( v == 0) { d1y = 0; n = 0; } else { n = abs(v); if (v < 0) d1y = -1; else if (v > 0) d1y = 1; } if (m > n) { d2x = d1x; d2y = 0; } else { d2x = 0; d2y = d1y; m = n; n = abs(u); } s = (m / 2); inreg.h.al = (unsigned char)col; inreg.h.bh = 0x00; inreg.h.ah = 0x0C; for (i = 0; i <= m; i++) { inreg.x.cx = (unsigned int)(a); inreg.x.dx = (unsigned int)(b); int86(0x10, &inreg, &outreg); s += n; if (s >= m) { s -= m; a += d1x; b += d1y; } else { a += d2x; b += d2y; } } } This circle drawing function uses in-line assembler to speed up the drawing process. It can easily be replaced with inreg and outreg parameters as in the other functions, or the other functions can be changed to in-line assembler. Both methods are shown to illustrate different ways of achieving the same result. /* Servile Software Library For Clipper */ #include #include #include void plot(int x, int y, unsigned char colour) { asm mov al , colour; asm mov bh , 00; asm mov cx , x; asm mov dx , y; asm mov ah , 0Ch; asm int 10h; } int getmode() { /* Returns current video mode and number of columns in ncols */ asm mov ah , 0Fh; asm int 10h; return(_AL); } CLIPPER s_circle() { int x_centre; int y_centre; int radius; int colour; int x,y,delta; int startx,endx,x1,starty,endy,y1; int asp_ratio; x_centre = _parni(1); y_centre = _parni(2); radius = _parni(3); colour = _parni(4); if (getmode() == 6) asp_ratio = 22; else asp_ratio = 13; y = radius; delta = 3 - 2 * radius; for(x = 0; x < y; ) { starty = y * asp_ratio / 10; endy = (y + 1) * asp_ratio / 10; startx = x * asp_ratio / 10; endx = (x + 1) * asp_ratio / 10; for(x1 = startx; x1 < endx; ++x1) { plot(x1+x_centre,y+y_centre,colour); plot(x1+x_centre,y_centre - y,colour); plot(x_centre - x1,y_centre - y,colour); plot(x_centre - x1,y + y_centre,colour); } for(y1 = starty; y1 < endy; ++y1) { plot(y1+x_centre,x+y_centre,colour); plot(y1+x_centre,y_centre - x,colour); plot(x_centre - y1,y_centre - x,colour); plot(x_centre - y1,x + y_centre,colour); } if (delta < 0) delta += 4 * x + 6; else { delta += 4*(x-y)+10; y--; } x++; } if(y) { starty = y * asp_ratio / 10; endy = (y + 1) * asp_ratio / 10; startx = x * asp_ratio / 10; endx = (x + 1) * asp_ratio / 10; for(x1 = startx; x1 < endx; ++x1) { plot(x1+x_centre,y+y_centre,colour); plot(x1+x_centre,y_centre - y,colour); plot(x_centre - x1,y_centre - y,colour); plot(x_centre - x1,y + y_centre,colour); } for(y1 = starty; y1 < endy; ++y1) { plot(y1+x_centre,x+y_centre,colour); plot(y1+x_centre,y_centre - x,colour); plot(x_centre - y1,y_centre - x,colour); plot(x_centre - y1,x + y_centre,colour); } } } The Clipper facilities for displaying text on the screen, @....SAY and ? do not work when the monitor is in graphics mode. You then need the following function to allow text to be displayed in a graphics mode; /* Servile Software Library For Clipper */ #include #include #include int sgetmode(int *ncols) { /* Returns current video mode and number of columns in ncols */ union REGS inreg,outreg; inreg.h.ah = 0x0F; int86(0x10, &inreg, &outreg); *ncols = outreg.h.ah; return(outreg.h.al); } void at(int row, int col) { asm mov bh , 0; asm mov dh , row; asm mov dl , col; asm mov ah , 02h; asm int 10h; } CLIPPER s_say() { char *output; int p = 0; unsigned char page; unsigned char text; int n; int r; int c; int attribute; output = _parc(1); r = _parni(2); c = _parni(3); attribute = _parni(4); asm mov ah , 0Fh; asm int 10h; asm mov page, bh; sgetmode(&n); at(r,c); while (output[p]) { text = output[p++]; asm mov bh , page; asm mov bl , attribute; asm mov cx , 01h; asm mov ah , 09h; asm mov al , text; asm int 10h; c++; if (c < (n-1)) at( r, c); else { c = 0; at(++r,0); } } } When drawing graphs, it is often required to fill in areas of the graph in different patterns. This is a graphics function to fill boundered shapes with a specified hatching pattern providing a means to achieve more usable graphs; /* Servile Software Library For Clipper */ #include #include #include int pixset(int x, int y) { /* Returns the colour of the specified pixel */ asm mov cx ,x; asm mov dx ,y; asm mov ah ,0Dh; asm int 10h; return(_AL); } CLIPPER s_fill() { /* Fill a boundered shape using a hatch pattern */ int mode; int xa; int ya; int bn; int byn; int x; int y; int col; int pattern; int maxx; int maxy; int hatch[10][8] = { 255,255,255,255,255,255,255,255, 128,64,32,16,8,4,2,1, 1,2,4,8,16,32,64,128, 1,2,4,8,8,4,2,1, 238,238,238,238,238,238,238,238, 170,85,170,85,170,85,170,85, 192,96,48,24,12,6,3,1, 62,62,62,0,227,227,227,0, 129,66,36,24,24,36,66,129, 146,36,146,36,146,36,146,36}; /* Patterns for fill, each integer describes a row of dots */ x = _parni(1); y = _parni(2); col = _parni(3); pattern = _parni(4); mode = getmode(); switch(mode) { case 0: case 1: case 2: case 3: break; case 4: case 9: case 13: case 19: case 5: maxx = 320; maxy = 200; break; case 14: case 10: case 6: maxx = 640; maxy = 200; break; case 7: maxx = 720; maxy = 400; break; case 8: maxx = 160; maxy = 200; break; case 15: case 16: maxx = 640; maxy = 350; break; case 17: case 18: maxx = 640; maxy = 480; break; } xa = x; ya = y; /* Save Origin */ if(pixset(x,y)) return; bn = 1; byn = 0; do { if (hatch[pattern][byn] != 0) { /* If blank ignore */ do { if ((bn & hatch[pattern][byn]) == bn) { asm mov al , col; asm mov bh , 00; asm mov cx , x; asm mov dx , y; asm mov ah , 0Ch; asm int 10h; } x--; bn <<= 1; if (bn > 128) bn = 1; } while(!pixset(x,y) && (x > -1)); x = xa + 1; bn = 128; do { if ((bn & hatch[pattern][byn]) == bn) { asm mov al , col; asm mov bh , 00; asm mov cx , x; asm mov dx , y; asm mov ah , 0Ch; asm int 10h; } x++; bn >>=1; if (bn <1) bn = 128; } while((!pixset(x,y)) && (x <= maxx)); } x = xa; y--; bn = 1; byn++; if (byn > 7) byn = 0; } while(!pixset(x,y) && ( y > -1)); /* Now travel downwards */ y = ya + 1; byn = 7; bn = 1; do { /* Travel left */ if (hatch[pattern][byn] !=0) { do { if ((bn & hatch[pattern][byn]) == bn) { asm mov al , col; asm mov bh , 00; asm mov cx , x; asm mov dx , y; asm mov ah , 0Ch; asm int 10h; } x--; bn <<= 1; if (bn > 128) bn = 1; } while(!pixset(x,y) && (x > -1)); /* Back to x origin */ x = xa + 1 ; bn = 128; /* Travel right */ do { if ((bn & hatch[pattern][byn]) == bn) { asm mov al , col; asm mov bh , 00; asm mov cx , x; asm mov dx , y; asm mov ah , 0Ch; asm int 10h; } x++; bn >>=1; if (bn <1) bn = 128; } while((!pixset(x,y)) && (x <= maxx)); } x = xa; bn = 1; y++; byn--; if (byn < 0) byn = 7; } while((!pixset(x,y)) && (y <= maxy)); } SPELL - AN EXAMPLE PROGRAM It has been said that example programs provide a good way of learning a new computer language. On that basis the following simple program is offered as an example of making use of the dynamic memory allocation provided by DOS. This is a spell checker for ASCII (text) documents, written in, and making use of Borland's Turbo C text graphics facilities for displaying windows of text. /* Spell checker for ascii documents */ /* Compile with -mc (compact memory model) and unsigned characters */ #include #include #include #include #include #include #include #include #include #include #include #include #define ON 0x06 #define OFF 0x20 #define MAXIMUM 15000 #define WORDLEN 20 #define LEFTMARGIN 1 union REGS inreg,outreg; char *dicname; char *dic[MAXIMUM]; /* Array of text lines */ char word[31]; char comp[31]; char fname[160]; int lastelem; char changed; char *ignore[100]; int lastign; int insert; int n; int bp; int mp; int tp; int result; char *text; char *textsav; void AT(int, int); void BANNER(void); int COMPARE(void); void CORRECT(void); void FATAL(char *); void FILERR(char *); void GETDIC(void); void IGNORE(void); void INSERT(void); int MATCHSTR(char *, char *); void SPELL(void); void UPDATE(void); void CURSOR(char status) { /* Toggle cursor display on and off */ union REGS inreg,outreg; inreg.h.ah = 1; inreg.h.ch = (unsigned char)status; inreg.h.cl = 7; int86(0x10,&inreg,&outreg); } void DISPLAY(char *text) { /* Display 'text' expanding tabs and newline characters */ while(*text) { switch(*text) { case '\n': cputs("\r\n"); break; case '\t': cputs(" "); break; default: putch(*text); } text++; } } void GETDIC() { /* Read dictionary into memory */ FILE *fp; char *p; int poscr; int handle; window(1,22,80,24); clrscr(); gotoxy(28,2); cprintf("Reading Dictionary...."); changed = 0; lastelem = 0; dicname = searchpath("spell.dic"); handle = open(dicname,O_RDWR); if (handle < 0) FILERR("spell.dic"); fp = fdopen(handle,"r"); if (fp == NULL) FILERR("spell.dic"); do { dic[lastelem] = calloc(WORDLEN,1); if (dic[lastelem]) { p = fgets(dic[lastelem],79,fp); /* Remove carriage return from end of text line */ poscr = (int)strlen(dic[lastelem]) - 1; if (dic[lastelem][poscr] == '\n') dic[lastelem][poscr] = 0; } else FATAL("Unable To Allocate Memory"); } while((p != NULL) && (lastelem++ < MAXIMUM)); lastelem--; fclose(fp); } void UPDATE() { FILE *fp; int n; if (changed) { window(1,22,80,24); clrscr(); gotoxy(27,2); cprintf("Updating Dictionary...."); fp = fopen(dicname,"w+"); if (fp == NULL) FILERR("spell.dic"); for(n = 0; n <= lastelem; n++) fprintf(fp,"%s\n",dic[n]); fclose(fp); } } void IGNORE() { /* Add a word to the ignore table */ if (lastign < 100) { ignore[lastign] = calloc(strlen(word) + 1,1); if (ignore[lastign]) strcpy(ignore[lastign++],comp); else { clrscr(); cprintf("No available memory for new words!\r\nPress A key...."); bioskey(0); } } else { clrscr(); cprintf("No available memory for new words!\r\nPress A key...."); bioskey(0); } } void FATAL(char *text) { /* Fatal error drop out */ textcolor(LIGHTGRAY); textbackground(BLACK); window(1,1,80,25); clrscr(); printf("SERVILE SOFTWARE\n\nSPELL V1.7\nFATAL ERROR: %s\n\n",text); CURSOR(ON); exit(0); } void FILERR(char *fname) { char text[60]; strcpy(text,"Unable To Access: "); strcat(text,fname); FATAL(text); } int COMPARE() { char **p; /* Check Ignore table */ for(p = ignore; p <= &ignore[lastign]; p++) if (strcmp(comp,*p) == 0) return(1); /* Binary search of dictionary file */ bp = 0; tp = lastelem; mp = (tp + bp) / 2; while((result = strcmp(dic[mp],comp)) != 0) { if (mp >= tp) { /* Not found! */ insert = mp; if (result > 0) insert--; return(0); } if (result < 0) bp = mp + 1; else tp = mp - 1; mp = (bp + tp) / 2; } return(1); } void INSERT() { int n; changed = 1; lastelem++; n = lastelem; dic[n] = calloc(WORDLEN,1); if (dic[n] == NULL) { clrscr(); cprintf("No available memory for new words!\r\nPress A key...."); bioskey(0); free(dic[n]); lastelem--; return; } while(n > (insert + 1)) { strcpy(dic[n],dic[n-1]); n--; }; strcpy(dic[insert + 1],comp); } void SPELL() { FILE *target; FILE *source; char *p; char *x; char temp[256]; char dat1[1250]; char dat2[1250]; int c; int m; int found; int curpos; int key; int row; int col; int srow; int scol; window(1,1,80,20); textcolor(BLACK); textbackground(WHITE); /* Open temporary file to take spell checked copy */ target = fopen("spell.$$$","w+"); source = fopen(fname,"r"); if (source == NULL) FILERR(fname); lastign = 0; do { clrscr(); text = dat1; p = text; textsav = dat2; strcpy(text,""); /* Display read text */ row = wherey(); col = wherex(); for(m = 0; m < 15; m++) { x = fgets(temp,200,source); if (x) { strcat(text,temp); DISPLAY(temp); } if (wherey() > 18) break; } /* return cursor to start position */ gotoxy(col,row); do { memset(word,32,30); curpos = 0; do { c = *text++; if ((isalpha(c)) || (c == '-') && (curpos != 0)) word[curpos++] = c; } while(((isalpha(c)) || (c == '-') && (curpos != 0)) && (curpos < 30)); word[curpos] = 0; strcpy(comp,word); strupr(comp); if (*comp != 0) { found = COMPARE(); if (!found){ textbackground(RED); textcolor(WHITE); } } else found = 1; srow = wherey(); scol = wherex(); cputs(word); textbackground(WHITE); textcolor(BLACK); switch(c) { case '\n': cputs("\r\n"); break; case '\t': cputs(" "); break; default: putch(c); } row = wherey(); col = wherex(); if (!found) { window(1,22,80,24); clrscr(); cputs("Unknown word "); textcolor(BLUE); cprintf("%s ",word); textcolor(BLACK); cputs("[A]dd [I]gnore [C]orrect [S]kip"); do { key = toupper(getch()); if (key == 27) key = 'Q'; } while(strchr("AICSQ",key) == NULL); switch(key) { case 'A':INSERT(); break; case 'C':CORRECT(); break; case 'I':IGNORE(); break; } if (key == 'C') { clrscr(); gotoxy(1,1); strcpy(textsav,--text); /* Delete old word */ text -= strlen(comp); *text = 0; /* Insert new word */ strcat(text,word); /* Append remainder of text */ strcat(text,textsav); text += strlen(word); text++; /* Length of text may have changed ! */ if (strlen(word) < strlen(comp)) col -= (strlen(comp) - strlen(word)); window(1,1,80,20); clrscr(); DISPLAY(p); } else { clrscr(); gotoxy(29,2); cputs("Checking Spelling...."); window(1,1,80,20); gotoxy(scol,srow); cputs(word); } window(1,1,80,20); gotoxy(col,row); } } while((*text) && (key != 'Q')); fprintf(target,"%s",p); } while((x != NULL) && (key != 'Q')); window(1,22,80,24); clrscr(); gotoxy(27,2); cprintf("Writing Updated File...."); do { p = fgets(temp,200,source); if (p) fprintf(target,"%s",temp); } while(p); fclose(target); fclose(source); /* Now transfer spell.$$$ to fname */ unlink(fname); rename("SPELL.$$$",fname); } void CORRECT() { /* Locate a good match and return word */ char text[51]; int m; int n; int key; window(1,22,80,24); clrscr(); gotoxy(25,2); cprintf("Searching For Alternatives...."); /* Remove any pending key strokes from keyboard buffer */ while(kbhit()) getch(); for(n = 0; n <= lastelem; n++) { if (MATCHSTR(dic[n],comp)) { strcpy(text,dic[n]); if (strlen(word) <= strlen(text)) { for (m = 0; m < strlen(word); m++) { if (isupper(word[m])) text[m] = toupper(text[m]); else text[m] = tolower(text[m]); } for(m = strlen(word); m < strlen(text); m++) if (isupper(word[strlen(word)])) text[m] = toupper(text[m]); else text[m] = tolower(text[m]); } else { for (m = 0; m < strlen(text); m++) { if (isupper(word[m])) text[m] = toupper(text[m]); else text[m] = tolower(text[m]); } } clrscr(); cprintf("Replace "); textcolor(BLUE); cprintf("%s ",word); textcolor(BLACK); cprintf("With "); textcolor(BLUE); cprintf("%s",text); textcolor(BLACK); cprintf(" Yes No Continue"); do { key = toupper(getch()); } while(strchr("YNC",key) == NULL); if (key == 'Y') { strcpy(word,text); return; } clrscr(); gotoxy(25,2); cprintf("Searching For Alternatives...."); /* Remove any pending key strokes from keyboard buffer */ while(kbhit()) getch(); if (key == 'C') return; } } clrscr(); gotoxy(23,2); cprintf("NO ALTERNATIVES FOUND! (Press a key)"); bioskey(0); return; } int MATCHSTR(char *src, char *tgt) { /* Compare two words and return non zero if they are similar */ int match; int result; int strsrc; int strtgt; int longest; strtgt = strlen(strupr(tgt)); strsrc = strlen(strupr(src)); longest = max(strtgt,strsrc); match = 0; if(strtgt > strsrc) { for(; *src ; match += (*src++ == *tgt++)) ; } else { for(; *tgt ; match += (*src++ == *tgt++)) ; } result = (match * 100 / longest); /* result holds percentage similarity */ if (result > 50) return(1); return(0); } void AT(int row, int col) { /* Position the text cursor */ inreg.h.bh = 0; inreg.h.dh = row; inreg.h.dl = col; inreg.h.ah = 0x02; int86 (0x10, &inreg, &outreg); } void WRTCHA (unsigned char ch, unsigned char attrib, int num) { /* Display a character num times in colour attrib */ /* via the BIOS */ inreg.h.al = ch; inreg.h.bh = 0; inreg.h.bl = attrib; inreg.x.cx = num; inreg.h.ah = 0x09; int86 (0x10, &inreg, &outreg); } void SHADE_BLOCK(int left,int top,int right,int bottom) { int c; AT(bottom,right); WRTCHA(223,56,1); AT(top,right); WRTCHA('�',7,1); for (c = top+1; c < bottom; c++) { AT(c,right); WRTCHA(' ',7,1); } AT(bottom,left+1); WRTCHA('�',7,right-left); } void BOX(int l, int t, int r, int b) { /* Draws a single line box around a described area */ int n; char top[81]; char bottom[81]; char tolc[5]; char torc[5]; char bolc[5]; char borc[5]; char hoor[5]; sprintf(tolc,"%c",218); sprintf(bolc,"%c",192); sprintf(hoor,"%c",196); sprintf(torc,"%c",191); sprintf(borc,"%c",217); strcpy(top,tolc); strcpy(bottom,bolc); for(n = l + 1; n < r; n++) { strcat(top,hoor); strcat(bottom,hoor); } strcat(top,torc); strcat(bottom,borc); window(1,1,80,25); gotoxy(l,t); cputs(top); for (n = t + 1; n < b; n++) { gotoxy(l,n); putch(179); gotoxy(r,n); putch(179); } gotoxy(l,b); cputs(bottom); } void BANNER() { window (2,2,78,4); textcolor(BLACK); textbackground(GREEN); clrscr(); SHADE_BLOCK(1,1,78,4); BOX(2,2,78,4); gotoxy(4,3); cprintf("Servile Software SPELL CHECKER V1.7 (c)1992"); } void main(int argc, char *argv[]) { char *p; char tmp_name[160]; char tmp_fname[160]; if (argc != 2) { puts("\nERROR: Usage is SPELL document"); exit(1); } else strcpy(fname,argv[1]); CURSOR(OFF); GETDIC(); window(1,22,80,24); clrscr(); gotoxy(28,2); cprintf("Making Backup File...."); strcpy(tmp_fname,argv[1]); /* Remove extension from tmp_fname */ p = strchr(tmp_fname,'.'); if(p) *p = 0; /* Create backup file name using DOS */ sprintf(tmp_name,"copy %s %s.!s! > NUL",argv[1],tmp_fname); system(tmp_name); window(1,1,80,25); textcolor(WHITE); textbackground(BLACK); clrscr(); gotoxy(29,2); cprintf("Checking Spelling...."); SPELL(); UPDATE(); window(1,1,80,25); textcolor(LIGHTGRAY); textbackground(BLACK); clrscr(); CURSOR(ON); } APPENDIX A - USING LINK General Syntax: LINK [options] obj[,[exe][,[map][,[lib]]]][;] `obj' is a list of object files to be linked. Each obj file name must be separated by a + or a space. If you do not specify an extension, LINK will assume .OBJ. `exe' allows you to specify a name for the executable file. If this file name is ommited, LINK will use the first obj file name and suffix it with .EXE. `map' is an optional map file name. If you specify the name `NUL', no map file is produced. `lib' is a list of library files to link. LINK searches each library file and only links in modules which are referenced. eg: LINK filea+fileb,myfile,NUL; Links .obj files `filea.obj' and `fileb.obj' into .exe file `myfile.exe' with no map file produced. The ; at the end of the line tells LINK that there are no more parameters. Using Overlays Overlay .obj modules are specified by encasing the .obj name in parenthesis in the link line. eg: LINK filea + (fileb) + (filec),myfile,NUL; Will link filea.obj fileb.obj and filec.obj with modules fileb.obj and filec.obj as overlay code. Overlay modules must use FAR call/return instructions. Linker Options All LINK options commence with a forward slash `/'. Options which accept a number can accept a decimal number or a hex number prefixed 0X. eg: 0X10 is interpreted as 10h, decimal 16. Pause during Linking (/PAU) Tells LINK to wait before writing the .exe file to disk. LINK displays a message and waits for you to press enter. Display Linker Process Information (/I) Tells LINK to display information about the link process. Pack Executable File (/E) Tells LINK to remove sequences of repeated bytes and to optimise the load-time relocation table before creating the executable file. Symbolic debug information is stripped out of the file. List Public Symbols (/M) Tells LINK to create a list of all public symbols defined in the object files in the MAP file. Include Line Numbers In Map File (/LI) Tells LINK to include line numbers and associated addresses of the source program in the MAP file. Preserve Case Sensitivity (/NOI) By default LINK treats uppercase and lowercase letters as the same. This option tells LINK that they are different. Ignore Default Libraries (/NOD) Tells LINK not to search any library specified in the object files to resolve external references. Controlling Stack Size (/ST:n) Specifies the size of the stack segment where 'n' is the number of bytes. Setting Maximum Allocation Space (/CP:n) Tells LINK to write the parameter 'n' into the exe file header. When the exe file is executed by DOS, 'n' 16 byte paragraphs of memory are reserved. If 'n' is less than the minimum required, it will be set to the minimum. This option is ESSENTIAL to free memory from the program. C programs free memory automatically on start-up, assembly language programs which want to use dynamic memory allocation must be linked with this option set to a minimum. Setting Maximum Number Of Segments (/SE:n) Tells LINK how many segments a program is allowed to have. The default is 128 but 'n' can be any number between 1 and 3072. Setting Overlay Interrupt (/O:n) Tells LINK which interrupt number will be used for passing control to overlays. The default is 63. Valid values for 'n' are 0 through 255. Ordering Segments (/DO) Tells LINK to use DOS segment ordering. This option is also enabled by the MASM directive .DOSSEG. Controlling Data Loading (/DS) By default LINK loads all data starting at the low end of the data segment. At run time the DS register is set to the lowest possible address to allow the entire data segment to be used. This option tells LINK to load all data starting at the high end of the data segment. Control Exe File Loading (/HI) Tells LINK to place the exe file as high as possible in memory. Prepare for Debugging (/CO) Tells LINK to include symbolic debug information for use by codeview. Optimising Far Calls (/F) Tells LINK to translate FAR calls to NEAR calls where possible. This results in faster code. Disabling Far Call Optimisation (/NOF) Tells LINK not to translate FAR calls. This option is specified by default. Packing Contiguous Segments (/PAC:n) Tells LINK to group together neighbouring code segments, providing more oportunities for FAR call translation. 'n' specifies the maximum size of a segment. By default 'n' is 65530. This option is only relevant to obj files created using FAR calls. Disabling Segment Packing (/NOP) Disables segment packing. This option is specified by default. Using Response Files Linker options and file names may be specified in a response file. Each file list starting on a new line instead of being separated by a comma. eg: filea.obj fileb.obj myfile.exe NUL liba.lib libb.lib A response file is specified to LINK by prefixing the response file name with '@'. eg: LINK @response