From c7f51db40fc21992106efa832b4e1e5c6b745953 Mon Sep 17 00:00:00 2001 From: sparky4 Date: Thu, 11 Dec 2014 13:08:56 -0600 Subject: [PATCH] added a cool document new file: doc/CGUIDE_3.TXT --- doc/CGUIDE_3.TXT | 14499 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 14499 insertions(+) create mode 100644 doc/CGUIDE_3.TXT diff --git a/doc/CGUIDE_3.TXT b/doc/CGUIDE_3.TXT new file mode 100644 index 00000000..23f6a6b8 --- /dev/null +++ b/doc/CGUIDE_3.TXT @@ -0,0 +1,14499 @@ + + + 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 + -- 2.39.2