Home of the original IBM PC emulator for browsers.
The following document is from the Microsoft Programmer’s Library 1.3 CD-ROM.
Proficient C
The Microsoft(R) guide to intermediate and advanced C programming
by AUGIE HANSEN
PUBLISHED BY
Microsoft Press
A Division of Microsoft Corporation
16011 N.E. 36th Way, Box 97017, Redmond, Washington 98073-9717
Copyright (C) 1987 by Augie Hansen
All rights reserved. No part of the contents of this book may be reproduced
or transmitted in any form or by any means without the written permission
of the publisher.
Library of Congress Cataloging in Publication Data
Hansen, Augie
Proficient C.
Includes index.
1. C (Computer program language) I. Title.
QA76.73C15H367 1987 005.13'3 86-31109
ISBN 1-55615-007-5
Printed and bound in the United States of America.
4 5 6 7 8 9 FGFG 8 9 0 9 8 7
Distributed to the book trade in the United States by Harper & Row.
Distributed to the book trade in Canada by General Publishing Company,
Ltd.
Distributed to the book trade outside the United States and Canada by
Penguin Books Ltd.
Penguin Books Ltd., Harmondsworth, Middlesex, England
Penguin Books Australia Ltd., Ringwood, Victoria, Australia
Penguin Books N.Z. Ltd., 182-190 Wairau Road, Auckland 10, New Zealand
British Cataloging in Publication Data available
Microsoft(R) and MS-DOS(R) are registered trademarks of Microsoft
Corporation.
This book is dedicated to my parents, who worried throughout my childhood
and teenage years that I would electrocute myself in my electronics
laboratory, and to my family, Doris, Lindsey, Reid, and Corri. They all
mean much more to me than I can ever tell them using mere words.
Contents
Acknowledgments
Introduction
SECTION I Getting Started
1 The C Compiler System
2 Program Development
3 The DOS-to-C Connection
SECTION II Standard Libraries and Interfaces
4 Using Standard Libraries
5 PC Operating System Interfaces
6 The User Interface
7 Automatic Program Configuration
SECTION III File-Oriented Programs
8 Basic File Utilities
9 File Printing
10 Displaying Non-ASCII Text
SECTION IV Screen-Oriented Programs
11 Screen Access Routines
12 Buffered Screen-Interface Functions
13 The ANSI Device Driver
14 Viewing Files
SECTION V Appendixes
A Microsoft C Compiler, Version 4.00
B Other C Compilers
C Characters and Attributes
D Local Library Summary
Index
Acknowledgments
Working with the management and staff at Microsoft
Press has been a distinct honor and a rewarding experience
in many ways. I am impressed by the dedication, skill, and
knowledge of all the individuals that I have dealt with
either directly or indirectly during the development and
preparation of Proficient C.
The person who is responsible for suggesting this book
to me is Claudette Moore. She helped me pare down the
original outline to something manageable, provided all the
needed software, arranged for Microsoft experts to answer my
questions, and kept things moving in the right direction.
Marie Doyle had the primary responsibility for editing
the manuscript. She has been tireless in her efforts to
obtain consistency and accuracy of the material and to
ferret out my hidden assumptions. Thanks also to Dori
Shattuck who participated in the early editing of the
project.
I am indebted to Jeff Hinsch for poring over all the
figures and source listings, testing all programs to verify
correct operation, and working with me at some pretty
strange hours to transfer files electronically.
Jim Beley has been involved throughout the project. He
was best known to me as the hit man ("Just calling to check
on our schedule ... "), but his efforts and those of his
staff are essential to the preparation and delivery of a
book. Jim made many helpful suggestions along the way to
improve the book and assure its timely delivery.
I also wish to thank Reed Koch, who reviewed the
manuscript and contributed many useful comments and
suggestions on the manuscript and the programs.
Many other individuals who have devoted themselves to
this project will probably never be known to me by name. I
appreciate their efforts nonetheless.
Introduction
Building a house that someone is willing to live in is quite an
undertaking. The process starts with a good design that makes sense in the
context of its surroundings and proceeds to completion through a series of
carefully planned steps. Throughout the process, the builder must select
the right materials and must employ the right tools to shape and join those
materials.
A computer programmer faces a similar challenge. He or she needs good
tools and needs to use them as effectively as a craftsperson in any other
profession or trade. Rather than hammers and saws, the programmer needs
assemblers, compilers, linkers, debuggers, and many other specialized
tools. In addition, the computer programmer can benefit from the work of
others by examining and using collections of ready-to-use program modules.
Objectives of This Book
Proficient C is a book for programmers. It is primarily about writing
programs that are good tools for programmers. It demonstrates the use of C
in the development of nontrivial applications, so we will not waste any
time solving the Fibonacci series or calculating factorials. Although
programs that do such things are instructive, they are of little value to
us in building our tools.
Proficient C presents proven techniques for developing programs that
solve real problems. Now, any person with a modicum of business sense knows
that a problem is simply an opportunity in disguise, and some programmers
have been astute enough to detect this connection and turn it into piles
of hard cash. Whether or not money is your motivation for programming is
unimportant; if you are going to program for any reason, you should try to
do it well. This book is designed to help you do just that.
We will focus on creating building blocks--reusable modules that have
wide applicability. Being frugal with our time and energy, we will use
existing modules, such as those in the standard libraries that accompany
most C compilers, as much as possible. And, of course, we will use our own
modules again and again as we develop increasingly sophisticated programs.
Intended Audience
Programmers with at least a modest level of experience in some high-
level language or assembly language will have an easy time going through
this book. A working knowledge of C or a similar language (such as Pascal,
ALGOL, Modula 2, or Ada) is assumed. Those readers without such experience
will be best served by first reading an introductory text on C. Several
good beginner- and intermediate-level C books are listed at the end of this
introduction.
I don't share the opinion that BASIC programmers are somehow crippled
by the experience of programming in BASIC, but I admit that many will find
some of the concepts embodied in C to have an alien feel. Topics like
indirection and recursion don't become immediately clear to most
programmers who encounter them for the first time. Study and practice are
required in order to become comfortable with them.
Hardware
All functions and programs in this book were developed and tested in
an MS-DOS environment on an AT&T PC6300, and in a PC-DOS environment on an
IBM PC/AT. The programs will probably also work on other compatible
hardware that runs MS-DOS version 2.00 or later. With suitable compilers,
many of the programs in this book that are not dependent upon specific PC
features can easily be ported to other hardware and operating systems.
That's one of the many beauties of C.
You are well advised to have as much primary and secondary storage as
possible. A comfortable amount of random access memory (RAM) is 512
kilobytes. This allows room for a RAM disk to speed some operations. A hard
disk is also recommended. These recommendations are based on the fact that
most modern C compilers in the PC marketplace are large and are usually
accompanied by massive libraries of standard functions. C compilers will
function on less well-endowed systems, but the cost is usually diminished
performance.
Software
Many of the programs in this book require MS-DOS or PC-DOS, version
2.00 or later. For the most part, I will refer to DOS in the generic sense.
I will use the terms MS-DOS and PC-DOS in contexts where the difference is
noticeable. (There are very few of these, limited mostly to a few external
commands that may be available in one version and not in another.)
A good text editor is critical to success in large programming
projects. You can use the DOS EDLIN editor if you're a bit of a masochist,
but I recommend that you acquire a good programmer's editor like EDIX
(Emerging Technology Consultants, Inc.), Brief (Solution Systems), or
Epsilon (Lugaru Software, Ltd.). You may also use Microsoft Word, WordStar,
or any other word processor that permits files to be saved in a straight
ASCII (unformatted) form.
Without a C compiler, of course, this book will be of little value to
you. The programs were written using Microsoft C, version 4.00, but other C
compilers can be used instead. I have tried to minimize the use of features
of one compiler that would prevent the use of others of comparable
capability. Appendix B describes several other C compilers and what, if
anything, must be done to compile the programs presented in this book
using those compilers.
The DOS linker (LINK) provided with the operating system is used to
combine object files and library modules into executable programs. A
version of the linker that understands DOS pathname conventions is a
necessity. Microsoft provides updated versions of the linker with all of
its language products.
Organization
Section I describes the C compiler system and presents some views on
program development. In Chapter 1, we look at the proposed ANSI standard
for C and at compilers that attempt to track this moving target. Chapter 2
explores ways in which program development can be made more of a science
than an art. Chapter 3 shows how C is used in a DOS setting with emphasis
placed on effective use of the DOS command-line and environment variables.
Section II describes standard libraries and interfaces. Chapter 4
focuses on the use of the many existing functions in the standard C
libraries. In Chapter 5, we explore the low-level interface to DOS from
our C programs. Although not portable to other operating systems, such as
UNIX/XENIX, these functions are important for certain classes of programs
running on the PC. Additional functions built on top of the standard
libraries are used at a higher level to manage the all-important
user/machine interface. These functions are the topic of Chapter 6. In
Chapter 7, we automate program configuration, but allow the user to
override the automatic settings in various ways.
Section III presents a useful set of file-oriented programs.
Chapter 8 provides several UNIX-like file and directory utilities,
including an LS program to list a directory in special formats, a TOUCH
command to assist the MAKE program maintainer, and an RM command. This
orientation to basic file utilities leads to the development of a versatile
print command (PR) in Chapter 9. Chapter 10 deals with ways of viewing
the contents of non-ASCII files.
Section IV switches gears, leaving the realm of line-oriented programs
to address the issues of screen-oriented programs. Chapter 11 introduces
the concept of a buffered screen interface, and the concept is brought to
fruition and expanded upon in Chapter 12, which covers screen-management
functions. Chapter 13 describes an interface package that uses the much-
maligned ANSI device driver provided with DOS. Chapter 14 presents a
visual means of viewing files.
Other Books on C
Many introductory-level C books crowd the shelves in bookstores, with
most of the books introduced in the past four or five years due to the
rapidly increasing popularity of PCs and to the "discovery" that C is an
extremely compliant and versatile computer programming language. Here is a
list of some C books that my students and I have found to be valuable
learning aids.
The C Primer, 2nd Edition, by Les Hancock and Morris Krieger (McGraw-
Hill, 1985), is noted for its gentle introduction to programming in C. The
use of flowcharts and examples based on models familiar to everyone makes
this an easy-to-read and enjoyable book.
Programming in C, by Stephen G. Kochan (Hayden, 1983), has been my
mainstay as a teaching book for beginning programmers. It covers the
essentials of C without getting bogged down in superfluous detail.
C Primer Plus by Mitchell Waite, Stephen Prata, and Donald Martin
(SAMS, 1984), is replete with corny but memorable examples and
illustrations. It is a comprehensive book that gets a bit deeper into C
language than some of the other beginners' books.
In addition to these introductory books, you may want to take a pass
through Variations in C by Steve Schustack, another C book from Microsoft
Press (1985). It is oriented toward developers of business applications
and is loaded with advice on good programming style and many very useful
examples.
And every C programmer should have a copy of the ubiquitous The C
Programming Language by Brian Kernighan and Dennis M. Ritchie (Prentice-
Hall, 1978). Although it is somewhat dated now, it is a treasure trove of
C lore and law and is still considered the standard reference on the C
language.
Notational Conventions
The following notational conventions apply to the text and examples in
this book:
► DOS program names are shown in capital letters (DIR, TYPE). The
names of programs we develop are treated the same way (PR, VE, NOTES) when
the executable program is being referred to.
► Names of C functions, variables, and keywords are shown in italics
(environ, fgets(), main(), #define). This same treatment is given to the
names of source and object files (myprog.c, myprog.obj) and user-supplied
function, constant, and variable names.
► Command syntax follows these conventions:
═══════════════════════════════════════════════════════════════════════════
ITEM CONVENTION EXAMPLE
───────────────────────────────────────────────────────────────────────────
literal text │ roman type │ cmd
placeholders │ italic type │ cmd option
repeat item │ ellipses │ cmd file . . .
optional item │ square brackets ([]) │ cmd [opts] file
logical OR │ vertical bar (|) │ cmd [a | b] file . . .
───────────────────────────────────────────────────────────────────────────
SECTION I Getting Started
Chapter 1 The C Compiler System
The tradition of C, originally established in its UNIX context, has
had a profound effect on the programming environments of a vast range of
machine types and sizes and has influenced the gamut of computer operating
systems. Whether you program on an S-100-bus 8080 system running CP/M or on
a CRAY-2 hosting a version of UNIX System V, or nearly anything in between,
there is probably a C compiler system available for your environment.
In this chapter, we will briefly describe the proposed American
National Standards Institute (ANSI) standard for C, some emerging standards
for operating systems, and extensions of those that affect our C
programming. Then we'll get a brief overview of the Microsoft C Compiler,
its various memory models, and C programming support tools.
Standards and Compatibility
Anything that survives the test of time and that is likely to have a
wide base of support is a candidate for standardization. As standards
evolve, de facto or otherwise, we face the issue of compatibility with the
standard. So it is with C.
Proposed ANSI-Standard C
The first attempt at standardizing C was the distribution of the "C
Reference Manual" written by Dennis M. Ritchie, the author of C. The "C
Reference Manual" was published as an appendix to the book The C
Programming Language by Brian Kernighan and Dennis M. Ritchie. For many
years, this book has been the only generally available description of C.
However, since the "C Reference Manual" was promulgated, there have been
numerous changes to C that are not supported by all C compiler suppliers,
so portability among computer systems, and even among compilers on the same
computer system, has been diminished.
The /usr/group Committee produced the "UNIX 1984 Standard" document to
attempt standardization of the UNIX operating system. One major aspect of
the /usr/group proposal is a standard set of library routines. Many of the
recommendations, but not all of them, have been incorporated into AT&T's
"System V Interface Definition," which defines a standard base for, among
other things, the Operating System Services and Other Library Routines, the
two major components of the standard library for C. The current de facto
standard for C is the UNIX System V implementation by AT&T.
In recent years, the American National Standards Institute's Technical
Committee on the C Programming Language--Committee X3J11--has been working
to produce the first official American National Standard for C. Committee
X3J11 is composed of members from various industrial companies, educational
institutions, and government agencies.
The proposed ANSI standard for C is an attempt to "promote
portability, reliability, maintainability, and efficient execution of C
language programs on a variety of computing systems." The draft standard
uses the "C Reference Manual" and the "UNIX 1984 Standard" as base
documents for the language and the library, respectively.
The following list describes some of the features that have been added
to C or changed since its original description and points out some of the
implications of the new features and changes.
► The enum type specifier has been added, giving C an enumeration
data type.
► The void keyword can be applied to functions that do not return a
value. A function that does return a value can have its return
value cast to void to indicate to the compiler (and lint, under
UNIX/XENIX) that the value is being deliberately ignored.
► Structure handling has been greatly improved. The member names in
structure and union definitions need not be unique. Structures can
be passed as arguments to functions, returned by functions, and
assigned to structures of the same type.
► Function declarations can include argument-type lists (function
prototyping) to notify the compiler of the number and types of
arguments.
► Hexadecimal character constants can be expressed using an
introductory \x followed by from one to three hexadecimal digits
(0--F). Example: 16 decimal=\x10, which can be written as 0x10
using the current notation.
► The # in preprocessor directives can have leading white space (any
combination of spaces and tabs), permitting indented preprocessor
directives for clarity. In addition, new preprocessor directives
have been added:
#if defined (expression)
#elif (expression)
Standard Run-Time Library Routines
The standard libraries that accompany most C compilers are standard
because we accept them as such, not because of any law. Over many years and
millions of lines of C source code, programmers have evolved a set of
often-used routines that have been polished and packaged into libraries.
Some of the routines, such as read() and write(), provide system-level
services and are essentially our hooks into the underlying operating
system. Other routines--string-handling functions and macros, for example--
are part of the external subroutine library. (In Chapter 4, we will examine
some representative standard library functions and present examples of
their uses in programs.)
In the last few years, some new additions have been made to the
standard libraries. Among them are a set of buffer-management routines,
some new string-handling routines, improved I/O error reporting, and
additional debugging support routines. The proposed ANSI standard specifies
a basic set of system-level and external routines.
Microsoft C, Version 4.00
The Microsoft C Compiler, version 4.00, adheres closely to the
proposed ANSI standard. I will briefly describe the compiler system and its
features, to set the stage for the programs that follow. Appendix A
provides a detailed description.
The Microsoft C Compiler is a three-pass optimizing compiler. The
passes are named C1, C2, and C3. These passes could be called directly, but
in practice they should not be. We use one of the control programs MSC and
CL to invoke them for us. MSC is the primary compiler control program. With
MSC, the linker must be called separately to combine program object files
and library modules into an executable program. CL is similar in operation
to the UNIX/XENIX C compiler control program cc. It invokes both the
compiler passes and the linker, as needed, to produce an executable
program.
The Microsoft C Compiler uses DOS environment variables to locate
libraries and header files in special disk directories. These are normally
set to \lib and \include, respectively. The compiler will use a designated
temporary area on disk if the variable TMP is defined. The temporary area
can be a virtual disk to speed disk-intensive processing.
If the machine used at run time has a math coprocessor, the
coprocessor is used automatically. Floating-point emulation is used if no
coprocessor is installed. Thus, it is not necessary to have separately
compiled versions of the same program for differently configured
systems.
The material in this book was developed on personal computers running
MS-DOS and the Microsoft C Compiler, version 4.00. But many of the programs
presented here can be moved with little or no change to other hardware,
other operating systems, and other C compilers. Appendix B describes
several other major C compilers and how they can be used to compile the
programs in this book.
The primary areas that preclude unqualified portability to other
environments are related to the occasional use of special keys on the
PCkeyboard, to screen updating via BIOS and direct-access routines, and to
the use of other hardware elements of the PC, such as built-in timers and
reserved memory areas. Where possible, dependencies on PC hardware have
been avoided. One of the goals of the book is to show off some of the
marvelous capabilities of the PC and the Microsoft C Compiler, so by
design, some of the material is not portable to other environments without
some changes.
Memory Models and Uses
Microsoft C supports programs up to one megabyte in size. It provides
five primary memory models plus some extensions that let you produce mixed
memory models to meet special needs. The memory models are defined by the
following table:
═══════════════════════════════════════════════════════════════════════════
MODEL SIZES
───────────────────────────────────────────────────────────────────────────
Code Data Arrays
─────────────────────────────────────────────────────────────
small │ 64 KB │ 64 KB │ 64 KB
medium │ 1 MB │ 64 KB │ 64 KB
compact │ 64 KB │ 1 MB │ 64 KB
large │ 1 MB │ 1 MB │ 64 KB
huge │ 1 MB │ 1 MB │ >64 KB
───────────────────────────────────────────────────────────────────────────
Support Tools
When we talk about a C compiler, we usually implicitly include a set
of support tools that make up a working C compiler system. We'll now
examine some of the important support tools that enhance the C programming
environment.
LINK, the DOS Linker Regardless of what language you program in, you
will use the DOS LINK program, the object linker provided with DOS.
Depending upon which version of DOS and which language product you have,
you may have to upgrade to a newer version of the linker. The linker must
understand DOS pathnames and, when used with the Microsoft C Compiler, must
be able to access the values in DOS environment variables that point to the
library and include file directories.
The version of LINK that is used to link the programs in this book is
version 3.51. It was shipped with the version 4.00 C compiler. This version
of LINK provides a single-level overlay-linking capability that permits
memory space to be shared sequentially by several program modules.
Earlier versions of LINK, back to version 3.12, will also work, but they do
not have the overlay capability of the 3.51 version. I chose not to use
overlays in this book, so there is no requirement for an overlay linker.
LIB, the Object-File Library Maintainer A library in the computer
programming context is a collection of compiled or assembled functions. The
functions in a given library are typically related to each other by some
common factor, such as the role they serve in handling one major
programming task. LIB is provided with Microsoft language products. It
allows us to create, organize, and maintain run-time libraries.
The linker calls on various libraries to resolve external names during
a linker session. The linker automatically looks for the standard C library
(slibc.lib, mlibc.lib, and so on) for the appropriate memory model. It
looks in the current directory unless the DOS environment variable LIB
tells it to look elsewhere, or you provide a list of other libraries to
search.
We will make extensive use of the libraries provided with our
compilers, and we will create some libraries of our own. (Our library
strategy is documented in Chapter 2.) Nearly all of the service routines
we write in this book will end up in an object library somewhere. Appendix
D summarizes all object library functions developed in this book.
MAKE, the UNIX-Style Program Maintainer Any repetitive task begs to
be automated. MAKE is an automated program maintainer that emulates the
basic behavior of the UNIX MAKE program. I will present information on the
use of MAKE in building some of the programs in this book. Because my
programs, even some of the simple ones, are usually broken into a number of
small modules, it is important to have a tool that takes most of the
drudgery out of maintaining the programs as changes are made to them during
the development and lifetime maintenance periods.
MAKE performs its magic by applying rules, both built-in and user-
supplied, to keep each object file up to date with its sources. The user-
supplied rules reside in a "makefile" that lists all the modules of a
program; the dependencies of objects on sources, header files, and
libraries; and the recipe for how to connect the pieces. MAKE is equally
able to control documentation projects and other activities that manage
groups of disk files.
The next chapter describes the design and development process espoused
in this book and shows the relationships of the various activities involved
in building a program. MAKE plays an important role in the process.
CodeView, the Symbolic Debugger With the release of version 4.00 of
the Microsoft C Compiler, Microsoft unleashed CodeView, a spectacular
window-oriented, source-level symbolic debugger.
Anyone who has done even a modest amount of programming knows the
sense of frustration that accompanies a bug hunt. A program that should
take a couple of hours to design, write, and test can cost you many hours
of anguish because of a subtle, hard-to-find bug. CodeView has the
debugging capabilities and operating features needed to shrink the
debugging task down to manageable proportions.
The debugger is a logical extension of the DOS DEBUG program and
Microsoft's SYMDEB, so knowledgeable users of those programs will find
CodeView very easy to learn. Using multiple windows, CodeView allows you to
view source code, disassemble object code, and monitor program variables,
CPU registers, and the stack. Figure 1-1 is a sample CodeView screen that
reveals some of the primary features of the debugger.
CodeView works on color or monochrome systems, can handle a 43-line
EGA (enhanced graphics adapter) screen, and supports both mouse and
keyboard user interfaces.
MASM, the Macro Assembler The Microsoft C Compiler produces object
files in the Microsoft object format, so there is no need to run a separate
assembler pass. For information about MASM and ASM, see Advanced MS-DOS by
Ray Duncan, Microsoft Press, 1986.
╔══════════════════════════════════════════╗
║ ║
║ Figure 1-1 is found on page 10 ║
║ in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
FIGURE 1-1 ▐ Sample CodeView screen display
Chapter 2 Program Development
Program development means different things to different people. The
particular interpretation often depends on whether the question is asked of
a programmer who always works independently or one who normally functions
as a member of a team.
Whether done by an independent programmer or a team of programmers,
program development is a blending of art and science and is an iterative
process. For anything but trivial projects, we rarely know exactly how we
will do the job before actually starting to do it. We can usually make some
good guesses, but there is often an element of discovery involved. As long
as something is learned each time through the loop, and as long as mistakes
are not repeated, we will likely make some headway.
The science part is somehow keeping ourselves headed in the right
direction, capturing needed information during the course of a project, and
effectively applying what is learned. The art is in knowing what to try
when all else fails and in being able to conjure up the correct data
structures and algorithms to solve seemingly intractable problems.
There is usually at least one approach to solving a problem that makes
the task easier than do other approaches. Experience and imagination are
your two most important allies in pursuit of this approach. The experience
need not be all yours, either--try to borrow existing ideas and adapt them
to your purpose if there is a reasonable fit. And be pleased to have other
programmers borrow your ideas--it's a sure sign of success.
Guiding Principles
During the past 10 years or so, I have had the good fortune to be both
an observer and a participant on solo and group programming efforts. As a
technical writer and publications supervisor, I was able to see the good,
the bad, and the ugly aspects of very large programming efforts. As a
developer, I saw firsthand how the development process can be made to work
well and how it can be made to fail pitifully.
The consistent and careful application of structured design and the
incremental development of both programs and the documents that describe
them are critical factors in the success of programming projects. I suspect
that the exact method used has less bearing on the outcome of a project
than does the fact that the participants consciously attempt to control
what is going on rather than just letting it happen.
This book encourages a heavy dependence on the development of reusable
modules. The concept of creating reusable modules--essentially stockpiling
programming pieces--seems obvious in retrospect, but it was quite a
revelation to a generation of programmers 10 to 15 years ago. The standard
library is a wonderful example of the concept coming to fruition. There is
something elegant about a function that, for example, copies a string onto
the end of another string and returns a pointer to the start of the
resulting string. Variations of this technique pervade the standard library
and give us options in using the library routines that we would not have if
the routines had been designed in a less general way.
The point here is that we should keep an eye focused on the future use
of what we are doing now. First, strive to get something working, but don't
quit too soon. Refine it, generalize it, and make it available for future
use, both by yourself and by others. The extra time spent in this endeavor
will be paid back many times over in saved project time. Also, don't
neglect to document what you do, both in comments within the sources and in
separate manual-style documentation.
The Development Cycle
The first step in the development cycle is determining what must be
done. This is the problem-definition phase and is the time when we
determine the user's needs, investigate the operating environment, and try
to get a feel for the boundary conditions that will influence our
design.
The next phase, program design, is anything but a straightforward
process. We need to keep our minds open to many alternatives. We should
attempt paper designs, perhaps written in pseudocode, which is a human-
language description disguised as program code. Our hope is that at least
one of the paper designs will lead to a solution that can be implemented. A
major objective at this stage is to produce a language-independent solution
to the problem at hand so that we have options later, in terms of choosing
a language that best fits the circumstances.
When we have what we believe is a workable solution, we can select a
language and start coding. I prefer to use C because of its compactness and
versatility, but I sometimes use Pascal, Assembler, or BASIC in my work.
The choice should be based on factors such as the "lingua franca" of the
host environment (local custom), whether any work has already been done on
the problem in a particular language (invested value), and whether any of
the current investment is worth saving (sometimes it isn't). But telling a
hundred assembly-language programmers that from today forward all work will
be in C is potentially risky business. It might be easier to learn yet
another assembly language!
The next phase of the program-development cycle is an important one
that is often overlooked. Testing should be planned right along with
development, and should be done either by the programmer of the item to be
tested or by an associate working closely with the programmer. As soon as
the code is done, there should be a way to exercise it to see that the
program does what it is supposed to do, doesn't do anything it shouldn't,
and protects itself from possible adverse effects of unwanted inputs.
It is not too hard to check a program for correct behavior in the
presence of expected inputs. This is, of course, important. But the
behavior of a program in the face of unexpected inputs is equally
important. Writing a program that defends itself well is a bit of a
challenge and requires a somewhat perverse mentality on the part of the
programmer and the tester.
Checking boundary conditions, handling errors promptly and correctly,
and generally just being on guard for the conditions that could make your
program crumble is something that requires discipline and practice. Be sure
to check the return codes from routines that flag errors. Standard library
I/O routines are particularly good about telling you when something goes
wrong (full disk, bad filename, and so forth). Don't let this vital
information fall on the floor.
As I said earlier, the program-development process is an iterative
one. At any point in the process, don't be afraid to pull the plug and
start over if you find yourself traveling the wrong path. If necessary, go
to completion, refine the original definition, and do the job again until
it's right. You'll be glad you did.
Our Local Environment
For the purposes of our development work in this book, we will set up
some directories for header files and libraries. The hardware and software
assumptions listed in the Introduction apply. We will use the Microsoft
recommendations for the standard run-time libraries and supplied header-
file locations. Any header and library files that we create will be placed
in the new directories \include\local and \lib\local, respectively. This
practice precludes one of our homegrown files from clobbering one of the
files provided with the compiler if we accidentally use the same name. It
also collects all of our own files together for convenient access.
We will be preparing several header files as we go on through this
book. For now, we'll start with the file std.h, which contains some basic
information needed by many of our C source files.
In addition to defining some often-used constants, std.h contains a
definition of a Boolean data type (using the enum type specifier) and some
aliases for standard data types that have long names (unsigned short, for
example). I used to use these often, but as my typing speed increased, I
began to use the original names. Feel free to use whatever you find most
natural (or easiest to type).
We can use the preprocessor to retrieve our local header files in a
way that is consistent with the approach used for standard header files.
The preprocessor directive
#include <local\std.h>
tells the preprocessor to read in the text of the file \include\local\std.h
because we previously set the DOS environment variable INCLUDE to
\include.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 15 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
Similarly, our local library directory will eventually contain a
utility library, util.lib, a pair of operating-system interface libraries,
dos.lib and bios.lib, and several other special-purpose libraries. The
linker must always be given the correct name for the directory that
contains the library files. We will arrange for this to be done
automatically by using a makefile that includes pathname specifiers for the
required directories. Then we'll let MAKE do all the hard work. Isn't high
tech wonderful?
Now it's time to do some programming. We begin in Chapter 3 with an
examination of how our C programs interact with the MS-DOS system.
Chapter 3 The DOS-to-C Connection
DOS is, of course, the very popular and widely used operating system
for the IBM PC and other personal computers that have a comparable 16-bit
architecture. Proficient C is designed to help you write C programs in a
DOS environment.
To obtain the greatest benefit from our PCs, we must understand both
the C language and the DOS environment in which we program. In addition, we
would like to exploit the similarities among DOS, UNIX, and XENIX for some
classes of programs, especially those that don't require the use of
specialized hardware or direct screen access.
From the beginning, DOS has had some features in common with UNIX and
its many descendants, including XENIX. Among these similarities are basic
aspects of command syntax (copy from to, rather than the "backward"
approach of CP/M); the use of a common "file" orientation for all data
streams, including the console (keyboard and screen) and the printer; and a
batch-processing capability (.BAT files under DOS and shell scripts under
UNIX). Starting with version 2.00, the operating system acquired many more
features that are reminiscent of those provided by UNIX. Among the new
features were a hierarchical file system and related support for a hard
disk, installable device drivers for customization, a user-controllable
environment, and input/output (I/O) redirection. These important features
added greatly to the power and flexibility of DOS.
In this chapter, we will examine I/O redirection, command-line
processing, and access to the DOS environment. Some aspects of each of
these topics will be involved in the construction of the programs that are
presented in the remainder of this book.
Proficient C makes extensive use of the routines in the C run-time
library. In this chapter, we use only a few of the more common library
routines. A detailed discussion of the C run-time library is presented in
the next chapter (Chapter 4). We defer discussion of device drivers until
Chapter 13.
Input/Output Redirection and Piping
By way of a few simple examples, we will quickly review some of the
basics of input and output and set the stage for considerably more detailed
work using the I/O features of the C run-time library, a marvelous
collection of prepackaged routines.
Program I/O is based on the concept of standard streams: standard
input (stdin), standard output (stdout), and standard error (stderr). The
stdin stream is usually "connected" to the keyboard, and both the stdout
and stderr are usually connected to the screen. Under DOS, both stdin and
stdout can be redirected to other devices, but stderr always goes to the
screen.
Because all references to I/O devices are treated like files under
DOS, we can make ad hoc changes in the source or destination of data being
processed by a program. The output of the DIR command, for example,
normally goes directly to the screen (stdout). But with a minor change to
the command line, we can send the directory listing directly to a printer
by redirecting the DIR command's standard output. The command
dir > prn
tells DOS to feed the data that DIR normally sends to the console screen to
the default printer device instead. Thus, the standard output of DIR is
redirected.
We can similarly redirect input to a command. The DOS MORE command is
a good example. MORE is classified in the DOS manual as a filter--a program
that is designed to read from the standard input device, perform a
transformation of some kind on that input, and produce some output on the
standard output device. The transformation performed by MORE is to parcel
out the input it receives in screen-sized chunks. MOREcan be used as the
termination of a pipeline--a series of programs connected to each other by
a pipe, symbolized by the vertical bar (|). The DOS pipe is actually
implemented as a pair of redirections. The output of one program is
redirected to a specially named disk file, which then becomes the input
to the next program in the pipeline.
┌────────────────────┐ ┌─────────────┐
│ ┌────────────────┐ │ │ │
│ │ │ │ stdout │ │
│ │ ├─┤◄────────────────────────────┤ │
│ │ screen ├─┤ stderr │ │
│ │ │.│◄────────────────────────────┤ │
│ └────────────────┘▓│ │ │
└────────────────────┘ │ │
│ program │
│ │
│ │
┌───────────────────────────────┐ │ │
│┌─┬────────┬────────┬─┐ │ │ │
││▒│▒▒▒▒▒▒▒▒│▒▒▒▒▒▒▒▒│▒│ ┌────┐│ │ │
││ │ │ ││ stdin │ │
││▒ keyboard ▒▒▒ └┐ │▒ │├────────────────►│ │
│└┐▒▒▒ ▒┌┐▒┌┘ │ ▒▒ ││ │ │
│ └─────────────────┘└─┘ └────┘│ │ │
└───────────────────────────────┘ └─────────────┘
FIGURE 3-1 ▐ Default standard I/O streams
The ability to join individual programs into custom-designed
collections via the pipe mechanism is an important consideration in the
design of an operating system. We can easily plug the output of one program
into the input of another to perform special processing. To control the
output of the TYPE command, for example, we issue the following
command:
type myfile.txt | more
This command will display no more than a full screen of text at a time,
allowing the user to page through the text at a leisurely pace rather than
see the text flash by and scroll off the screen before it can be read.
The same effect can be achieved by redirecting input to MORE using the
command
more < myfile.txt
We have redirected the standard input of MORE in this case.
To illustrate the use of redirection and piping techniques, here
is a program called CP, which is a highly simplified version of the DOS
COPY command. It is written as a filter in that it copies its standard
input (stdin) to its standard output (stdout) one character at a time.
Strictly speaking, however, CP is not a filter, because it simply passes
its input to its output without modification.
/*
* cp -- a simplified copy command
*/
#include <stdio.h>
main()
{
int ch;
while ((ch = getc(stdin)) != EOF)
putc(ch, stdout);
exit(0);
}
This program should look familiar to anyone who has studied C
programming at any level. Although very simple, CP allows us to copy a file
from one place in a file system to another with ease. It has some serious
limitations that we will address as we proceed through this chapter--for
one thing, CP does no error checking, so it depends on DOS to do that work
on its behalf. In addition, CP takes no filenames on the command line, so
all input must be via redirection, which requires extra typing.
CP simply gathers a single character at a time from the standard input
and checks to see whether it is the EOF (end-of-file) indicator. If the
input is not EOF, it is immediately sent to the standard output. If EOF is
detected, the processing loop terminates, and so does the program.
EOF is defined in stdio.h as -1. This is why the character-storage
location ch is declared to be an int instead of a char data type. Since
char can be signed or unsigned (depending on system implementation), we
must always be prepared to detect the EOF flag to terminate the copying
process.
The getc() and putc() library routines are implemented as macros and
are defined in stdio.h. They are fast and use the standard I/O library
user-buffering scheme. A buffer of BUFSIZ bytes (512 bytes in the Microsoft
C implementation) is used for reading and writing operations. When getc()
attempts to read past the end of an input stream, it returns EOF.
There are several ways to use CP. Simply typing CP on the DOS command
line causes the program to take input from the keyboard and display a copy
of it on the console screen. You will see the text you type twice, once as
you type it, because DOS is echoing your input, and a second time after you
type a carriage return to end your input. User input may be terminated by
typing Ctrl-Z followed by a carriage return.
C>cp
This is a line of text.
This is a line of text.
^Z
C>
To copy the contents of a file to the screen, you type the
command
CP < filename
just as you would with the MORE command. To create a new file (or update an
existing file), you can type
CP >> newfile
and CP will copy your keyboard input into newfile until you type Ctrl-Z
(the DOS end-of-file marker).
Although this simple program is useful, it would be much more
versatile if it could accept optional parameters on the invocation
commandline, such as the names of files to process, and control information
to tailor the processing to meet user needs. The next section explores the
use of command arguments and applies them in several demonstration
programs.
Command-line Processing
A program's invocation command line may contain optional arguments in
addition to the program name. The main() function of a C program is usually
written in such a way as to look first for optional arguments and then for
filenames or other parameters.
Under UNIX, the convention is to start optional arguments with a
leading dash. DOS, however, uses a forward slash (/) to specify options, or
what are commonly referred to as switches. (UNIX uses the forward slash as
the pathname separator. Since DOS was not originally so close in spirit to
UNIX as it is now, DOS's use of the forward slash as a switch flag is
forgivable. However, this choice made it necessary to use something else
for a separator in pathnames when they were introduced in DOS version 2.00,
so DOS designers decided to use the backslash (\) as the pathname
separator.) I have elected to use the leading dash as an option flag, but
you can easily modify the code to allow the use of /, or to accept either
form, as is done in the Microsoft C Compiler programs.
Arguments are passed to our programs by the C run-time startup
function. A count of arguments, argc, and an array of pointers to character
strings, argv, are passed as arguments to main() (along with envp, which
we'll look at in detail shortly). If a program needs to access the
arguments, it must include argc and argv in the definition of its main()
function:
main(argc, argv)
int argc;
char *argv[];
{
/* function body */
}
Figure 3-2 depicts how the command line and its components
are managed in memory. The command line can be thought of as having two
components: the command name itself (available within C programs only under
DOS version 3.00 and later) and the command "tail," which may contain
optional arguments such as filenames or input data. Only the command-name
component is required by DOS. The command-line text, if present, is
obtained from the DOS command-line tail. Information in the tail may be
optional or required, based on the design of the program being
executed.
main (argc, argv)
argc program name
┌─────┐ ┌─────┬─────┬─────┬─────┬─────┐
│ 3 │ │ P │ R │ O │ G │ \0 │ (DOS 3.00
│ │ │ │ │ │ │ │ and later)
└─────┘ ─────┴─────┴─────┴─────┴─────┘
│
│ command-line arguments
argv │ ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐
┌─────┐ │ │ A │ R │ G │ 1 │ \0 │ A │ R │ G │ 2 │ \0 │
0│ ■──┼──┘ │ │ │ │ │ │ │ │ │ │ │
│ │ ─────┴─────┴─────┴─────┴──────────┴─────┴─────┴─────┴─────┘
├─────┤ │ │
1│ ■──┼─────┘ │
│ │ │
├─────┤ │
2│ ■──┼───────────────────────────────────┘
│ │
├─────┤
3│ ■ │ NULL (per proposed ANSI C standard, but not done by
│ │ all C compilers--don't depend on it yet)
└─────┘
FIGURE 3-2 ▐ Command-line arguments
Note the declaration of argv in the template for the main() function
shown above. The declaration
char *argv[];
says that argv is an "array of type pointer to type char." This declaration
may also be written as
char **argv;
which says that argv is a "pointer to a pointer to type char." The net
result is the same, but these are not identical declarations.
Program Name
The name used to invoke a program under DOS version 3.00 and later is
available to the program as argv[0] (or *argv, if the pointer has not been
moved). Earlier versions of DOS do not save the name used to invoke a
program, so most C compilers for DOS substitute a dummy value, such as a
null string or c. Being able to retrieve the invoking name can be very
useful at times, so if our programs detect the use of DOS version 3.00 or
later, we will let them use the name to their advantage.
How can the name be used? For one thing, when many programmers are
producing programs in diverse locations for the same family of machines and
operating systems, it is likely that the same name will be used for two or
more programs that come into general distribution. We might like to be able
to rename one of two identically named commands to avoid problems. This is
easy to do if we have the source code, but it might not work if all we have
is the executable program. Moreover, some programs (usually copy-protected
programs) will not run if called by a different name. Another problem
occurs when a program is renamed because, in most cases, the program name
is hard-coded into error and help messages. So if we renamed CP.EXE to
CPY.EXE, we might still get an error message like cp: can't open file.txt,
which isn't nearly as useful as one that displays the currently used
program name.
In Chapter 7, we'll find other uses for the command name. And
throughout the programs in this book, where it makes sense to do so, we
will try to obtain the invoking program name for use in error and help
messages.
Ambiguous Filenames
Microsoft C provides a set of functions to handle the processing of
wildcard characters in the formation of ambiguous filenames. Four object
files are provided, one for each major memory model, and they may be placed
in the default library directory for convenient access. The ssetargv.obj
file is used with small-model programs; the csetargv.obj, msetargv.obj, and
lsetargv.obj files are used with compact-, medium-, and large-model
programs, respectively.
The setargv() function expands wildcard characters in the command-line
tail according to DOS rules (see the DOS manual for details) and passes
them to the program in the form of an argument list. It is then up to the
program to sift through the argument list it receives and separate optional
arguments from filename arguments.
Accessing the Command Line
The following program shows how to access command-line arguments,
including the program name. The REPLAY program first assumes that it will
be called by the name replay but, just in case, it checks to see which
version of DOS it is running under. If the DOS major number, obtained from
the _osmajor global variable defined in stdlib.h, has a value of at least
3, REPLAY calls getpname() to extract the filename part of the full
pathname passed by DOS. If the user renames REPLAY.EXE to something else--
say ARGLIST.EXE--then the program will issue the correct name when it
displays the banner line just before displaying the arguments, if any,
found on the command line.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 25 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
Notice a few things in this demonstration program. We test for the
presence of arguments (if (argc == 1)) and quit with a nonzero exit code if
there are none. An argument count of 1 means that only the command name was
typed on the DOS command line. This simple precaution prevents us from
printing a banner line and nothing more, which would look kind of
silly.
The temporary pointer p is declared as a pointer to a pointer to a
character and is assigned the initial value of argv. When we enter the
loop, argv is incremented so that it points past the command-name string to
the first optional argument. Each time through the loop, argv gets
incremented, so argv - p in the body of the loop yields the number of the
current argument and *argv yields the argument string. We could just as
easily have incremented p while leaving argv stationary, which would
require the argument-number calculation to be reversed (p - argv).
The pgm character array is large enough to hold an eight-character
name plus a terminating null character. It must be declared static so that
it can be initialized to the expected name. We put a name in the array so
that if none is available from the operating system, we'll still have one
to work with--although there is a small risk that it may be the wrong one,
if someone has changed the program name.
If _osmajor is 3 or more, we call getpname() to grab the base program
name out of the pathname string, which may be anything from a simple
filename to a full or relative pathname, complete with drive specifier and
extension. (DOS allows pathnames for programs in version 2.00 and later.
For the purposes of this book, earlier versions of DOS are considered to be
obsolete.)
Here is the code for getpname():
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 26 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
The program-name pointer, pname, accesses an array in the calling
function that is large enough to hold the filename part (eight characters)
of a valid DOS filespec, plus the terminating null byte.
The getpname() function receives the full pathname of a file (path),
immediately sets cp to the start of path, finds the terminating null byte,
and backs up one position. It then moves backward to the start of the
filename, which is marked by the beginning of path if the name is a simple
filename, or by one of the accepted separators (:, \, or /). Notice that
the backslash has to be escaped (\\) to turn off its special meaning. The
last thing getpname() does is copy the filename part to pname, stopping
when it sees a dot or a null byte. (It is customary in programming to refer
to a single . as a dot.)
The getpname() function is one we will use often, so it should be put
in a place where it can be retrieved easily. We will now start accumulating
useful object modules in a library of our own called util.lib, which will
reside in a directory called \lib\local on the default disk drive. (Users
of other compilers may need to make some adjustments if their compilers do
not allow this.) The function is first compiled using the command
msc getpname;
and then the object file, getpname.obj, is added to the utility library by
typing
lib \lib\local\util +getpname;
Any time we need to use this function, we add the library path to the list
of libraries to be searched by LINK.
We can automate the process by using MAKE with a script in a makefile
routine such as the following one I assembled for the REPLAY program (the
use of the MAKE program maintainer is explained in Chapter 1):
# makefile for replay program
LIB=c:\lib
LLIB=c:\lib\local
replay.obj: replay.c
msc $*;
replay.exe: replay.obj $(LLIB)\util.lib
link $* $(LIB)\ssetargv $(LLIB)\util;
This makefile, called replay.mk, is invoked using the command
make replay.mk
The MAKE program will take care of the details of recompiling objects and
relinking the needed program modules to produce a new executable program.
This example is the paradigm for all the programs that follow. Simple
programs will be presented with specific compile and link descriptions; I
will present makefiles for the more complex programs in later chapters.
Pointers and Indirection
The source code presented thus far uses quite a few pointers. Many
programmers who are new to C language (and, for that matter, quite a few
who are not so new to it) have problems with the declaration and
application of pointers. Indeed, I have seen entire books written about C
programming that sidestep the issue of pointers by using indexed arrays
almost exclusively. That may seem a good way to minimize the difficulty one
experiences in learning C, but it robs the programmer of one of C's most
powerful capabilities and is really an unnecessary limitation.
If you are having problems with pointers, try to visualize a pointer
declaration in the following way. (We'll use a pointer to a character here
because it is commonly encountered.) The line
char *cp;
declares a character pointer. Place your finger tip over the cp part of the
declaration and see what's left (char *). Read this as "cp is a pointer
(the *) to a character storage location (the char)." In an expression,
then, the unadorned cp yields a storage location that holds a pointer to
(the address of) a character. If, however, you cover the *cp grouping, you
are left with the type char. This says that when you see *cp in an
expression, it yields a character storage location. You can retrieve the
value stored there and may assign a character value into the storage
location.
Look back through the source for getpname(), which is loaded with
examples of the variable cp used in both contexts. Once you feel
comfortable with single indirection, as this is called, try the same thing
with double indirection, such as you encounter with the declaration of the
argument vector:
char **argv;
The illustration in Figure 3-3 on the next page may help to clarify this
a bit. If it doesn't come easily to you, don't give up on it. A little
practice will make it clear. You have to get a picture in mind of what's
happening.
In Chapter 6, we will examine user/machine interfaces further. We will
introduce a variety of ways of handling command-line arguments and talk
about getopt(), an AT&T library routine that attempts to standardize
command-line syntax.
SINGLE INDIRECTION:
┌──────────────┐ ┌────┬────┬────┬────┬────┬────┬────┬────┐
│ ■───────┼─►▒▒▒▒│▒▒▒▒│▒▒▒▒│▒▒▒▒│▒▒▒▒│▒▒▒▒│▒▒▒▒│▒▒▒▒│
└──────────────┘ └────┴────┴────┴────┴────┴────┴────┴────┘
char *s;
░► s yields a pointer to a char
▓▓► *s yields a char
DOUBLE INDIRECTION:
┌──────────────┐ ┌──────────────┐ ┌────┬────┬────┬────┬────┬────┬────┬────┐
│ ■───────┼─► ■───────┼─►▒▒▒▒│▒▒▒▒│▒▒▒▒│▒▒▒▒│▒▒▒▒│▒▒▒▒│▒▒▒▒│▒▒▒▒│
└──────────────┘ └──────────────┘ └────┴────┴────┴────┴────┴────┴────┴────┘
char **s;
░► s yields a pointer to a pointer to a char
▓▓► *s yields a pointer to a char
▒▒▒► ** yields a char
FIGURE 3-3 ▐ Pointers and indirection
DOS Environment Variables
Another way to get data into a program or to modify a program's
behavior is by using variables that are maintained in the DOS environment.
To see which variables are in the environment and what their values are,
type
set
at the DOS prompt and view the listing. All environments contain the values
of COMSPEC and PATH. On my system, the environment list looks like
this:
COMSPEC=C:\COMMAND.COM
PATH=C:\DOS;C:\BIN;C:\PWP;C:\WP;
PROMPT=$p$g
PWPLIB=c:\pwp
INCLUDE=c:\include
LIB=c:\lib
TMP=c:\
CONFIG=c:\config
FGND=cyan
BKGND=blue
BORDER=blue
From the DOS command line or from within a batch file, a user can add
variables to the operating environment by using the SET command. The
command
set var=val
instructs DOS to add the variable var to the environment and assign it the
value val. Within a batch file, the value of the DOS environment variable
var can be obtained by using the construct %var%. The following batch file,
SHOW.BAT, does this to display the values set for foreground and background
colors (see Chapter 13 to find out how these values can be used):
echo off
echo FGND = %FGND%
echo BKGND = %BKGND%
The initial echo off is used to silence DOS, which displays each line
in a batch file before processing it, unless it's told not to. (The default
really ought to be echoing off, and many enterprising programmers have
patched COMMAND.COM for each version of DOS to disable this echoing.)
COMMENT
The amount of environment space reserved by DOS (prior
to version 3.2) is only 160 bytes. You may want to
expand the default space by using SETENV, a utility
program supplied with version 4.00 of the Microsoft C
Compiler. Some other commercial software products
include a program called ENVSIZE, or something similar,
but not all C compilers have such a utility.
We want to be able to selectively read DOS environment variables into
our C programs, and we may even want to change them during the execution of
our programs. The library functions getenv() and putenv() allow us to do
both of these tasks. The functions use an environment pointer to gain
access to the DOS environment.
The Environment Pointer
Access to the DOS environment is obtained through the global variable
environ, or the main() function argument envp. The header file stdlib.h
declares the environment pointer as
extern char **environ
Thus, environ is an array of pointers to environment strings.
To add a third argument to main() to obtain a pointer to the
environment, you would use the following form:
main(argc, argv, envp)
int argc;
char *argv[];
char *envp[];
{
. . .
}
If envp is declared as an argument to main(), both argc and argv also must
be declared because of the way C function arguments are processed. Note
that the declarations for argv and envp could be written
char **argv;
char **envp;
because these are functionally identical to the previous
declarations.
The envp argument to main() is simply a copy of environ at the time
the program began execution. If subsequent changes are made to the
environment, the global environ variable is kept current, but the value of
envp, which is local to main(), is not.
Reading from the DOS Environment To read the value of a DOS variable
into a C program, use the getenv() function, which returns a pointer to a
character string if the variable is defined, or returns NULL if it is
not.
The following test program, SHOWENV, accepts a list of strings from
the user and checks each in turn to see if it is the name of a currently
defined environment variable. If it is, SHOWENV displays the string and its
value. If not, SHOWENV displays the message "var not defined" and moves on
to the next string, if any. If no arguments are given to the command,
SHOWENV prints out the entire environment list.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 33 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
The library function strupr() is applied to the string being
processed, because DOS always converts the variable name to uppercase
letters before saving it in the environment. If strupr() were not used, no
match would be found if the user typed the string arguments in lowercase
letters.
Writing to the DOS Environment The process of modifying the DOS
environment list is analogous to reading it, except that the putenv()
function is called. A return value of 0 indicates success and -1 indicates
failure, usually due to a lack of memory in which to write the modified
environment. The program SETMYDIR attempts to add the variable MYDIR to the
DOS environment by calling setenv().
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 34 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
Please note that putenv() modifies the environment in which the
current process is running (the environment inherited is a copy from DOS),
but it does not change the original DOS environment. To observe this,
compile the SETMYDIR program. Then run a SET command to list the starting
environment (assuming you don't already have a MYDIR variable), run the
SETMYDIR program, then run the SET command again. You'll see the MYDIR
variable come into existence while the program is running, then it will
disappear when the program stops executing.
CAUTION
Be careful to assign values into the environment
correctly. The form of the SET command is rigid. No
white space (space, tab) is permitted on either side of
the equal sign. If spaces or tabs are inserted, neither
getenv() nor the %var% batch-file mechanism will
recognize that the variable is defined.
The ability to modify the environment passed to a running program is
important. A program can, for example, set up its own private PATH so that
it can access its own set of commands while running, without disturbing the
user's normal environment, which is restored when the program terminates.
That's about it for the DOS-to-C connection via command lines and
environment variables. We'll be using these connections extensively in our
programs, so spend some time getting familiar with the techniques and
routines described in this chapter.
In the next chapter we will take a much closer look at some of the
standard library routines that are part of the run-time library of the C
compiler package.
SECTION II Standard Libraries and Interfaces
Chapter 4 Using Standard Libraries
C has no built-in input/output capabilities; all I/O operations are
handled by external functions. Neither does C have any built-in
stringhandling facilities or mathematical functions. This is not a
deficiency, but rather it is a benefit that permits each C compiler
implementation to use features and capabilities of the host hardware and
operating system, while presenting the same external interface to programs.
All such facilities are external to C and are handled by routines
(functions and macros) that typically reside in special libraries, which
are collections of often-used routines.
The UNIX heritage of C has produced a large body of ready-to-use
routines that have successfully made the trip to DOS despite some
significant differences in the underlying operating systems and their
supporting environments. The routines mask the differences by providing a
clean, consistent, and fairly well-documented interface to programs.
In this chapter, we will explore a small selection of the hundreds of
routines in the standard C run-time library and use them to construct some
simple but useful programs. Other library routines will be introduced in
later chapters as the need for them arises.
Why Use Libraries?
Simply put, the standard libraries give us a reasonable degree of
program portability, provide consistent and predictable interfaces, and,
most important, reduce programming effort. Out of the millions of lines of
C code that have been written over the years, certain operations have been
programmed over and over again. Just a few obvious examples include:
opening and closing files; reading and writing characters; detecting errors
and displaying relevant messages; splitting, concatenating, and copying
strings; and managing a hierarchy of directories and files.
In the standard libraries, many of these operations have been highly
refined for speed, flexibility, and applicability to a wide range of
situations. I prefer to use something that exists, if it works, rather than
start from ground zero. Therefore, this book will concentrate on using the
run-time library provided with the compiler wherever the library routines
will do the job. We will develop mid-level and high-level routines that
depend on standard libraries to carry out the low-level work. In all but a
few special cases, we will avoid creating new low-level functions--it is
simply not necessary most of the time.
The portability issue is often the basis of religious debate,
something I wish to avoid because it is a waste of valuable time and
energy. The portability I strive for in the C programs in this book is that
needed to move a program among the various MS-DOS and PC-DOS systems, to
extensions of the DOS environment such as Windows, and to a lesser degree,
to XENIX and other UNIX-like systems. In some cases, I will sacrifice
portability to achieve a high level of performance under raw DOS because
that's what will "sell" best for a given application.
To go for complete DOS/UNIX transparency requires that some of the
nicer features of a PC must be forsaken so that programs will run on any
terminal type that can be connected to a multi-user system. In particular,
this usually means saying good-bye to the use of function keys, arrow keys,
attractive line-drawing characters, and fast screen updates. Going for the
best performance on a PC usually means sacrificing portability to UNIX for
such screen-intensive applications as a programmer's editor.
However, careful design can minimize the effects of most of the
differences between DOS and UNIX. The use of standard libraries goes a long
way toward ensuring DOS/UNIX portability because of the effort Microsoft
and other C compiler vendors have placed on providing compatible libraries.
The differences among vendors' libraries usually occur in PCspecific low-
level interfaces. Although I will discourage the use of such PC-specific
functions in programs designed for general use, they are the subject of
Chapter 5 and will be used in some of our programs.
Exception Handling
Some days, things just don't seem to go right. Ever had one of those?
Well, programs have days like that, too. As programmers, we have a
responsibility to our programs to build in some way of dealing with
unexpected events. This is called exception handling and is an essential
component in the development of commercial-quality programs.
Exception handling has two parts: detection and recovery. The standard
library functions attempt to detect errors and inform calling functions
about them. It is up to us to build in the appropriate recovery procedures,
even if that simply means giving up on a task, which is sometimes all we
can do.
One of the more common error conditions is the absence of a needed
file. The fopen() function, for example, tries to open a named file. If
asked to open a nonexistent file for reading, fopen() returns NULL instead
of a valid file pointer. What's the appropriate response to this error
condition? At the very least, we may want to inform the user about the
missing file. If the error occurs in a loop that is processing a list of
files, we may choose to print a brief but informative message ("file XYZ.C
not found") to stderr before moving on to the next name in the list. In
other circumstances, it may be best to print the message and terminate
processing with an ERRORLEVEL (exit code), to tell DOS that something is
wrong.
Standard Library Error Functions
A set of standard library functions assists us with exception
handling. A global variable, errno, holds the number of the most recent
error. This number is used as an index into an array of error messages,
sys_errlist. (The message associated with error number 0 is "Error 0," a
catchall for errors not described elsewhere.) Rather than access errno and
sys_errlist directly, we can call on perror() to print the error message to
the standard error output. The manual page summary of perror() is:
#include <stdlib.h>
void perror(string);
char *string;
where void indicates that there is no return value and string is a pointer
to a message that we provide.
COMMENT
Compilers that do not adhere completely to the proposed
ANSI C standard may not support the void data type (or
lack of type, in this case) for functions. It may be
necessary to use the following typedef to compensate
for this deficiency:
typedef int void;
Calls to
void functions will actually return an integer that can
safely be ignored.
The output of perror() is the string we provided, followed by a colon,
a space, the text of the system message for the most recent error, and a
trailing newline, all sent to stderr. Thus, the statement
perror("File error");
if triggered by an attempt to open a nonexistent file for reading, produces
the following output on the screen:
File error: No such file or directory
Appendix A of the Microsoft C Compiler Library Reference Manual lists
the errno symbolic constants used under MS-DOS, the message text for each,
and a description of each error. The DOS error messages are necessarily a
subset of the error messages used by UNIX and XENIX. See your own
C compiler library documentation for a detailed description of each error
and message.
Ambiguous Return Values Two macros, ferror() and feof(), are used to
resolve an ambiguity that is produced by certain standard library routines:
Because they are designed to return a pointer to a character, functions
like fgets() (which we will use in an example shortly) must return NULL to
indicate either that an error has occurred or that the end of the file has
been reached. We need to take additional steps to determine which condition
caused the NULL return value before we take any action.
The manual page summary of ferror() is
#include <stdio.h>
int ferror(stream);
FILE *stream;
where stream must be the file or stream associated with the NULLreturning
routine. The summary for feof() is the same except for the macro name. The
ferror() macro returns a nonzero value to indicate that an error condition
exists and feof() returns a nonzero value if the end of the file has been
reached, thus resolving the ambiguity of the NULL value.
The following code fragment illustrates a way of handling the
ambiguous return:
.
.
.
errcount = 0;
while ((s = fgets(line, BUFSIZ, fp)) != NULL) {
/* process the line */
.
.
.
}
if (ferror(fp)) {
perror("Read error");
clearerr(fp);
if (++errcount >= MAX_ERR) {
fprintf(stderr, "Too many errors\n");
exit(1);
}
}
.
.
.
Notice the use of clearerr() in the preceding fragment. Once an error
occurs in a stream, the error indication remains until the stream is closed
or until the indication is intentionally cleared by using either clearerr()
or rewind(). In the foregoing example, we clear the error indication and
allow some maximum number of errors (MAX_ERR) before giving up. On the
other hand, if the NULL is the result of having reached the end of the
file, we fall through to other processing.
A common programming procedure is to display an error message and then
call exit() to flush output buffers and return to the operating system with
an exit code that indicates an abnormal termination. For convenience, we
will package these statements into a function called fatal(), which
displays the program name, the message text, the system error message, and
a newline. This will usually provide enough information to the user to help
determine the cause of the abnormal exit.
/* fatal -- issue a diagnostic message and terminate */
#include <stdio.h>
#include <stdlib.h>
void fatal(pname, mesg, errlvl)
char *pname; /* program name */
char *mesg; /* message text */
int errlvl; /* errorlevel (exit code) */
{
fputs(pname, stderr); /* display error message */
fputc(':', stderr);
fputc(' ', stderr);
fputs(mesg, stderr);
fputs('\n', stderr);
/* return to DOS with the specified errorlevel */
exit(errlvl);
}
The fatal() function doesn't return to the calling function, hence the
void return type. The errlvl value should be nonzero--a small positive
integer--to indicate an abnormal termination. The specific values used for
errlvl should be documented in a manual page for the program.
Operating System Interrupts Another type of exception is an interrupt
from the operating system, usually caused by the user pressing Ctrl-Break
or Ctrl-C. The library function signal() can be used to control how such
interrupts are handled. The summary for signal() is
#include <signal.h>
int (*signal(sig, func))();
int sig;
int (*func)();
This bizarre-looking declaration is not a misprint. The line
int (*func)();
declares a pointer to a function that returns an integer. This is not the
same as
int *func();
which declares a function that returns a pointer to an integer. The
declaration of signal() also is a pointer to a function that returns an
integer.
The only value for sig permitted under DOS is SIGINT, #defined in the
signal.h header file. SIGINT is the DOS Ctrl-Break interrupt. Other
signals, including SIGHUP (hangup) and others that apply under the
multitasking UNIX and XENIX operating system, currently have no meaning
under DOS.
One of three responses to the interrupt can be specified in the func
argument. A value of SIG_IGN causes the interrupt to be ignored. We might
choose to ignore the DOS Ctrl-Break interrupt during all or a portion of
the lifetime of a program to prevent the user from aborting some critical
operation. For example, it is unwise to let the user break out of a text
editor without first presenting the opportunity of saving changes that were
made to the editing buffer--it could be that the user was fumbling for,
say, Ctrl-D to move the cursor right and hit Ctrl-C by mistake. I know,
I've done it myself!
Second, a func value of SIG_DFL causes the running program to abort
upon receipt of the interrupt, closing all open files and returning control
to DOS. Buffers are not flushed. This is usually what will happen if you
don't use signal(), unless you are using DOS functions that ignore Ctrl-
Break by design (see Chapter 5).
A third type of response is to provide a pointer to a signal-handling
function. This is referred to as signal catching and is more of an art than
a science. The safest method of signal catching simply cleans up any work
in progress and makes a graceful exit. It either accepts the default
treatment or, in a few cases, simply ignores the interrupt.
A return value of -1 from signal() flags an error, errno = EINVAL,
which says that the sig argument is invalid. This identifies a programming
problem that should be corrected before any user ever sees the
program.
Time Functions
The standard library provides several routines for retrieving and
converting time values. The UNIX ctime subroutine package and the time()
system call are available as functions in the Microsoft C run-time library
for DOS. Because these functions have received very little attention in
books about DOS, we will give them a moment in the spotlight.
The time() function returns the number of seconds that have elapsed
since 00:00:00 GMT (Greenwich mean time) on January 1, 1970. This date and
time is called the epoch. GMT is now officially called Universal time (UT),
but the use of the term GMT is likely to continue indefinitely, just as
core is still used to mean main memory, even if it's all solid state. File-
modification times and other times and dates that are visible to the user
are presented as local time values.
The declaration of time() is
#include <time.h>
long time(timeptr);
long *timeptr; /* (usually NULL) */
The time() function takes a pointer to long as an argument and produces a
long return value. The argument can always be the NULL pointer. Before C
had a long data type, it was necessary to provide a suitable buffer for the
time value. Passing the address of the buffer synthesized a "call by
reference" (C uses "call by value" for parameter passing). Now the
application of time() simply assigns the return value to a long
integer.
Time Conversions
The long time value is wonderful for the computer, but we humans don't
often tell someone what time it is as a number of seconds since the epoch.
A more convenient notation for us is a character string that spells out, at
least as abbreviations, what the date and time are. We need some convenient
way to make a conversion from the long value to a character string. We also
have to deal with Universal time and local time and with the complications
of daylight saving time.
Figure 4-1 shows the relationships of the time() function
and the functions that constitute the ctime subroutine package. Two
functions, gmtime() and localtime(), can be used to convert values
from the long result of time() to time structure pointers. The DOS time
structure template, struct tm, defines a set of variables of type int that
can hold hour, minute, second, month, day, and year values, plus a flag for
daylight saving time and numbers for day-of-week and day-of-month
calculations. This time structure template is defined in the file time.h.
RELATIONSHIPS:
┌──────────┐
┌─────► ├──────────────────────────┐
│ │ ctime() │ │
│ └──────────┘ │
│ │
┌────────┐ ┌──────┐ ┌───┴───┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─▼──────┐
│▒system▒│ │ │ │▒▒▒▒▒▒▒├─► ├─►▒▒▒▒▒▒▒▒▒├─► ├─►▒▒▒▒▒▒▒▒│
│▒▒time▒▒│ │time()│ │▒long▒▒│ │gmtime() │ │struct tm│ │asctime()│ │ string │
└────────┘ └──────┘ └───┬───┘ └─────────┘ └────────┘ └─────────┘ └────────┘
│ │
│ ┌───────────┐ │
│ │ │ │
└─────►localtime()├─────┘
└───────────┘
════════════════════════════════════════════════════════════════════════════
┌──┐
LEGEND: ▒▒▒ = data type │ │
▒▒▒ └──┘ = ctime package function
FIGURE 4-1 ▐ Time and date functions and variables
Time and date information in the structure is easy to access and use,
but it is still not suitable for human consumption. Both gmtime() and
localtime() return pointers to a time structure. The declarations for these
functions are
#include <time.h>
struct tm *gmtime(time);
long *time;
struct tm *localtime(time);
long *time;
CAUTION
These two functions use a single statically allocated
structure. Any call to one of these functions,
therefore, will overwrite the result of any previous
call to either function.
While gmtime() always returns a direct conversion of the time value
returned by time(), localtime() does some additional adjustments to
compensate for the difference between GMT and the time zone set into the
DOS environment via the TZ variable. If TZ is not explicitly set (it
usually isn't), then Pacific time is used. I am willing to wager that
nearly every PC running DOS today thinks it is in Redmond, Washington!
We'll see shortly how you can tell your PC where it really is, but
first we need to examine how to convert the time structure data into
strings we can read and understand.
Creating Strings The function asctime() takes a pointer to the time
structure and converts the structure members to a 26-byte string. The
format of the string is
Sat Jul 12 13:05:33 1986\n\0
where \n is a newline character and \0 is the terminating null byte. Each
character should be surrounded by single quotes to show that it is a
character constant. Rather than clutter the displayed string, I'll just ask
you to imagine the quotes there.
The function ctime() provides a shortcut for creating a local time
string. It takes a long time result from time() and returns a pointer to a
character string of the format described above. Its declaration is
#include <time.h>
char *ctime(time);
long *time;
CAUTION
The character string pointed to by ctime() is the same
statically allocated string pointed to by asctime(). A
call to one of these routines destroys the result of
any previous call to either of them.
Time Zones Now on to the subject of time zones. A DOS environment
variable called TZ can be set to reflect the time zone in which a PC is
operating. Adding a line like
set TZ=MST7MDT
to my AUTOEXEC.BAT file tells DOS that my machine is running in the
mountain time zone, that there is a 7-hour difference between mountain
standard time and Greenwich mean time, and that daylight saving time is
honored in this zone. Several other variables are also maintained by the
ctime() subroutine package. They can all be updated by a call to tzset()
(or to asctime(), which calls tzset()). These variables are
═══════════════════════════════════════════════════════════════════════════
VARIABLE MEANING
───────────────────────────────────────────────────────────────────────────
int daylight; │ Daylight savings time flag
long timezone; │ Difference from GMT in seconds
char *tzname[]; │ timezone strings
────────────────────────┴──────────────────────────────────────────────────
If a daylight string is set in the TZ variable, daylight is set to a
nonzero value. The value of timezone is the difference in seconds between
local standard time and GMT. The variable tzname is an array of pointers to
character strings. The first (tzname[0]) is a pointer to the three-letter
string for the standard time zone and the second (tzname[1]) is a pointer
to the daylight string (e.g., MDT) if one was specified. The last element
(tzname[2]) is a NULL pointer that typically terminates an array of
pointers. If no TZ setting is done at boot time, the ctime package uses
PST8PDT as a default.
When compiled and run, the following little program, TIMEDATA, shows
some of the time-related values for your system. Don't be surprised to see
Pacific time zone data even if that is not your "home" time zone.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 49 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
Here is an example of the output of TIMEDATA taken from my system in
Denver, Colorado:
daylight savings time flag = 1
difference (in seconds) from GMT = 25200
standard time zone string is mst
daylight time zone string is mdt
ctime(): Mon Feb 16 06:19:33 1987
local time: Mon Feb 16 06:19:33 1987
universal time: Mon Feb 16 13:19:33 1987
If you accept the Pacific default for timezone while operating in a
different time zone, there is usually no harm done. The effect of not
setting TZ is that you are fibbing about the local time, which will cause
DOS to have an incorrect notion of what GMT is. For example, had I let TZ
default to PST8PDT and set local time correctly for Denver, the system
value for GMT would be off by one hour.
However, problems might occur for systems connected into wide-area
networks that must keep accurate track of file-modification times and
various other timed events, possibly while crossing several time zones. In
such cases, all connected systems should have the correct time zones set in
the TZ variable and agree on what the value of GMT is.
Getting a time-and-date string is a first step for us in the
development of a program called NOTES, a simple electronic diary. But we
will also need a way of creating and modifying files before we can assemble
a working prototype, so let's talk about that next.
Basic File and Character I/O Functions
Much of what we will do in the rest of this book will involve some
interaction with disk files. There are many standard I/O routines in the C
run-time library that deal with files on various levels. In this section,
we will review some of the more frequently used functions. Then we will
create a useful demonstration program.
The run-time library provides access to disk files and other
I/Ostreams using DOS file handles. These functions--open(), close(),
read(), write(), lseek(), and so on--are analogous to the UNIX system calls
of the same names that use file descriptors. These functions are unbuffered
from a user's perspective, although the operating-system kernel may
anticipate read operations and delay write operations to minimize disk-head
movement, and improve I/O efficiency.
Buffered I/O
Another level of file access is the set of standard library
subroutines that do buffered I/O. The functions fopen), fclose(), fgetc(),
fputc(), fgets(), and fputs() are representative of this level, and are
used extensively in the programs in this book. These functions use a file
pointer that points to a structure of type FILE. The FILE structure
template is defined in stdio.h, the header file that is included in all
source files that use standard I/O library functions.
All file-access functions will be covered at one time or another in
programs later in the book. For now, we'll concentrate on the buffered I/O
library functions. By way of example, here is a fairly typical use of the
standard run-time library in a program called NOTES, which lets us keep an
electronic notebook or diary. This first version, NOTES1, is a simplified
prototype.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 52 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
This program, although rather simple and not yet fully developed, is
immediately useful in its current form. It takes input from the keyboard
and appends it to a "notesfile" in the current directory. The program is
invoked by typing NOTES on the DOS command line. Each new session starts by
placing a blank line and a date/time stamp at the end of the data file,
currently hard-coded into the program as NOTES.TXT. Text input is simply
typed at the keyboard and terminated by the user typing a Ctrl-Z (end-of-
file) character on a line by itself.
This simple version of NOTES allows no options. If you make an error
while inputting text, you must back up and correct it before moving off the
line. To edit the NOTES.TXT file, you must leave the NOTES program and call
upon some text-editing program. We will embellish the NOTES program with
additional features in later chapters, but for now we will accept its
limitations and treat it as a learning aid.
The call to fopen() is interesting. The function will open the named
file for appending if the file already exists, or it will silently create
the file if it doesn't exist. The call returns a valid FILE pointer if the
file is opened successfully, or NULL to flag an error. (Attempting to open
a directory, for example, produces an error.) Our program must be prepared
to deal with errors. Therefore, at the risk of having our programs look
like LISP source code, we will always test the return value of I/O
functions. To ignore this extra bit of work is not simply bad programming
practice; it can be dangerous. A program that fails without a whimper is
not apt to inspire strong feelings of trust in its users.
The declaration of fopen() is
#include <stdio.h>
FILE *fopen(pathname, type);
char *pathname;
char *type;
The pathname argument may be a simple filename and optional extension,
or a full or relative pathname with an optional leading drive specifier.
The type argument is one of the following:
═══════════════════════════════════════════════════════════════════════════
ARGUMENT MEANING
───────────────────────────────────────────────────────────────────────────
r │ Open for reading (file must exist)
w │ Open for writing (create or truncate)
a │ Open for appending (create if necessary)
r+ │ Open for both reading and writing (the file must exist)
w+ │ Open for both reading and writing (create or truncate)
a+ │ Open for reading and appending (create if necessary)
───────────────────────────────────────────────────────────────────────────
The optional + within the type string specifies update mode, in which both
reading and writing are allowed. The file pointer must be set by an
intervening call to fseek() or rewind() when switching between read and
write operations or vice versa.
Unlike UNIX and XENIX, which use a single newline (NL) character to
terminate a text line, DOS uses a carriage-return character (CR) plus a
linefeed (LF) character. The codes to NL and LF are identical (ASCII code
10), so you will usually see the DOS end-of-line treatment described as
CR/NL, but it is really CR/LF. Technically, LF moves down a line but
retains the same column position, so a CR is needed to get back to the
first column. The NL code convention achieves the two tasks in a single
operation, saving a byte per line in ordinary text files. Both methods of
terminating a line are allowed by the ANSI standard (see Chapter 13 for
more information about ANSI standards).
In addition to a different end-of-line treatment, DOS uses an explicit
Ctrl-Z (ASCII code 26) to mark the end of a file. (Under UNIX and XENIX,
the end-of-file condition is sensed when an attempt is made to perform an
operation beyond the last position in a file, which is known because an
exact size is maintained for each disk file.)
The conventions just described for DOS text files do not apply to
binary files (executable program files, for example), which can contain any
arbitrary bit patterns in any position. For binary files, all bits are
significant, and an accurate file size must be maintained by DOS so it
knows where the end of the file is.
To maintain compatibility with existing C programs, UNIX textfile
conventions are used within C programs under DOS. When a file is opened,
the differences are specified by a text (t) or binary (b) flag appended to
the type argument to fopen(), or by the global _fmode translation-mode
variable if neither t nor b is explicitly specified. The default for _fmode
is text-translation mode.
We can use separate statements for the processing and the
errordetection phases of each call to a library routine:
FILE *fp;
fp = fopen(notesfile, "a");
if (fp == NULL)
fatal(pgm, notesfile, 1);
This is rarely done, however. You are more likely to see this written in
the condensed form (shown in notes1.c, on page 52) which is the de facto
convention for embedding assignments that is used by many, but by no means
all, C programmers:
FILE *fp;
if ((fp = fopen(notesfile, "a")) == NULL)
fatal(pgm, notesfile, 1);
Don't let "convention" sway you, however. Use whichever form looks pleasing
to your eye. Just be sure the statements are logically correct and that
error conditions are checked and responded to in a reasonable way.
The getchar() and putc() calls are actually macros defined in stdio.h.
These calls tend to work somewhat faster than the equivalent functions,
fgetchar() and fputc(), because they expand to in-line code and eliminate
the function-call overhead associated with true functions. However, if we
had many calls to putc() and getchar() (which is just getc() with stdin
specified as the stream), we would save some space by using the functions
because the code for the function only has to appear one time in the object
file, rather than once for each call to the macros.
Rather than clutter this chapter with information that is available to
you in the C compiler's Library Reference Manual, we'll just refer to that
document (or the equivalent for other C compilers) for more details and
move on to some other interesting topics that are essential to the
development of our programs.
Process Control Functions
A process is an environment in which a program executes. When you run
the NOTES.EXE program, for example, DOS allocates an instruction segment, a
user data segment, and a system data segment. It then initializes the
instruction and data segments from the program and starts execution.
The Microsoft C run-time library provides a group of functions for
controlling the execution of processes from within a process. The easiest
to use is system(), which takes a program-name argument (may be a full or
relative pathname) and runs the named program as a subprocess. When the
subprocess terminates, control is returned to the calling process. The
declaration of system() is
#include <process.h>
int system(string);
char *string;
where the string argument is the name of a built-in DOS command or an
external program that must be in the current directory or be accessible
via the PATH variable. DOS must be able to find and load COMMAND.COM to
service the system() call. A return of -1 indicates an error.
A simple example of the system() function in use is the following code
fragment that calls a text editor from within a running process:
.
.
.
if (system("EDIX") == -1) {
perror("System error");
/* error recovery procedure */
.
.
.
}
An error return could be caused by the absence of a valid COMMAND.COM
file or by the inability of DOS to find the EDIX.EXE file with the
information provided by the system() call plus the current value of
PATH.
The exec() and spawn() families of process-control functions are more
complicated to use, but they provide considerably more versatility. The two
families have much in common and each has six members. The base function
name--exec, for example--takes a one- or two-letter suffix to fully specify
the function's behavior. Thus, the exec() function names are execl(),
execle(), execlp(), execv(), execvp(), and execve(). The symbols appended
to the base function names have these meanings:
═══════════════════════════════════════════════════════════════════════════
SUFFIX MEANING
───────────────────────────────────────────────────────────────────────────
l │ Uses a NULL-terminated list of arguments
v │ Uses a variable list of arguments
e │ Receives an environment pointer
p │ Searches PATH to find program files
───────────────────────────────────────────────────────────────────────────
Rather than show all six function declarations for each of the
families, we'll look at one representative function of each. Both of these
functions automatically pass along a copy of the current environment to the
child process. Here is the declaration for execl():
#include <process.h>
int execl(pathname, arg0, arg1, ..., argn, NULL);
char *pathname;
char *arg0, *arg1, . . .,*argn;
The declaration for spawnvp() is
#include <process.h>
int spawnvp(modeflag, pathname, argv);
int modeflag;
char *pathname;
char *argv[];
The exec() family always overlays the current process (parent) with a
new process (child) that destroys the parent, so it is impossible to return
from an exec() function call. The spawn() family, however, runs the
subprocess with an action determined by the modeflag parameter. A value of
P_WAIT causes the parent process to suspend execution while the child runs.
When the child process terminates, control is returned to the parent. A
spawn() call with a modeflag value of P_OVERLAY produces the same effect as
the equivalent exec() function call.
Arguments are handled differently for the fixed-list (l) and variable-
list (v) versions of these functions. The fixed-list version is used when
we know in advance how many arguments will be presented. We place a NULL
argument after the last argument we want to pass, to tell the exec() or
spawn() function that the list is complete. In the variable-list version of
the functions, a pointer to an argument list is passed. The argument list
is handled just like the argv of a program's main() function, which was
described earlier in this section.
The first argument in a fixed list (arg0) or a variable list (argv[0])
is usually a copy of the pathname argument. Other values will not cause an
error, but NULL should not be used in the first position of a fixed list:
The NULL would effectively hide the remainder of the list from the child
process, since a NULL argument marks the end of the list. Recall that under
DOS version 3.00 and later, the program name is available to the child as
arg0 or argv[0].
Here is an example of the spawnvp() function in action. It is used to
run a text editor as a subprocess. If an EDITOR environment variable is
defined, its string value is used as the program name. If EDITOR is not
defined, NOTES uses the DOS EDLIN program. The editor is given the name of
the current notesfile as an argument. This update to the NOTES program also
adds an alternative way of terminating input (using a dot alone on a line
like the UNIX mail program) and a way of ignoring the Ctrl-Break interrupt
so that a user cannot garble the NOTES.TXT file by interrupting it in the
middle of text entry.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 58 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
The source listing for NOTES contains a few things we haven't shown
before. First, the signal() function is used to disable the Ctrl-Break
input from the keyboard. Since signal() returns a pointer value of -1 on
errors instead of the usual NULL, we need to use the strange-looking cast
of the value on the right side of the comparison. The (int(*)())-1 is
needed because the C compiler expects a pointer to an integer.
Second, the dot (.) is used to terminate data input. This involves a
slight complication. A dot usually does not occur in free text as the first
character of a line, but it may occur anywhere else. We depend on this, and
check that the current position is the beginning of a line and that the
input character is a dot before terminating data entry.
Third, the EOF signal (a typed Ctrl-Z) may be used anywhere to halt
data entry. It should, however, be used only on a line by itself. If Ctrl-Z
is typed following some text on a line, some programs will not accept the
line because it is not properly terminated. Programs should be written to
handle this situation, but many insist that each line in a text file end
with a CR/LF combination and may fail in unpredictable ways if a line dead-
ends without the expected termination.
The next program, RUN_ONCE, is a bit of a novelty, but it has a
purpose in some restricted circumstances. Let's say you have an office
setting in which computer users with very limited experience are expected
to run a single program, perhaps an integrated database-spreadsheet-word
processor-blender-and-kitchen-sink program. You want the system to come up
running this program and you also want to prevent the users from exiting to
DOS. If they quit the catchall program, the RUN_ONCE program tells them to
turn off the power and then it locks up the machine.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 62 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
After compiling this program, try running it on your favorite text
editor or some other program. For example,
run_once word myfile.doc
loads and executes the RUN_ONCE program, which spawns Microsoft Word with
MYFILE.DOC as an argument. When the user quits Word, the system displays
the exit message and appears to be dead. A reboot is required to restart
the system. If the RUN_ONCE program is embedded in the AUTOEXEC.BAT file,
it will prevent the users from doing anything except what is planned for
them by whoever sets up the system. (This is easily defeated by someone who
knows DOS. All that is required is to load a floppy disk with another copy
of DOS that does not have the RUN_ONCE program installed. But someone who
knows this probably knows how to use DOS anyway.)
Other Library Functions
We have barely scratched the surface of the standard run-time library
provided with the Microsoft C compiler. Our purpose has been to describe
some representative library routines and to illustrate their use in real
programs. As we develop programmer's utilities and other programs in the
remainder of this book, we will introduce more of these routines in
context.
The functions and macros we have looked at so far are portable, with
little or no change, to various versions of DOS and to UNIX-based operating
systems. The next chapter delves into functions that are tied intimately to
DOS and are therefore not easily ported to other operating environments.
Although they lack portability, the functions are important for programs
that need to obtain the highest level of performance possible from a PC.
Chapter 5 PC Operating System Interfaces
For me, one of the joys of working with a personal computer, after
having spent years working on time-sharing systems over slow-speed data
lines, is the incredible responsiveness of the PC in terms of screen
updates. To "paint" a 24-line screen at 9600 bps (bits per second)
typically takes about 4 seconds--not too bad, really. But at the more
common 1200 bps used by most dial-up connections, a complete paint job
takes closer to 16 seconds. Paging through a document or a source list at
this speed becomes a bit of a chore, and one quickly learns how to use
regular expression searches, small window sizes, and other tricks to
minimize the time it takes to find the material of interest.
In contrast, using one of the slowest methods available on a PC, the
DOS print-character function call, painting a full 25-line screen takes
only about 4 seconds. By using BIOS routines intelligently, one often can
do the job in less than half a second. Using direct screen access, the
updates become virtually instantaneous. The PC has spoiled me, and it is
really hard to go back to dial-up operations: It feels like the whole world
has been put into slow motion.
In this chapter, we'll explore methods of managing screen displays via
the DOS and BIOS interrupt services. The methods used here are not portable
to UNIX and XENIX, and the BIOS routines are not even guaranteed to be
portable to some nominally IBM-compatible hardware.
This material is presented to acquaint you with some of the techniques
used in commercial-quality programs. The mark of such programs is how well
they serve the user's needs. Among the needs I hear most often expressed,
the following are related to screen and system configuration:
► The program must work on any compatible machine, regardless of the
configuration (disks, display type, and so forth).
► The screen must be updated quickly_no growing old waiting around
for the next frame.
► If both color/graphics and monochrome display systems are
installed, it must be possible to choose which to use.
► There must be no flickering, blizzard effect, or other visual
"noise" at any time.
Using DOS and BIOS routines, we can satisfy these needs almost
completely. Only the first one is a problem because some machines were
designed in a way that limits their BIOS compatibility. As a developer, I
want to maximize the audience, so I address such machines by using the ANSI
device driver, which is covered in Chapter 13.
The routines in this chapter cover needed functions that are not
already handled by the standard run-time library, with a few exceptions. In
those few cases, a function is presented that duplicates a system function
because that function may not be available in the library of other C
compilers. A case in point is kbhit(): The function keyready() performs the
same function for those whose libraries don't have kbhit() available.
We begin with an overview of the DOS and BIOS access routines in the
standard library. These routines are the low-level access functions upon
which our interface routines are built. Then we will write a program that
shows how to use many of the routines we develop. The routines presented
here are not a complete set; they are the high-use routines that we will
need shortly. Other routines will be added to the DOS and BIOS libraries as
we need them in later chapters.
System Interface Functions
The C run-time library provided with Microsoft C contains several low-
level system-access functions. We will use three of them as a basis for our
DOS and BIOS interfaces: bdos(), int86(), and intdos().
The functions int86x() and intdosx() are used in programs that use
long addresses. They behave just like their int86() and intdos()
counterparts, but they take an additional argument that specifies segment
registers used in forming long addresses. The segment register values can
be obtained by using the segread() function.
The file dos.h in the \include directory contains several structure
and union definitions that are needed by the system access functions. If
you are not comfortable using C unions, here is a good example of how they
may be used.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 67 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
The header file defines two structures for the CPU registers and
flags. The first is struct WORDREGS:
/* word registers */
struct WORDREGS {
unsigned int ax;
unsigned int bx;
unsigned int cx;
unsigned int dx;
unsigned int si;
unsigned int di;
unsigned int cflag;
};
The second structure is struct BYTEREGS:
/* byte registers */
struct BYTEREGS {
unsigned char al, ah;
unsigned char bl, bh;
unsigned char cl, ch;
unsigned char dl, dh;
};
A composite word/byte register data structure is made by overlaying
the two structures, like this:
/* general purpose registers union - overlays the corresponding word and
* byte registers.
*/
union REGS {
struct WORDREGS x;
struct BYTEREGS h;
};
The union REGS lets us access the registers either as bytes (AL, AH,
and so on) or as words (AX, BX, and so on). We can also access the system
carry flag as a word register. It is unfortunate that we don't have similar
access to the zero flag.
Now that we have the data structures in mind (or at least on paper),
we can examine the primary system-access routines. Each is presented here
with a manual-page summary and a brief description. In the next two
sections, we will apply these functions to the task of creating our DOS and
BIOS libraries. Any file that calls the following functions must include
the header file dos.h by using the line
#include <dos.h>
Here are the system-level functions:
int bdos(dosfn, dosdx, dosal);
int dosfn; /* function number */
unsigned int dosdx; /* DX register value */
unsigned int dosal; /* AL register value */
The bdos() function is a DOS function interface that loads the DX and
AL registers with values provided by the caller and then executes an INT
21H. Following the DOS function call, the value of the AX register is
returned to the caller.
The uses of bdos() are limited to DOS function calls that take
arguments in either or both of the DX and AL registers (or none at all) and
that do not use the carry flag to indicate errors. To minimize function-
call overhead, we will use bdos() in some keyboard functions and recommend
its use for macros or for in-line code within a program.
int int86(intno, inregs, outregs);
int intno; /* interrupt number */
union REGS *inregs; /* register values on call */
union REGS *outregs; /* register values on return */
The int86() function is a general-purpose interface routine that gives
us nearly complete access to the full range of software interrupts. The
function sets up the registers according to the caller's wishes and
executes the specified interrupt. Upon return from the function call,
int86() fills outregs with the current register values and the value of the
system carry flag, which will have a nonzero value if an error has
occurred.
We will use int86() to call BIOS routines for video and keyboard
access and to check equipment and memory information. We also could use it
to access DOS functions, but intdos() does the job faster.
int intdos(inregs, outregs);
union REGS *inregs; /* register values on call */
union REGS *outregs; /* registers values on return */
The intdos() function is just like the int86() function except that it
is designed to make DOS function calls directly and does not require an
interrupt number. The INT 21H interrupt number is effectively hard-coded
into intdos(). The same input and output register behavior is observed as
for int86().
The declaration of the register arguments to int86() and intdos()
shows them as pointers. The union and structure definitions in dos.h do not
reserve storage; they simply describe the needed storage requirements. We
must declare storage in our routines by a statement like
union REGS inregs, outregs;
This statement allocates automatic storage for the unions. We then can load
values into inregs, make the needed function call, and extract data from
outregs. (There is nothing sacred about the names inregs and outregs. Use
anything that seems appropriate, but ideally the names should be
descriptive.) The int86() and intdos() functions need the addresses of the
unions. A call to intdos() looks like this:
intdos(&inregs, &outregs);
We can access elements of the structures either as bytes or as words.
To load a value into AL, for example, we use the byte-oriented
approach:
inregs.h.al = value;
To read the results of a function from the DX register, we use the word-
oriented approach:
value = outregs.x.dx;
Next, we will put these system-level library functions to work for us in
two important libraries in our growing collection of function
libraries.
DOS Library Routines
The DOS library is composed of only a few routines that supplement the
standard run-time library, which is very complete in its coverage of disk,
keyboard, date/time, and many other functions. The routines presented here
are primarily for the benefit of those who are using C compilers with
libraries that do not fully support the DOS interface.
A local header file, doslib.h, contains constants used to specify DOS
interrupts and the function numbers for calls to INT 21H, the DOS
"umbrella" interrupt.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 72 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
DOS Version Number
Under Microsoft C, the header file stdlib.h defines the global
variables _osmajor and _osminor. These variables receive the DOS major and
minor version numbers when a program starts running. The numbers may be
used to verify that the version of DOS is capable of supporting features
required by a program. For example, programs that use pathnames require DOS
version 2.00 or later. The simple block of code added to the program
provides low-cost insurance and alerts the user that it's time to upgrade.
if (_osmajor < 2) {
fprintf(stderr, "Need DOS 2.00 or later\n");
exit(1);
}
Another way to get the version number is to ask DOS for the number.
The function ver() does the job.
/* ver -- get the MS-DOS (or PC-DOS) version number */
#include <local\doslib.h>
/************************************************************
* For MS-DOS versions prior to 2.00, the low byte (AL) of
* the return value is zero. For versions 2.00 and beyond,
* the low byte is the major version number and the high
* byte (AH) is the minor version number.
************************************************************/
int ver()
{
return(bdos(DOS_VERSION, 0, 0));
}
The ver() function uses bdos() to get the version number from DOS. It
returns the return value from bdos(), which is the value in the AX
register. AL holds the major version number and AH holds the minor version
number. For versions of DOS prior to version 2.00, the returned major
number is 0.
Keyboard Functions
The run-time library includes a couple of functions that read from and
test the keyboard buffer. This section presents some alternative functions
that do essentially the same jobs and which can easily be modified for use
with other compilers.
To gather the user's input, we can call the run-time library function
getch(). It reads the next available character from the keyboard buffer and
responds to a Ctrl-Break. If there is nothing ready to read, it waits until
there is. This operation is called a "blocking read" because the machine
simply marks time while waiting for the user to type something. If the user
presses Ctrl-Break, getch() executes the Ctrl-Break handler.
The function getkey() also does a blocking read. However, it differs
from getch() in a few ways. First, it ignores Ctrl-Break. This prevents a
user from breaking out of a program at a critical point that might damage
files or data stored in memory. Second, getkey() checks the value of the
keyboard code. If the code is a null byte (\0), it gets the next character
code, bitwise-ORs it with 0x100 (which has the same effect as adding 256),
and returns the modified value. This value can be compared against the
constants that are defined in keydefs.h to determine what key was pressed.
The defined values include most of the keys and combinations of keys on the
keyboard.
Here is the source code for keydefs.h:
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 76 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
This is the source code for getkey():
/* getkey -- return a code for single and combo keystrokes
* - returns a unique code for each keystroke or combination
* - ignores "Ctrl-Break" input */
#include <dos.h>
#include <local\std.h>
#include <local\doslib.h>
#include <local\keydefs.h>
int getkey()
{
int ch;
/* normal key codes */
if ((ch = bdos(KEYIN, 0, 0) & LOBYTE) != '\0')
return (ch);
/* convert scan codes to unique internal codes */
return ((bdos(KEYIN, 0, 0) & LOBYTE) | XF);
}
To determine whether a key has been pressed, we would use the run-time
library function kbhit(), which returns a nonzero value if a code is
available in the keyboard buffer, indicating that at least one key has been
pressed. The function keyready(), presented next, does the same job.
/* keyready -- nonzero if the keyboard buffer
* has any codes waiting */
#include <dos.h>
#include <local\doslib.h>
int keyready()
{
union REGS inregs, outregs;
inregs.h.ah = CH_READY;
intdos(&inregs, &outregs);
return (outregs.h.al);
}
The kbhit() or keyready() function should be called before an attempt
is made to read the keyboard. This produces a "nonblocking read." If
nothing is ready, the program can do something useful while it waits for
the user to press a key. That something should be broken up into brief
allocations of work (i.e., sending a character to the printer), so that the
keyboard provides a timely response to user input. There is, however, no
good reason to put the entire computer to sleep waiting for user input. The
following code fragment shows a way to handle keyboard processing and a
background task:
.
.
.
int k, getkey(), keyready(); /* keyboard functions */
int do_task(TASK); /* task dispatcher */
TASK taskp; /* task pointer */
.
.
.
while (1) {
/* get user's input */
if (keyready()) {
k = getkey();
switch (k) {
case K_ESC:
do_exit();
.
.
.
}
/* do background task */
do_task(taskp);
}
.
.
.
For example, if the task dispatcher is calling a routine that dumps
characters into a printer spooling buffer, the printer appears to run
continuously while not burdening the main program at all. User commands and
input will be honored immediately. Other background tasks might include
doing a hard disk-to-tape backup, checking timed alarms, and running a
sprinkler system.
The makefile, dos.mk, automatically builds the library dos.lib:
# makefile for the DOS library
LINC = c:\include\local
LLIB = c:\lib\local
# --- inference rules ---
.c.obj:
msc $*;
# --- objects ---
OBJS = getkey.obj keyready.obj ver.obj
# --- compile sources ---
getkey.obj: getkey.c $(LINC)\std.h $(LINC)\doslib.h $(LINC)\keydefs.h
keyready.obj: keyready.c $(LINC)\doslib.h
ver.obj: ver.c $(LINC)\doslib.h
# --- create and install the library ---
$(LLIB)\dos.lib: $(OBJS)
del $(LLIB)\dos.lib
lib $(LLIB)\dos +$(OBJS);
BIOS Library Routines
The ROM BIOS in a PC offers a wide range of services that we can use
to our advantage. We will write routines that let us determine how the host
computer is configured, control the cursor, check the status of special
keyboard keys (such as Caps Lock and Num Lock), and read and write
characters and video attributes anywhere on the screen.
The header file bioslib.h contains the constants needed to call
various BIOS interrupts and services by symbolic names.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 82 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
Equipment Determination Functions
We'll start with a pair of functions that help us determine what
equipment is installed in a PC and how much memory it has.
The first of these functions, equipchk(), uses BIOS interrupt 11H to
determine what devices are installed. A structure is used to hold the data
obtained by equipchk(), which declares the structure globally and fills it
when called. Other functions can also access the data if they contain the
following external definition:
extern struct EQUIP Eq;
Here is the source code for equip.h and equipchk().
/* equip.h -- header for equipment determination/inventory */
struct EQUIP {
short disksys, /* 1 if disks installed */
game_io, /* 1 if game i/o adapter installed */
nprint, /* number of printer ports */
nrs232, /* number of serial ports */
vmode, /* initial video mode (from switches) */
ndrive, /* number of disk drives
(from switches) */
basemem; /* amount of base memory in Kbytes */
};
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 84 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
To find out whether a game port is installed, call equipchk() and then
look at the game_io member of the structure.
#include <local\equip.h>
extern struct EQUIP Eq;
.
.
.
if (equipchk())
/* handle the error */;
if (Eq.game_io == 1)
printf("Game adapter installed\n");
Although equipchk() can tell us how much memory is installed on the
main system board, it cannot help us get the total installed memory value.
The memsize() function calls BIOS interrupt 12H to get the system memory
size, including any memory in the I/O channel on adapter cards (exclusive
of display memory and that which is not contiguous with the main system-
board memory).
/* memsize -- get memory size */
#include <dos.h>
#include <local\std.h>
#include <local\bioslib.h>
int memsize()
{
union REGS inregs, outregs;
return (int86(MEM_SIZE, &inregs, &outregs));
}
The size of main memory is reported as a 16-bit integer that
represents the total number of kilobytes found.
Keyboard Status
Older PC keyboards did not have status lights on the Caps Lock and Num
Lock keys. It is helpful to the user if the status of these keys is
reported on the screen in applications in which the status is important.
The same holds true for the Ins key and, in some situations, the Scroll
Lock key. Also, we can use the status of the Shift, Ctrl, and Alt keys to
alter the meaning of the function keys to do other jobs.
The kbd--stat() function lets us obtain the needed information. It
calls BIOS interrupt 16H, service 2 (status), and returns the value of the
AL register. The header file keybdlib.h contains definitions of masks that
are used to test the return value from kbd_stat() to determine which keys
were pressed.
/* keybdlib.h */
#define KBD_READ 0 /* keyboard routine numbers */
#define KBD_READY 1
#define KBD_STATUS 2
#define KS_RSHIFT 0x0001 /* bit masks for keys and states */
#define KS_LSHIFT 0x0002
#define KS_CONTROL 0x0004
#define KS_ALT 0x0008
#define KS_SL_STATE 0x0010
#define KS_NL_STATE 0x0020
#define KS_CL_STATE 0x0040
#define KS_INS_STATE 0x0080
/* kbd_stat -- return the keyboard status
* word (bit-significant) */
#include <dos.h>
#include <local\bioslib.h>
#include <local\keybdlib.h>
unsigned char kbd_stat()
{
union REGS inregs, outregs;
inregs.h.ah = KBD_STATUS;
int86(KEYBD_IO, &inregs, &outregs);
return ((unsigned char)(outregs.h.al));
}
Video Access
One of the most frequently used ROM BIOS interrupts is 10H, the access
point for video services. We will use many of these services in our
programs.
The header file video.h contains some data structures used to hold
important video-mode and cursor data. It also contains definitions of color
and attribute values, special and drawing-character codes, and constants
used by the mode and cursor structures.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 87 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
The primary functions in interrupt 10H are those used to get the
current video state data (mode, screen width, and visual page) and to set
the video mode.
The getstate() function invokes video service 15 to get the values of
the video mode, the screen width (redundant information, because the mode
number implies a width, but screen width is directly accessible), and the
visual page number. Although the IBM Technical Reference manual calls this
the active page, it is really the visual page. To be consistent with the
way BASIC defines pages, the active page is defined as the one being
written to and the visual page as the one being viewed. The page number
returned by service 15 is the visual page. Most of the time, the visual and
active page numbers are the same.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 91 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
Because getstate() contains the declarations of the video structures,
it should be called before you use any of the other video functions
(clrscrn(), clrw(), scroll(), and setctype()) that depend on the data
in those structures.
The setvmode() function is used to set the video mode. It cannnot be
used, however, to switch from one adapter to another. That task must be
handled in another way, which is described in Chapter 11. The mode numbers
are defined in video.h. The mode number presented to setvmode() is not
value-checked.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 92 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
For the IBM Monochrome Adapter, and for the standard Color Graphics
Adapter (CGA) in any of its graphics modes, only page 0 is valid. On the
CGA, the 40-column text modes permit up to eight screen pages in display
memory and the 80-column text modes up to four screen pages. On the
Enhanced Graphics Adapter (EGA), page ranges are a function of the mode as
follows: mode 13, 0 through 7; mode 14, 0 through 3; modes 15 and 16, 0 and
1.
The job of setting the visual page falls to setpage(). The pagenumber
argument must be valid for the current video mode because its value is not
checked.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 94 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
Next we have four functions that are used for cursor control. Two of
them, readcur() and putcur(), deal with cursor positioning. The other two,
getctype() and setctype(), are used to determine the cursor shape and to
set it to a particular shape, respectively.
The function readcur() gets the cursor position for the specified page
and passes back the row and column position data to the calling
routine.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 94 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
The row and col arguments are pointers to simulate "call-by-reference"
parameter passing, which allows readcur() to pass back more than a single
value by directly accessing the storage locations of the variables in the
calling function.
The putcur() function moves the cursor to an absolute screen location
on the specified screen page. The specified row, column, and screen-page
values are not checked.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 95 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
The cursor on a PC screen is a "hardware cursor" formed by one or
more raster scan lines in the cell at the current cursor position. The
cursor always blinks. A hardware cursor is available only in text modes and
not in graphics modes, although it is quite easy to fabricate one in
software.
To determine the starting and ending scan lines for the cursor, we use
getctype(). The function uses the same video service as readcur() but
returns cursor type data, not position data. We can use getctype() in a
program to save the starting and ending scan lines so that we can restore
them with setctype() before the program terminates.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 96 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
The inverse operation, setting the cursor type, is a bit more
difficult than finding out what type it is. The job is done by setctype().
The setctype() function sets the starting and ending scan lines to the
values given by its arguments. It is incumbent upon the calling function to
specify correct values for the prevailing display mode.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 97 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
The video functions described next are used to read and write
characters and attributes in any video mode, and dots--individual pixel
values--in graphics modes only.
The function readca() gets the character and video attribute of the
character cell at the cursor position without regard for the video mode.
This is a neat trick in graphics modes when you realize that, to do this,
the BIOS video routine has to do a pattern-matching operation on the
displayed image to find out what the character is.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 98 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
When we are operating in one of the graphics modes, we can read the
values of individual picture elements--pixels, or dots. The function
readdot() returns the color number for the pixel at the specified row and
column coordinates.
/* readdot -- read the value of a pixel (in graphics mode only) */
#include <dos.h>
#include <local\std.h>
#include <local\bioslib.h>
int readdot(row, col, dcolor)
int row, col;
int *dcolor; /* pointer to dot color */
{
union REGS inregs, outregs;
inregs.h.ah = READ_DOT;
inregs.x.cx = col;
inregs.x.dx = row;
int86(VIDEO_IO, &inregs, &outregs);
*dcolor = outregs.h.al;
return (outregs.x.cflag);
}
The allowed row and column values depend on the graphics mode:
═══════════════════════════════════════════════════════════════════════════
MODE ROW RANGE COLUMN RANGE
───────────────────────────────────────────────────────────────────────────
4 │ 0-319 │ 0-199
5 │ 0-319 │ 0-199
6 │ 0-639 │ 0-199
13 │ 0-319 │ 0-199
14 │ 0-639 │ 0-199
15 │ 0-639 │ 0-349
16 │ 0-639 │ 0-349
───────────────────────────────────────────────────────────────────────────
The function writec() writes a character or a string of identical
characters, starting at the current cursor position. It does not change the
cursor position. The number of repetitions must not cause the function to
write past the end of the current line.
This function is particularly handy for quickly drawing lines and
boxes on the screen. We will also use it as a building block in functions
that manipulate higher-level objects, such as text strings that are
presented in fixed-length fields and scrolling windows.
/* writec -- write a character and attribute to the screen */
#include <dos.h>
#include <local\std.h>
#include <local\bioslib.h>
int writec(ch, count, pg)
unsigned char ch; /* character */
unsigned char attr; /* attribute */
int count; /* number of repetitions */
int pg; /* screen page for writes */
{
union REGS inregs, outregs;
inregs.h.ah = WRITE_CHAR_ATTR;
inregs.h.al = ch;
inregs.h.bh = pg;
inregs.h.bl = attr;
inregs.x.cx = count;
int86(VIDEO_IO, &inregs, &outregs);
return (outregs.x.cflag);
}
The writeca() function is the same as writec(), except that it also
writes the video attribute at the same location or region.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 100 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
To simulate the printing behavior of a simple terminal, use the
writetty() function to place characters on the screen. This function writes
characters only, but it automatically does newline and screen-scrolling
operations.
/* writetty -- write to screen using TTY interface */
#include <dos.h>
#include <local\std.h>
#include <local\bioslib.h>
int writetty(ch, attr, pg)
unsigned char ch; /* character */
unsigned char attr; /* video attribute */
int pg; /* screen page for writes */
{
union REGS inregs, outregs;
inregs.h.ah = WRITE_TTY;
inregs.h.al = ch;
inregs.h.bl = attr;
inregs.h.bh = pg;
int86(VIDEO_IO, &inregs, &outregs);
return (outregs.x.cflag);
}
The writedot() function writes a dot of the specified color at the row
and column position passed as arguments. Valid color numbers are a function
of the current graphics mode:
═══════════════════════════════════════════════════════════════════════════
MODE COLOR NUMBERS
───────────────────────────────────────────────────────────────────────────
4, 5, 10 │ 0-3
6 │ 0 and 1
8, 9, 13, 14 │ 0-15
15 │ 0-1
16 │ 0-4 or 0-15
───────────────────────────────────────────────────────────────────────────
If bit 7 of the AL register is a 1, the dot color is exclusive-ORed (XOR)
with the current dot color value.
/* writedot -- display a dot at the specified position */
#include <dos.h>
#include <local\std.h>
#include <local\bioslib.h>
int writedot(r, c, color)
int r, c; /* row and column coordinate */
int color; /* dot (pixel) color */
{
union REGS inregs, outregs;
inregs.h.ah = WRITE_DOT;
inregs.h.al = color;
inregs.x.cx = c;
inregs.x.dx = r;
int86(VIDEO_IO, &inregs, &outregs);
return (outregs.x.cflag);
}
The following miscellaneous video functions are used to clear the
current screen page, or a portion of it, scroll a region of the screen up
or down, set the graphics palette or text border color, and write video
attributes.
The clrscrn() function uses the BIOS video scroll routine to
initialize the visual screen page. The clrw() function clears a window of
specified dimensions on the visual screen page.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 102 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 103 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
The palette() function serves two purposes. In text modes, it sets the
screen border color. In graphics modes, it sets the color palette from
which drawing colors are selected.
/* palette -- set graphics color values or border color */
#include <dos.h>
#include <local\bioslib.h>
int palette(id, color)
unsigned int id, color;
{
union REGS inregs, outregs;
inregs.h.ah = PALETTE;
inregs.h.bh = id;
inregs.h.bl = color;
int86(VIDEO_IO, &inregs, &outregs);
return(outregs.x.cflag);
}
To scroll the entire visual screen page or a rectangular portion of it
up or down, use scroll(). The function takes a signed number: negative
numbers scroll lines down the screen, nonzero positive numbers scroll lines
up, and zero initializes the specified region. Vacated lines are set to
blanks in the specified attribute.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 104 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
The next function is really a composite of two BIOS video services. To
change the attribute of a range of character positions without disturbing
the characters, use writea(). This function reads the character and
attribute at each affected position and writes back the same character in
the new attribute while leaving the current cursor position unchanged.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 105 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
Demonstration Program
Now that we have a collection of DOS and BIOS interface routines, what
can we do with them? Perhaps the best way to answer the question is to
demonstrate their use in a program. A vehicle for showing off some of the
routines is CURSOR, a program that magnifies the current cursor and lets us
change its shape. CURSOR is proof that carefully written and applied BIOS
video routines can produce startlingly fast screen manipulation--far from
instantaneous, but fast enough for demanding commercial applications.
Before examining the source code for CURSOR, we will prepare several
functions that are built on the current set of BIOS video routines. The
functions put_ch(), putstr(), and drawbox() can be viewed as a layer above
the writec() and putcur() functions of the BIOS library just described.
They provide often-used capabilities that are not provided in a convenient
way by the primary BIOS functions.
The put_ch() function displays a single character at the current
cursor position and then advances the cursor to the next position. The name
includes the underscore to distinguish put_ch() from putch(), a standard
console I/O routine in the Microsoft run-time library. The putstr()
function displays a text string and advances the cursor to the next
displayable position. Combining these two functions and several of the low-
level BIOS functions in drawbox() lets us create fine-ruled boxes for
highlighted text and graphical elements of our screens. The drawbox()
function uses the single-line drawing characters that are part of the IBM
extended-ASCII character set. We could also use double-line and other
combinations of line-drawing characters to produce distinctive box shapes,
as we will do in later chapters.
Here are the C source listings for put_ch(), putstr(), and
drawbox():
/* put_ch -- display a character in the prevailing video
* attribute and advance the cursor position */
#include <local\video.h>
int put_ch(ch, pg)
register char ch;
int pg;
{
int r, c, c0;
readcur(&r, &c, pg);
writec(ch, 1, pg);
putcur(r, ++c, pg);
return (1);
}
/* putstr -- display a character string in the
* prevailing video attribute and return number
* characters displayed */
int putstr(s, pg)
register char *s;
unsigned int pg;
{
unsigned int r, c, c0;
readcur(&r, &c, pg);
for (c0 = c; *s != '\0'; ++s, ++c) {
putcur(r, c, pg);
writec(*s, 1, pg);
}
putcur(r, c, pg);
return (c - c0);
}
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 108 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
Now we can write cursor.c. The goal is to design a program that
presents a magnified view of the cursor and that allows us to interactively
adjust the cursor shape. The source for CURSOR is quite long. It is one of
several programs I wrote to test the DOS and BIOS interface functions. The
program shows how the DOS and BIOS interface functions are used in a
working example. By writing "driver" programs as I wrote the interface
functions, I was able to both anticipate what functions might be needed and
test the functions as they were written.
The pseudocode for CURSOR shows how the program works.
get video information
save current attribute/color values
draw basic screen (header, cursor image, instructions)
while (key is not Return)
switch selection modes on left or right arrow
clear start/stop selection pointer
display new pointer
adjust scan line position on up or down arrow
clear current scan-line pointer
calculate new pointer value
update cursor image display
(scan lines, pointers)
set new cursor type
restore original attribute/color
clear screen
This simple description does not reveal a few details in the
calculations needed to accurately display images for cursors on various
types of hardware. For example, CURSOR automatically adjusts the displayed
image to compensate for 80- and 40-column screen widths, and differing
numbers of scan lines and widths in cursors on IBM monochrome display
systems (9 by 14) and color/graphics systems (8 by 8). The C source, which
shows how these details are handled, is in the file cursor.c.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 110 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
The makefile, bios.mk, automatically builds the library bios.lib using
the inference rules supplied by tools.ini. The makefile for CURSOR is
contained in cursor.mk:
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 116 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
# makefile for CURSOR program
LLIB=c:\lib\local
cursor.obj: cursor.c
msc $*;
cursor.exe: cursor.obj $(LLIB)\bios.lib $(LLIB)\dos.lib
link $*, $*, nul, $(LLIB)\bios $(LLIB)\dos;
When you run the CURSOR program, some computers will exhibit
unexpected behavior as a function of the installed hardware. The AT&T
PC6300, for example, does not permit "split" cursors (the stop scan line
is less than the start scan line). CURSOR will allow you to fashion a split
cursor, but on the 6300, it disables the cursor instead. Also, I have not
modified the program to work with EGA-compatible boards. EGA boards use
different numbers of scan lines per character cell than standard CGA boards
operating in the same (or equivalent) modes.
Before moving on to user interfaces, we will add one more function to
the BIOS video library. The writestr() function is not used in this
chapter, but it is called by a user-input function in Chapter 6. It
displays a two-part message in a fixed field and leaves the cursor at its
current position. The design of writestr() lets us concatenate strings
visually and prevents them from extending beyond the available display
area. In addition, if the resulting string does not completely fill the
display field, writestr() pads the field with spaces in the prevailing
attribute to clear any residue from the previous contents of the field.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 118 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
Recommendations
We need to address a few library management issues. In my work and in
this book, I have elected to keep the number of functions at one per file
except in rare circumstances where two or more functions have a symbiotic
relationship. The cost of doing this is increased disk use. Although most
of the source files are less than 500 bytes in size, they each require from
2 to 8 KB of storage capacity on a typical hard disk. The amount used is a
function of the block or cluster size of the disk, which is in turn a
function of its capacity and the version of DOS used to format the
disk.
The primary advantage of separate functions in libraries is that the
linker program adds the object code only for the functions used in a
program, thus avoiding the possibility of dragging in a lot of unneeded
instructions.
The alternative--packaging related functions in a single object module
in a library--can be effective too, as long as the functions so grouped are
likely to be used together in a program. We could, for example, group the
getctype(), setctype(), and other cursor-related functions in a single
object module because they are usually employed together, as in the CURSOR
example.
The issue of compatibility across a wide range of machines deserves
comment. To obtain the broadest PC compatibility for your programs, use the
higher-level DOS function calls. They are often slower and less versatile
than the BIOS functions, but programs built on them can be run on virtually
any machine that runs DOS.
Using BIOS interfaces will erode your market a little, but probably
not enough to fret about for more than a few seconds. If a large enough
market exists for a non-compatible machine (the Zenith Z-100, for example),
you can create a customized version of the program because the dependencies
can be limited to a few interface library functions.
Compatibility of PC/DOS-based programs to UNIX and XENIX is another
matter. Some fundamental differences in architecture make transporting a
program developed under DOS on a PC to a UNIX-based system difficult, but
not impossible. The primary difficulty is that users in a multi-user
environment are typically not using the console; they are connected by
dial-up or hard-wired lines to the host computer. Those connections are a
bottleneck that reduces the available "bandwidth" of the user interface to
a tiny fraction of what a typical PC has available on a dedicated machine.
Despite this difficulty, most programs can be written for portability
between DOS and UNIX/XENIX. In fact, I have done most of my development
work under XENIX and then converted to DOS afterward.
Going from DOS to UNIX is tougher because I tend to take advantage of
all that the PC and DOS offer for program performance reasons. To gain some
needed portability to UNIX, we can use the Termcap/Curses virtual terminal
interface packages that permit UNIX to work with just about any
alphanumeric video terminal. Termcap is a set of low-level functions that
get terminal information from a database of terminal capabilities and
provide control over cursor positioning and other video terminal
functions.
Curses is a high-level screen-management package that provides
optimization of cursor movements and control over displayed text, video
attributes, and keyboard interactions. Curses also supports buffers that
are larger than the screen and provides modest windowing features.
It is outside the scope of this book to provide detailed information
on Termcap/Curses software. You may want use the virtual-terminal interface
in your programs for one important reason: A program written under DOS on a
PC using a Curses interface for screen management is easily ported to
UNIX/XENIX. Give it a shot. A very good Curses implementation for the PC
from Aspen Scientific works well with Microsoft C and other C compilers
under DOS and is compatible with UNIX System V Curses. Lattice offers a
non-optimizing version of Berkeley Curses as an adjunct to its C compiler.
Other Curses implementations are available, but I have not tested
them.
There is much more to the DOS/BIOS interface than we have covered in
this chapter. As we need additional functions, we can use what we have done
so far as models for the needed functions. Next, we look at one of the most
important aspects of any program--the user interface.
Chapter 6 The User Interface
The user interface has received a lot of attention in recent years.
Human factors engineering (ergonomics)--the endeavor that is one part each
of engineering, science, and "black magic"--is best known for dealing with
such topics as how to design safe, comfortable car seats and how to lay out
the cockpit of a jet aircraft. However, it also offers much information
about how people use machines that have neither wheels nor wings. Although
by no means a dissertation on human factors engineering, this chapter
focuses on the primary points of contact between the computer and the
user--the keyboard, the display screen, and the speaker--and on how to use
these communications pathways effectively.
As programmers, we must consider both the application and the audience
for the application. In addition, we need to know about the environment in
which a program is to be used, in order to provide the best possible match
between the program and its intended users. Some programs lend themselves
to a command-oriented type of operation. Frequently used tools that may
become components of pipeline commands are examples of this category. Other
programs are well suited to windowing and menus, or at least benefit from
their use. Programs that are infrequently used and that may involve complex
step-by-step procedures fall into this category. Still other programs can
successfully blend the two approaches or switch easily between them. In
Chapter 13, we will develop a program that provides convenient screen
control with instructions from either the DOS command line or a simple menu
of commands. The mode of operation depends on how the program is
called.
The use of pleasing visual effects and appropriate sound can do a lot
to enhance both the appeal and the utility of a program. However,
effectively using visual effects and sound in programs is no less an art
than playing a musical instrument. Later in this chapter, we will explore
the use of sound and text-oriented visual effects in our programs. But we
will start with a view toward standardizing the way programs gather and
process command-line options. Then we will develop some timer functions
that are needed to do several important jobs.
Standard Command-line Processing
The standard user interface of unadorned UNIX/XENIX and DOS is the
command line. One of the major modes of communication from the user to a
program is via command-line options and other arguments. For years, there
has been a need to standardize command-line option processing by programs
in order to provide a clean and consistent user interface. This was evident
long before CP/M and DOS were conceived in the fertile minds of their
developers; the problem exists in Multics, UNIX, XENIX, and many other
minicomputer and mainframe operating systems. Here is an example of the
problem.
Under UNIX, command-line options are introduced by a leading dash (-).
An option letter selects a particular program option and may require an
option argument. Therefore, -w80 might be used to select a page width (w)
of 80 columns when used with a printer program. The option argument--80, in
this case, but other values may be allowed--must be entered if the w option
letter is used.
An early version of UNIX (Version 7 in commercial circles) requires
that options to the print command, r, be invoked individually, each having
its own option flag and letter. Thus, printing a file in multiple columns
without the usual header and footer lines requires a command constructed
according to the following template:
pr -2 -t filename
in which the -2 and -t arguments specify the desired options, and filename
is the lone filename argument.
Later versions of UNIX, starting with the release of System III,
permit the option letters to be combined and introduced by a single option
flag, so a command of the form
pr -2t filename
does precisely the same job as the first one shown. A command typed
according to the older specification (separate option flags) is also
allowed.
To complicate matters, some programs enforce the requirement that
option letters that take arguments must have a space or tab between the
letter and the argument while other programs require no white space (-w 80
vs -w80, for example). These variations are the result of no small amount
of anarchy among the developers and are the source of the confusion from
which standards eventually evolve.
So why should this matter to DOS programmers and users? Well, for one
thing, DOS resembles UNIX in many ways and emulates many UNIX features and
principles. For another, the same kinds of problems already exist under
DOS. Some commands take option switches, usually indicated by the forward
slash (/), but some programs accept the dash (-) as well. The option
switches usually are placed after the command name but before any
filenames, yet some commands require that option switches follow everything
else on the command line. Still other programs don't care where the option
flags and arguments go--they simply scan the entire command line before
doing anything.
Under recent versions of UNIX and XENIX, a utility function named
getopt() is used to process option flags and arguments in a consistent way.
The getopt() function can be used by any DOS program that accepts command-
line options and arguments. We will apply it to a sample utility program,
CAT, which concatenates files (one or more) onto the standard output
stream.
Figure 6-1 on the next page shows us how getopt() takes command
lines and sensibly parses options and arguments. We have already seen
in Chapter 3 how information in the DOS command tail is made available
to a program via the argc and argv parameters to the main() function and
how a program can be written to use the information in the command tail. We
have not yet, however, agreed on a format for commands.
For the programs in this book, we will present command lines according
to a common template. The command name always appears first (this is an
operating system requirement), followed by options, if any, followed by
other data, typically filenames, if any. Options are introduced by a
leading dash. Such a template works well for most of the commands we will
encounter. However, with the more complicated commands, we probably will
provide a menu-style interface instead of a command-oriented interface.
C> cat -s *.c linebuf .h
░░░░░░░░░░░░░░░░░► the command tail
is processed by setargv()
══════════════════════════════════════════════════════════════════════════
argc
┌────┐
│▒6▒▒│ ┌────┬────┬────┬────┐
└────┘ ┌───────►│ C │ A │ T │ \O │ (DOS version 3.0 and later)
│ └────┴────┴────┴────┘
│
│ ┌────┬────┬────┐
┌────┐ │ ┌─────────►│ - │ s │ \O │
0 │▒▒■─┼──┘ │ └────┴────┴────┘
├────┤ │
1 │▒▒■─┼─────┘ ┌────┬────┬────┬────┬────┬────┬────┬────┐
├────┤ ┌────────────────►│ F │ I │ L │ E │ 1 │ . │ C │ \O │
2 │▒▒■─┼────────┘ └────┴────┴────┴────┴────┴────┴────┴────┘
├────┤
3 │▒▒■─┼────────┐ ┌────┬────┬────┬────┬────┬────┬────┬────┐
├────┤ └────────────────►│ F │ I │ L │ E │ 2 │ . │ C │ \O │
4 │▒▒■─┼─────┐ └────┴────┴────┴────┴────┴────┴────┴────┘
├────┤ │
5 │NULL│ │ ┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐
└────┘ └─────────►│ l │ i │ n │ e │ b │ u │ f │ . │ h │ \O │
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘
FIGURE 6-1 ▐ Expanding ambiguous filename specifications
Ambiguous filename arguments are expanded automatically by the setargv
routine if it is included in the link list. The argument vector array,
argv, contains pointers to strings in memory. The DOS command tail is
everything that follows the command name on the DOS command line, including
the initial space or tab that separates the first option or argument from
the command name. Although the DOS command tail is limited to 128 bytes,
ambiguous file names can be expanded to produce effective command tails of
much greater length. The figure illustrates what the pointers and strings
would look like for the command line
cat -s *.c linebuf.h
if the current directory contains the two C source files, file1.c and
file2.c, the header file linebuf.h, and possibly other files. Consider what
this command might produce if invoked in a directory with many C source
files.
The two C source-file names are stored with all letters converted to
uppercase because DOS always converts to uppercase when it expands names.
The -s option and the header file name are stored as literal copies because
no expansions are required. The command name, available only under DOS 3.00
and later versions, is presented with all letters converted to uppercase
regardless of how they are typed by the user, again because of a DOS
convention.
The following source for getopt() was provided by AT&T. It is the
current version of getopt() that is available from the AT&T UNIX System
Toolchest.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 127 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
The following synopsis and description of getopt() are based on its
observed behavior under UNIX System V, Release 2, the current UNIX
standard. If we use this standard, programs developed under DOS that do not
employ any hardware-dependent code will be readily transportable to
UNIX/XENIX (and vice versa).
The getopt() function receives an argument count and an array of
pointers to argument strings (usually copies of the argc and argv
parameters of main()) and a string variable that is a list of allowable
option flags. Single letters (uppercase and lowercase letters are unique)
and single-digit numbers are acceptable option letters. If a valid option
letter in the list is followed by a colon, getopt() expects to find a
following option argument.
In addition to the parameters passed to the function, the interface to
getopt() involves three global variables. The optind variable, the option
index, is an integer that is initialized to 1; it keeps track of which
option is currently being processed. A character pointer, optarg, is set to
NULL unless the option being processed takes an argument, in which case
optarg points to what should be the required argument. The third global
variable is opterr. It is initialized to 1, which causes getopt() to report
errors such as an option flag that is not a member of the passed list or a
missing argument. Our programs can shut off error messages from getopt() by
setting opterr to 0. Although optind and optarg must be declared as
external variables in our programs, we need to declare opterr only if we
intend to change its value.
COMMENT
The UNIX and XENIX manual pages for getopt() contain an
error. The value of optind does not default to 0. It is
initialized to 1. Also, previous versions of getopt()
emitted error messages when opterr was 0. The latest
versions of getopt() have reversed the sense of the
opterr variable and emit error messages if opterr is 1
(the default value).
The pseudocode for getopt() reveals the complexities of handling user
input in a general way. The primary difficulty is in parsing optional
arguments that may or may not be separated from the associated option
letter by white space.
index to first argument following command name
(done before the first call to getopt())
if (no arguments OR
arg not an option OR
flag without option letter)
return EOF
else if (special end-of-options indicator)
skip over argument
return EOF
if (option letter not in option string)
if (opterr is non-zero)
print error message
return '?'
if (option letter followed by ':' in option string)
if (option argument found)
set optarg to start of argument
return option letter
else
if (opterr is non-zero)
print error message
return '?'
else
set optarg to NULL
return option letter
To show how the getopt() function is used in practice, we will now
write a program that uses getopt() in its simplest form. Later in this
chapter and in other chapters, we will use getopt() in more complex
argumentprocessing situations.
The CAT program, presented next, uses getopt() to process a single
option. CAT is used to concatenate files. It accepts a list of zero or more
files and produces a continuous data stream on its output. If only a single
file is named, CAT simply lists the file's contents on the screen. Figure
6-2 on the next page is the manual page for CAT.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the figure found on page ║
║ 132 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
FIGURE 6-2 ▐ Manual page for CAT
The source for CAT follows:
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 132 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
CAT duplicates its input stream on its output stream by calling
fcopy(). The input comes from files named on the command line or from stdin
if no files are named. Output is to the standard output stream, stdout.
Therefore, CAT is minimally a filter. The transformation it performs is to
merge data streams into a single sequential output stream. CAT is most
frequently used to list the contents of one file on the console screen or
to combine a group of files into a single file. It may be used as the
source node of a pipeline command, either taking its input from named files
or the console.
Following is the C source for fcopy().
/*
* fcopy -- copy input stream (fin) to output stream (fout)
* and return an indication of success or failure
*/
#include <stdio.h>
int fcopy(fin, fout)
FILE *fin, *fout;
{
int errcount = 0;
char line[BUFSIZ];
char *s;
while ((s = fgets(line, BUFSIZ, fin)) != NULL)
if (fputs(s, fout) == EOF)
++errcount;
if (ferror(fin))
++errcount;
return (errcount); /* 0 if all went well */
}
Since both getopt() and fcopy() are going to be useful in other programs,
add them to the utility library.
Now that we have a function that lets us process command lines in a
consistent way, we can move on to other aspects of the user-machine
interface. We will now look at methods of producing machine-independent
time measurements and delays.
Timing Functions
The routines and programs in this and the next subsections show how to
time events, produce time delays, and create sounds. All depend in some way
upon the PC's built-in timer circuits. We begin with a program called
TIMER, which uses some of the ctime subroutines in the standard library
that we examined in Chapter 4.
TIMER is an external program that lets us time intervals from the DOS
command level and from within batch files. The program is handy for timing
events lasting from a few seconds to a few days. It is not suitable for
shorter intervals because of the way it is implemented. A typical use
consists of starting a timer, running a program or performing a task, and
then calling the timer again to check the elapsed time. Figure 6-3 is the
manual page for TIMER, and its source code is in timer.c.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the figure found on page ║
║ 136 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
FIGURE 6-3 ▐ Manual page for TIMER
The source for TIMER follows:
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 137 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
The source for TIMER illustrates a slightly more complicated use of
getopt() than we saw in the CAT program. In addition to handling more
option flags, TIMER has one option flag that requires an argument. The
option -f takes an argument that specifies a filename; if -f is present,
the next argument is presumed to be the required argument. The user can
insert extra white space characters between the option flag and the
argument, but it's not required. When getopt() detects the -f option, it
immediately sets optarg to point to the start of the argument string. The
program calling getopt() then interprets the argument.
The TIMER program uses a special area in memory that is called the
intra-application communications area (ICA). The ICA resides in an area of
memory (addresses 4F0--4FF hex) that is reserved by DOS. The ICA is a mere
16 bytes in length, but it's big enough to store four long integers, one
for each of four separate timers. Few commercial programs use the ICA, but
TIMER should protect itself against corruption of its data. Therefore, each
timer value is represented by the lower 30 bits (0--29) of a four-byte
long. The top two bits are used for status and identification purposes. The
ID bit, bit 30, must be a logical value of 1. The most significant bit, bit
31, is also set to a logical value of 1 to indicate that a timer value has
been stored. If these bits do not have the correct values, attempts to show
an elapsed time produce an error message.
The time() library function returns a long integer that is the number
of seconds since the epoch (00:00:00 on January 1, 1970 for ctime
subroutines). There are 31,536,000 seconds in a normal year (add one day,
86,400 seconds, for a leap year). Using the first 30 bits of a long to hold
the time value gives us a range of 34 years, so TIMER will function
correctly until 2004--by which time I hope to have a new computer and
operating system. We can extend the span to 68 years by not using a
separate ID bit, but that increases our risk of returning incorrect elapsed
time values if some other program happens to use the ICA.
TIMER is a small-model program because it requires little code space
and even less data space. However, the ICA is in the BIOS segment, not the
program's data space. TIMER uses the movedata() library function to read
and write data in the ICA. Because movedata() requires two segmented
addresses, TIMER also calls the segread() library function to get the data
segment register value. The other address needed by movedata() is in the
BIOS data segment. I chose to use 0x4F as the segment value (ICA_SEG) and
an offset of 0 for the ICA, but these can be reversed if you prefer.
When the -e flag is given, TIMER calls interval() with the number of
seconds between "now" and when the timer was started. The saved timer is
not altered. The interval() function translates the elapsed seconds into
ASCII text using the form hh:mm:ss. If no timer number is specified, timer
0 is used. The user may restart an interval timer by using the -s (start)
option along with the timer number.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 141 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
I have found TIMER to be most useful for recording work time against
project accounts in my consulting practice and also as a means of measuring
program execution times. Using the -f option, all data that would normally
appear on the console screen is instead appended to the named file, which
becomes a handy record of activities. When testing program execution
speeds, I minimize the effects of disk loading times by setting up a
virtual disk for the batch file, the TIMER program, and the data file.
Everything else is run from a hard disk or a floppy disk so that loading
and access times for the program will be taken into account. This beats
using a stopwatch and ink on paper.
The PC Timer and Time Delays
Following is a brief description of the timer circuits in the PC and a
look at some of the internal programs that keep track of time in various
ways. Refer to Figure 6-4 while reading through this description. The
figure shows the basic elements of the PC's timer and speaker-control
circuits. We can ignore the speaker elements (shaded boxes) for now
and concentrate our attention on the clock signal generator and
timer/counters 0 and 1.
╔═════════════════╗
║ ┌───────────┐ ║
┌───────────┐┌─╫─►│clk │0 ║
│1.19318 MHz││ ║ │ out├──╫─────────► timer interrupt
│from system││┌╫─►│gate │ ║
│clock │││║ └───────────┘ ║ ┌───────► RAM refresh
└─────┬─────┘││║ ║ │
▼ ││║ ┌───────────┐ ║ │
•──────•┼╫─►┤clk │1 ║ │ AND gate Low-pass /│
│ tied │║ │ out├──╫─┘ ┌── driver filter /▒│
│ high ─•╫─►│gate │ ║┌───►│▒▒▒\ ┌──────┐ ┌──────┐ ┌─┐▒▒│
│ ║ └───────────┘ ║│ │▒▒▒▒)───►│▒▒▒▒▒▒│─►│▒▒▒▒▒▒│─►│▒│▒▒│
│ ║ ║│ ┌─►│▒▒▒/ │▒▒▒▒▒▒│ │▒▒▒▒▒▒│ └─┘▒▒│
│ ║ ┌───────────┐ ║│ │ └─ └──────┘ └──────┘ \▒│
└────────╫─►│clk ▒▒▒▒▒▒▒│2 ║│ └──────────────────────┐ \│
║ │▒▒▒▒▒▒▒▒out├──╫┘ ╔════════════════════╪══════╗
┌─╫─►│gate▒▒▒▒▒▒▒│ ║ ║ port 61 hex │ ║ speaker
│ ║ └───────────┘ ║ ║ ┌─────────────────┬┴─┬──┐ ║
│ ╚═════════════════╝ ║ │▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒│1 │0 │ ║
│ 8253-5 timer/counter ║ └─────────────────┴──┴┬─┘ ║
│ ╚═══════════════════════╪═══╝
└────────────────────────────────────────────────┘
8255 programmable
peripheral interface (PPI)
FIGURE 6-4 ▐ Timer and speaker control circuits
The primary clock rates for the system unit and peripheral interfaces
are derived from a high speed crystal-controlled oscillator, an 8284A
device. One of the output signals is divided down to 1.19318 megahertz
(MHz) and is fed to the 8253-5 timer/counter on the CLK input of all three
channels. (An 8254-2 timer/counter circuit is used in the PC/AT.) Channel 1
is used to refresh main memory; we should not alter this channel in any
way. Channel 2 will be described when we cover sound generation. Of primary
interest to us now is channel 0, which is used to provide a system-wide
timer interrupt (interrupt 8). This interrupt is called a clock tick.
A clock tick occurs at a rate of about 18.2 per second, or one
approximately every 55 milliseconds. The number of ticks per second is the
timer input clock rate, 1.19381 MHz, divided by 65536, which is the
divisor on timer channel 0. Other divisors may be used to produce different
interrupt frequencies.
Each timer/counter channel contains a 16-bit counter, a pair of 8-bit
latches to hold the starting count, a pair of 8-bit output latches, and
control logic. Each input clock pulse decrements the value held in the 16-
bit counter until the value is 0. Setting a count of FFFF hex gives us a
divisor of 65535, which is one less than needed. If, however, we set the
starting count to 0, the first input clock pulse will cause the counter to
go from 0 to 65535 (0-1=-1; all bits set to logical 1). This technique
produces an effective divisor of 65536, the correct value, because the
counter is treated as an unsigned integer. Consult the Intel timer/counter
documentation for more information on how it works and how it may be
programmed.
The ticks are used by the ROM BIOS to update the time-of-day (TOD)
clock, which is stored as the number of ticks since midnight. Therefore,
any program that modifies the count on timer channel 0 must compensate for
the change to maintain the tick rate seen by BIOS. We can use the same 18.2
ticks per second as the basis of some machine-independent timing functions
to produce delays and to create sound.
We may want to build delays into our programs for a variety of
reasons. A program that has a banner frame (I like to call it the "ego
frame") must be seen to be appreciated, so it is appropriate to hold it on
screen for a few seconds. Automated "slide shows" need controllable delays
between frames to pace the presentation. Accurate short-duration delays are
also needed to produce audible sound effects.
When there was only a single PC model, many programmers were not too
careful about how time delays were produced. We often depended upon the
characteristics of the machine by using loops that went through the right
number of iterations to waste the required time. Of course, faster
processors and higher clock rates in many of the newer computers have
shrunk delays produced this way to as little as an eighth of their former
selves. We are now in need of a way to produce reliable, machine
independent time delays.
The delay() function uses the computer's timer to generate delays that
are consistent with the entire family of IBM PCs and portable to most
compatible machines. In order to produce the required waiting period, the
delay() function converts the specified period into a number of timer
ticks. It does so by multiplying the period by TICKRATE, which is defined
in timer.h in the \include\local directory. It then adds that amount to the
current number of clock ticks obtained from the time-of-day counter and
stores the sum. Then getticks() is called repeatedly by delay(). The
delay() function exits when the value returned by getticks() equals or
exceeds the target value.
The getticks() function queries the TOD clock and returns the current
time of day as a count of clock ticks. One modification is made when the
TOD clock rolls over from one day to the next: Since the TOD clock count is
reset to zero and a flag is set to indicate the day increment, the
getticks() function adds a day's worth of ticks (1,573,040 [1800B0 hex]) to
the count it returns if the flag is nonzero. Since all machines in the IBM
product line (and most compatibles) use the 1.19381 MHz timer clock rate,
the delay period is the same on all of the machines and is totally
independent of the CPU speed and master clock rate of the machine.
The following files contain the sources for the header file, timer.h,
and the functions delay() and getticks():
/*
* timer.h -- header for timer control routines
*/
/* timer clock and interrupt rates */
#define TIMER_CLK 1193180L
#define TIMER_MAX 65536L
#define TICKRATE TIMER_CLK / TIMER_MAX
/* timer port access for frequency setting */
#define TIMER_CTRL 0x43
#define TIMER_COUNT 0x42
#define TIMER_PREP 0xB6
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 145 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 146 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
The delay() function has general applicability and will be used in
other programs. We need to put it someplace that is easy to access. Because
delay() is based on the getticks() function, which in turn uses the ROM
BIOS, we will add both of these functions to the bios library. And, of
course, timer.h takes a permanent home in \include\local.
Audible Feedback
We now turn our attention to sound generation. The first thing to
notice about sound is that it irritates some users. Also, sound is totally
inappropriate in some settings. Therefore, we must provide a way for the
user to disable sound. The programs in this book will use a variable,
usually the global variable Silent, to record the user's preference
regarding sound. If Silent is logically TRUE, our program will be mute.
The shaded boxes in Figure 6-4 on page 143 show the PC
sound system. Notice how the sound system is built upon channel 2 of the
timer subsystem and a couple of I/O ports in the 8255-5 programmable
peripheral interface (PPI) device. The timer/counter produces squarewave
signals that are amplified and filtered, then passed to the speaker. The
AND gate ahead of the driver is controlled by bit 1 of the PPI port 61 hex.
In addition, bit 0 of the same port controls the gate lead of timer channel
2. Thus, the PPI can be used to turn sound on (both bits a logical 1) or
off (either bit a logical 0). The advantage of using the timer to produce
sound is that the sound plays in the "background," and does not steal
precious cycles from the CPU.
Alternatively, we can disable the timer by setting port 61 hex, bit 0
low, and pulse the speaker directly under program control via bit 1. The
problem with this approach is that the program can do nothing else while it
attends to the speaker. We will not use this approach.
The file sound.h, also in \include\local, is the header file for our
sound routines. It contains definitions and macros used to control the
speaker's state. SPKR_ON sets the low two bits in the peripheral interface
port to logical 1s. This enables the timer and passes signals through to
the speaker. The SPKR_OFF macro sets both bits low, turning the speaker off
by robbing it of input signals.
/*
* sound.h -- header for sound routines
*/
#define PPI 0x61
#define SPKR 0x03
#define SPKR_ON outp(PPI, inp(PPI) | SPKR)
#define SPKR_OFF outp(PPI, inp(PPI) & ~SPKR)
As noted already, the speaker can operate independently, letting us
put sounds in the background. We can demonstrate this with a simple control
program, SPKR. This program turns the speaker off if it is called with no
arguments and on if it receives any other number of arguments. The source
for this test driver is contained in spkr.c (on the following page).
/*
* spkr -- turn speaker ON/OFF
*
* no args => OFF
* any arg(s) => ON
*/
#include <local\sound.h>
main(argc, argv)
int argc;
char **argv;
{
/* turn speaker on or off */
if (argc == 1)
SPKR_OFF;
else
SPKR_ON;
exit(0);
}
The program has one problem--it does not set the pitch of the tone the
speaker emits. This must be done by another program, TONE, a simple test
driver that lets us set the pitch from the DOS command line. TONE takes a
single argument that specifies the desired frequency in hertz. The usable
range for the PC's internal speaker is about 40 Hz to a little more than 6
KHz. Higher frequencies can also be used; however, these may cause some
speakers to produce barely audible tones or clicking sounds. TONE calls
upon a low-level routine, setfreq(), to calculate the needed divisor for
the specified frequency and to set up the timer's channel 2, from which the
speaker derives its input. The setfreq() function includes the timer.h
header file which contains the declarations for the port addresses and
values needed to set the frequency of the timer.
The code for TONE and setfreq() follow:
/*
* tone -- set the frequency of the sound generator
*/
#include <stdio.h>
main(argc, argv)
int argc;
char **argv;
{
extern void setfreq(unsigned int);
if (argc != 2) {
fprintf(stderr, "Usage: tone hertz\n");
exit(1);
}
/* set the frequency in hertz */
setfreq(atoi(*++argv));
exit(0);
}
/*
* setfreq -- sets PC's tone generator to run
* continuously at the specified frequency
*/
#include <conio.h>
#include <local\timer.h>
void
setfreq(f)
unsigned f; /* frequency in hertz (approximate) */
{
unsigned divisor = TIMER_CLK / f;
outp(TIMER_CTRL, TIMER_PREP); /* prepare timer */
outp(TIMER_COUNT, (divisor & 0xFF)); /* low byte of divisor */
outp(TIMER_COUNT, (divisor >> 8)); /* high byte of divisor */
}
To use these programs to demonstrate some of the capabilities of the
sound system, compile and link them, and then type
TONE 1000
SPKR ON
This sets an audible pitch and turns on the speaker. You can do other work
and the tone will continue to play unless some other program turns off the
speaker. Typing
SPKR
without any arguments turns off the speaker.
You can simulate a burglar alarm sound if you have a perverse
nature. Key in the code for sweep.c, compile it and link it with the
setfreq.obj file, and type
SWEEP
To stop the sound before the police arrive, press any key.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 150 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
All this noise making is wonderful for something, I'm sure, but we
will want finer control of the sound subsystem in our programs. We obtain
that control via the sound() function, our high-level sound interface
function. Using sound(), we can produce distinctive sounds such as a
confirmation signal (confirm()), a warning signal (warn()), and many
others. The source for the sound() function is contained in sound.c.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 151 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
Notice that the speaker is turned on, continues to sound during the
specified interval, and is then turned off for each tone being generated.
Earlier demonstration programs turned on the speaker once, issued a series
of pitch changes, and then turned off the speaker. Either way is OK, but
the approach used by the sound() function is more versatile for our
purposes.
The program SOUNDS contains a few sample audible signals selected via
a simple menu. You can use these as a starting point and create some of
your own.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 152 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
Don't forget to check the user's preference before "making a joyful
noise." Give the user a command-line argument to disable sound: Use -s, for
example, to set Silent to TRUE if it is Boolean, or to a nonzero value if
it is a simple integer. Then a simple conditional such as
if (Silent == FALSE)
warn();
will prevent the sound produced by warn() from being heard by an ungrateful
audience!
Getting User Input
Next we turn our attention to the topic of getting input from the
user. In our programs, we sometimes need to ask the user for a filename, a
drive letter, or some other piece of important information. The goal of our
next task is to design a general purpose input routine that prompts the
user with a brief instruction and gathers a reply. It sounds simple enough:
Just use printf() to display the prompt and scanf() to collect the
response. This may sound reasonable, but this approach has some unnecessary
limitations and some really serious problems.
The scanf() function is not appropriate because it is designed for use
with formatted data--the kind that is produced automatically by a program,
not entered by hand. Besides, users are notorious for breaking things,
accidentally or on purpose, and scanf() can be easily broken, even by the
well-intentioned user who simply makes a typing mistake. We need something
more versatile.
Programs that have a lot of information on the screen may run short of
screen space, so to make things interesting, we will require that the
response field be able to take a response that is larger than the
displayable field width. The response field must, therefore, be a window
onto the user's response and must scroll sideways to permit the user to
view and edit the response text.
Figures 6-5A and 6-5B depict what we are trying to do in terms of
screen presentation. We will use the BIOS video routines developed in
Chapter 5 to control screen appearance and the DOS keyboard routines
(also Chapter 5) to gather the user's input. Editing operations include
the usual destructive backspace (<-) and single-character delete (Del). A
character is inserted into the buffer by pushing all characters from the
cursor to the end of the line, to the right one position, and by putting
the input character in the vacated spot. The cursor moves to the right one
position.
prompt field reply field
┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐
│░│░│░│░│░│░│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│
└─┴─┴─┴─┴─┴─┼─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┤
│ │
LINEBUF └───┐ └───┐
headers line buffers │ │
┌─┬─┬─┐ ┌─┬─┬─┬─┬─┬─┬─┬─┼─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┼─┐
│■│■│■┼─►│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│
└┴┬┴─┘ └─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘
│ │
┌┼┬▼┬─┐ ┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐
│■│■│■┼─►│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│
└─┴─┴─┘ └─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘
FIGURE 6-5A ▐ Memory and screen of getreply()
TSTREPLY File: c:\book\06user\timer.c
───────────────────────────────────────────────────────────────────────────
FIGURE 6-5B ▐ Sample screen: Getting a pathname
Cursor motions include moving to the beginning of text (Home), to the
end of text (End), and left and right by one character (left and right
arrows). Pressing Esc aborts the input operation and causes getreply() to
return a NULL pointer to the calling function; pressing Enter anywhere in
the line returns a pointer to the reply string. The calling function is
responsible for validating the reply it receives.
If getreply() is to be used more than once for a given reply field in
a form or an interactive program, it would be wonderful if it could recall
the most recent response, or better yet, scroll back and forth through a
list of previous responses. Rather than put all of the complications
associated with list management into getreply(), we will have the calling
function pass a pointer to an array of line buffers that it sets up. Then
getreply() permits the user to scan up and down the list of line buffers by
using the up and down arrow keys.
This design may seem to be a tall order to fill, but it is actually
not that difficult. Several cooperating functions give us a very nice user
interface routine and teach us a few things about windows and editing, too.
We will name the routine getreply() and try to generalize it enough to make
it useful in many programs.
We will call on the standard library to help us with a few tasks. The
memory function, memcpy(), copies strings within a single segment. The
source and destination strings will be allowed to overlap to permit
sideways scrolling. The memcpy() function guarantees that no characters
will be lost in an overlapping copy operation. The reply string buffer
within getreply() is initialized to null bytes (\0) before it is allowed to
receive any user data. This action guarantees that the resulting string
will be properly terminated.
After getting a preliminary version of getreply() working and grousing
over a few of its idiosyncracies, I arrived at the following sources for
the function, which behaves rather well when challenged by even the most
hostile users. The line buffer data structure is defined in linebuf.h, and
the source for the getreply() function is in getreply.c.
/*
* linebuf.h
*/
typedef struct lb_st {
struct lb_st *l_next;
struct lb_st *l_prev;
char *l_buf;
} LINEBUF;
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 157 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
A few parts of the code appear to be a bit dense and need some
explanation. The dimension of the reply field is calculated by subtracting
the prompt field width from the working area allowed to getreply() by the
calling function. Values are not error-checked, so be sure to pass
something that makes sense.
The bulk of the work is done in the while statement that loops as long
as the Return key (or Enter as IBM prefers to call it) is not pressed. If a
key is a printable ASCII character, it is inserted into the buffer. If not,
it is checked to see whether it is an editing or cursor movement command.
Valid commands are executed and invalid keypresses evoke an error response.
Controlling the window position relative to the input text is handled
by a pair of pointers. The character pointer cp tracks the current
input/editing position in the buffer, and wp, also a character pointer,
points to the first character in the buffer that will be displayed in the
reply field window. A test within the loop assures that the editing/input
position is always within the displayed reply window.
The getreply() function uses an external error message routine. This
practice assures the calling routine that nothing will be displayed
arbitrarily on the screen. The calling function has complete control as to
when and where the error message will appear.
One thing that angers me when I use some programs is having old error
messages still displayed on the screen. The code for getreply() prevents
messages from being displayed past their time. A message flag is set when a
new error message is issued and is cleared with the next keystroke. The
message handler is sent a null message, which is its hint to clean up the
message display area. It can ignore the hint, but it usually should not.
To see getreply() in action, compile and link TSTREPLY, and run it.
Although the program does little more than call getreply(), it gives us a
way to test getreply() and demonstrate its operation. It also provides all
of the code necessary to set up a message handler and manage a line buffer
array. The C source is in tstreply.c:
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 161 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
Chapter 7 Automatic Program Configuration
We saw in Chapters 3 and 6 how programs can be given optional
arguments to control their behavior in order to meet special needs. In the
case of CAT in Chapter 6, for example, the -s option causes CAT to remain
silent about missing files. The use of such a command-line option is a
manual means of configuring a program.
However, the use of command-line options is often an impractical
approach, particularly if a program uses many options. Users often will
avoid reading the manual page and therefore may give up on a perfectly good
program. What can be done about this?
One solution is to treat an additional program feature as you would an
extra nose on your face--avoid it if you can. No sense cluttering up a
simple and effective program with lots of little gargoyle-like appendages.
Try to keep a program simple and geared to doing a single task as well as
possible. If you believe that a program needs another feature to round out
its capabilities, see whether other users of the program agree. But avoid
the temptation to put in every feature requested by every user; the result
will usually be a program that nobody uses.
Assuming that there is no way to avoid adding that slick new feature
to your program, let's at least look at ways of automating things to make
using your software product easier for the "liveware." We can provide
automatic program configuration in several tidy, user-controlled ways,
including, but not limited to, using the program name and using external
configuration files.
Using the Program Name
As we learned earlier, the name used to invoke a program under
DOS version 3.00 and later is available to the program. We could,
therefore, use a program's name to alter its behavior. For example, if some
additional code were put into our CAT program from the previous chapter, we
could cause the program to become a "silent" CAT simply by making a copy of
CAT.EXE with the name SCAT.EXE. Then the command
C> scat filename
would have the same effect as the command
C> cat -s filename
used in our current design.
All we need to do is add these lines to cat.c just after line 46,
following the optional argument-processing loop:
if (strcmp(pgm, "SCAT") == 0)
Silent = TRUE;
Many other programs lend themselves to this form of configuration.
Under UNIX and XENIX, giving a program another name adds only a link to the
original file. No additional space is required for the program file's
contents. However, there is a price for doing this under DOS. Every
additional name for the same program file requires an additional directory
entry, a file allocation table entry, and storage space. Using aliases,
then, is not recommended except in rare situations in which the convenience
outweighs the lost disk space.
Using Configuration Files
There is another convenient way to configure a program. Like the use
of the program's name, it too provides automatic operation. Although it
also requires additional disk space, it typically uses only a single
cluster, not a complete duplication of the original program file.
In this method, variables and values are read from a file and are used
to initialize or update selected program variables at any time during the
operation of the program. Several levels of configuration files may be
used. An arrangement that works well in practice uses a global data file
that is located in a directory pointed to by a DOS environment variable.
For example, on my hard-disk-based systems, I use a single
subdirectory called c:\config as a convenient location for all
configuration files for the programs that accept external configuration.
Each of these files bears the name of the program it configures plus a .CNF
extension. Thus, c:\config\progname.cnf contains the configuration data for
the PROGNAME program. The DOS variable CONFIG is defined by the statement
set config=c:\config
in my AUTOEXEC.BAT file.
A local configuration file--one located in the current directory--
may override the global file. This permits directory-by-directory control
over program behavior. For instance, a user of a program that prints the
contents of a text file may want one type of behavior for program source
files and quite another for a "dumb-things-I-gotta-do" list.
The pseudocode for handling the configuration layers just described is
as follows:
initialize program control variables to built-in values
if (local configuration file is found)
overlay defaults with local configuration values
else if (specified DOS variable found)
if (global configuration file found)
overlay defaults with global values
else
return NULL file pointer
update variables per command-line instructions, if any
It is not an error for either the local or the global configuration
file, or both, to be missing, but it is an error for program-control
variables not to be initialized by the program before they are used. The
responsibility for the first step in the process--providing default
initialization--is left to the main program. The local and global steps can
be performed by a new addition to our utility library, the fconfig()
function. We still permit command-line arguments to override other settings
on a per-invocation basis, as indicated by the last step in the process
described by the pseudocode.
Here is the C source code for the fconfig() function, which seeks a
configuration file and, if it finds one, returns a FILE pointer to the
file, which is open and ready to read from the top.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 168 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
The calling function must specify the name of an environment variable,
which may contain a global-configuration directory name, and the name of
the configuration data file itself.
The name of the DOS variable that may hold a configurationdirectory
pathname is passed as a parameter to fconfig(), not hard-coded into the
function. This gives a calling program the latitude of specifying any
environment-variable name it wants. The pname string variable within
fconfig() is large enough (MAXPATH plus room for a terminating null byte)
to hold the longest pathname permissible under DOS.
The environment variable, if it exists, holds the pathname that
represents the chain of directories leading up to the place where the
global configuration file would be located. The fconfig() function still
must append a backslash and the filename to complete the full pathname of
the configuration file. (Because the backslash is the C "escape" character,
two backslashes are needed to get one out, the first turning off the
special meaning of the second.)
Nothing we've done so far places any restrictions on the type of data
that can be stored in and read from the configuration data file. The best
format is highly dependent on the amount of data and what will be done with
it. If a lot of Boolean flags are used to control a program, the best
approach might be to use a binary format with all bits significant. If the
data will be typed in by a casual user, the best approach might be to use
strictly ASCII text. In practice, it turns out that the ordinary text file
is best because it is more easily transported from one system to another
without concerns about the native size of data words or the ordering of
bytes within data words.
All data read into a program from a configuration file should be
qualified in some way before being used to update program data. Data files
created by mere mortals often will contain errors, such as typos, incorrect
data types, out-of-range values, or too many or too few values. Data files
created by a custom-designed setup program are less prone to such problems;
however, checking all data wastes little time compared with the time it
takes to reboot a system that chokes on its input.
Printer Control Functions
Now we'll look at an example of program configuration that nearly
every PC user has a need for at one time or another. In this section of the
chapter, we will develop a set of printer-interface routines that are aimed
primarily at controlling the fonts of Epson and IBM dot-matrix printers.
Then, in the next section, we will use the printer routines as the basis of
a printer-control program that enables us to control the printer from the
DOS command line and from batch files.
Let's begin with a look at Epson and compatible printers, including
the original IBM PC printer. Many other printers use Epson-compatible
control codes, so the following routines are applicable without change to a
wide range of printers. For those printers that are not Epson compatible,
our interface accepts user-supplied configuration data from a file.
To simplify matters a bit, we will make a few rules. First, the
printer must be able to produce emphasized text without having to backspace
and retype the text repeatedly. Second, the printer must be able to
underline without having to back up and restrike the text with underscores.
Third, if the printer does not offer a particular mode (double-strike,
compressed, or expanded, for example), initializing the control codes for
the mode to null strings ("") must effectively turn off the mode in the
interface routines; no attempt is made to synthesize a mode from
combinations of other modes.
Limitations such as these would be deadly to a commercial printer-
control program, but they are acceptable here in our demonstration of
printer control because they simplify our task and make it more manageable.
If you need something more elaborate, you can use this program as a base
and add controls for paper length, top-of-form position, line-to-line
spacing, and many other features. The principles are the same as for the
font-control set described here.
The file printer.h in the \include\local directory contains the
default values for Epson MX/FX-series printers and a set of printer
variables used by the interface routines.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 170 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
The ASCII codes are used directly to control printer modes (SO, SI,
and so forth) or to build up control-code strings (ESC in conjunction with
other characters).
A set of font-type constants is defined so that each font type is
mapped to a single bit. This permits fonts to be "stacked" to form
composite fonts by using the bitwise OR. For example, ITALICS | UNDERLINE
combines the values 0x10 and 0x20 to request an underlined italic
font.
The file printer.c contains a set of three related printer-control
functions: setprnt(), clrprnt(), and setfont(). These functions use the
variables defined in printer.h to determine what control strings to send to
the printer. Here is the text of printer.c:
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 172 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
The setprnt() function has the Epson default-control strings hard-
coded into it. However, if the user wishes to use some other printer,
setprnt() calls fconfig() to look for a local or global printer.cnf file to
override these values. If a configuration file is found, each line is read
in and assigned to the next printer control-code variable. If too many or
too few codes are obtained, an error is indicated by a return value of -1.
A return value of 0 tells the caller all went well, but it is still
possible that the codes were received in the wrong sequence or that bad
codes were given in the configuration file. The setprnt() function cannot
detect such errors; the printer will simply not operate correctly if it
gets bad control strings.
The next function, clrprnt(), resets the printer to the normal font.
It does so by turning off each special font individually rather than by
using the hardware reset-control string. The hardware reset on most
printers resets the top-of-form and causes the paper to creep a bit each
time it is called. Because of this, I have elected to avoid using the reset
command entirely, although its value is contained in the configuration data
structure for possible future use. The clrprnt() function calls upon the
standard library function fputs() with the control strings needed to turn
off each printer font mode. It sends the strings to the output stream
specified by its only argument.
The setfont() function accepts two arguments: one specifies the
desired font combination, and the other specifies the destination stream.
The first thing setfont() does is call clrprnt() to cancel all currently
active print modes. Then it issues control strings for each of the desired
modes.
Most print-mode combinations are legal, but a few are not. In this
design, two font combinations are excluded. The setfont() function returns
a FAILURE indication if the caller requests the compressed mode combined
with either a double-strike or emphasized mode. Epson printers don't allow
these, but some other printers might. All other combinations of supported
fonts and print modes are permitted.
The control strings are emitted one at a time for each requested mode.
Thus the request
setfont(EMPHASIZED | EXPANDED);
in a program results in the control string for emphasized mode being sent
to the destination stream, followed by the control string for expanded
mode.
To permit ready access to the printer interface, we will compile it by
typing
msc print;
and then add the print.obj file to our utility library using the
command
lib\lib\local\util +print;
Now we can proceed to an example of how to use this simple but
effective printer interface.
Printer Control Program
My original purpose in designing the printer interface just described
was to gain some control over the printer from within my C programs.
(Chapter 9 is devoted to a general-purpose printer program that uses this
interface.) The approach I took gave me reasonable printer control from C
programs, and, with the small amount of extra work that we'll do next, from
the DOS command line and from batch files as well.
We now have the tools needed to control print modes, but getting
access to the printer from DOS involves one additional step: packaging the
basic control functions in a DOS program file that accepts option flags to
set desired modes. In addition, we will write a supplementary program that
allows us to send arbitrary text strings to the printer.
The MX program, originally named for its use with my trusty old MX-80
printer, controls the basic print modes via command-line arguments. The
program also works nicely with the Epson FX- and JX-series printers and
with many other compatible printers. The configurability of MX permits its
use with many other printers, too. A look at the manual page (Figure 7-1)
and source listing for MX shows that it is simple to use and equally simple
to program.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the figure found on page ║
║ 177 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
FIGURE 7-1 ▐ Manual page for MX
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 178 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
Each time MX is invoked, it checks for a configuration file. This can
take a little time on a floppy disk-only system. If you will be using MX
with nothing but Epson-compatible printers on a floppy-based system, you
may want to bypass the configuration step and build in the Epson default-
control strings for faster execution.
MX uses getopt(), which is now part of our utility library, plus all
of the printer-interface routines in the printer object file. Most of the
option flags are obvious; however, two require some explanation. The -o
option takes as an argument the name of a destination stream, presumably a
disk file, to be used in place of the standard output stream. This allows
us to capture the output of MX for diagnostic purposes or to create files
that can drive the printer directly as formatting scripts.
The -t option uses the ASCII formfeed character to eject a page (go to
top-of-form). This will work on nearly all modern printers. To accommodate
all printers, you may instead want to permit the use of a series of
newlines to get to the top of the next page. The PR program described in
Chapter 9 shows how to use either control mechanism.
The text of mx.mk tells the Microsoft MAKE command how to assemble the
MX program.
# makefile for mx program
LINC=c:\include\local
LLIB=c:\lib\local
mx.obj: mx.c $(LINC)
msc $*;
mx.exe: mx.obj $(LLIB)\util.lib
link $*, $*,, $(LLIB)\util;
The PRTSTR program is an auxiliary program that prints an arbitrary
string. PRTSTR also permits optional newline suppression, which gives users
the ability to construct printed lines in a mixture of print modes to
obtain various textual effects. The default for PRTSTR is to end the string
with a newline, which causes the printer to return to the beginning of the
current line and then move down one line. In addition, if it receives no
arguments, PRTSTR issues a single newline. Think of PRTSTR as an
intelligent substitute for the DOS ECHO command. The only advantage that
ECHO has over PRTSTR is that ECHO is built into the DOS COMMAND processor,
so it operates faster. However, because printers tend to move like molasses
in the Arctic, PRTSTR poses no problem in typical uses.
The manual page for PRTSTR (Figure 7-2) describes its
operation and application. The source code and makefile (prtstr.mk) are, by
now, quite predictable.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the figure found on page ║
║ 183 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
FIGURE 7-2 ▐ Manual page for PRTSTR
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 184 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
This very simple program has only one wrinkle that requires close
examination. When the newline-suppression option is selected, we suppress
not only the trailing newline, but also the single space that follows the
last argument string. This permits us to mix print modes within text
strings. We could, for example, print part of a word in boldface and
another part in italics to distinguish the fixed and variable parts of a
user's response.
# makefile for prtstr program
LINC=c:\include\local
LLIB=c:\lib\local
prtstr.obj: prtstr.c $(LINC)
msc $*;
prtstr.exe: prtstr.obj $(LLIB)\util.lib
link $*, $*,, $(LLIB)\util;
The SAMPLE.BAT file is a demonstration of printer control from a DOS
batch file. The sample text (Figure 7-3 on the next page) shows
off some of the many text combinations that can easily be obtained with
simple commands. Note the use of mixed print modes in some of the
lines.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 186 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
╔══════════════════════════════════════════╗
║ ║
║ Figure 7-3 is found on page 186 ║
║ in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
FIGURE 7-3 ▐ Output from SAMPLE.BAT
Please keep in mind that this is a minimal printer interface intended
to demonstrate means of automatic program configuration. Modern dot-matrix
and ink-jet printers offer many additional font and print-mode selections,
and laser printers permit even greater selections. Have fun making the most
of them!
Selection Functions
In configuring programs, some types of input require fairly elaborate
processing to get the input data into an internal form that is suitable for
program use. For example, the file select.c contains several functions that
work together to manage input data presented in the form of lists.
The next section of this chapter uses the SELECT package to process
tab settings. Our objective is to take the user-supplied list of columns
that represents tab stops and convert it into an array of integers that can
be used by a program to initialize its internal tab stops to something
other than the hardware tabs that occur at multiples of eight columns,
starting in column one (1, 9, 17, and so on). (Users generally count the
leftmost column as one, not zero.) The SELECT routines can be used to
process other types of lists, too. For instance, the routines will be used
in Chapter 9 to select pages to be printed by the PR program.
By way of example, one of our programs might receive a list such as 6,
20, 71--80 as a command-line argument. To be used by the program, the data
must be extracted from the list, which is a character string, converted to
integer form, and stored somehow for easy access. We'll deal with internal
data access soon. For now, let's just worry about the process of extracting
and converting the data.
The example specification shown has twelve members. Technically
speaking, each entry in the list represents a range, although some ranges
have the same minimum and maximum values. Thus the 6 entry may be
represented internally as min = 6, max = 6, whereas the 71--80 entry is
represented as min = 71, max = 80. We will use an array of structures to
hold these values in data storage that is private to the routines within
the SELECT module.
The SELECT module contains three related functions: mkslist() creates
a selection list from user data; save_range() is an internal function
(static) called by mkslist() to extract starting and ending values from
explicit and implicit range specifications; and selected() returns a
nonzero value to its caller to indicate that a given value is contained in
the selection list.
The routines in the SELECT module communicate through a global data
structure, Slist, which is defined as
struct slist_st {
long int s_min;
long int s_max;
} Slist[NRANGES + 1];
This allocates an array of NRANGES + 1 structures, each of which can hold a
minimum and a maximum value for a single range. NRANGES is currently
defined to be 10, but any reasonable number may be used. I have had no need
to specify more than 10 ranges in a list. Slist is declared globally here
for testing and demonstration purposes, but in practice it should be
declared static so that only the functions in the SELECT module can access
it.
The pseudocode description of mkslist() shows how a selection list is
parsed. The manifest constant BIGGEST is defined in \local\std.h to be the
largest number (65535) that can be stored in an unsigned integer. A global
variable, Highest, is initially 0 and is updated to the highest value
received in the list.
if (list in null)
set min = 0
set max = BIGGEST
else
while (next token of list is not NULL)
save_range(token)
set min in next to -1 (terminate list)
The save_range() function could be coded right in mkslist() because it
is only called once. However, by separating out the task of converting a
string token to a range of numbers, the apparent complexity of mkslist() is
reduced. The save_range() function is described as follows:
copy first number to a buffer
convert to long int
if (only one number received)
set max = min
else
if (second number is null)
set max = BIGGEST
else
copy second number into buffer
convert to long int
save in max
return (max)
This design lets the user specify open ranges such as 40--, which
gives a firm minimum number but lets the maximum default to a large number.
This is handy for telling a program to operate on all lines starting at 40
and continuing until the last line (of unknown number) is reached.
To determine whether a number is a member of the selection list, we
can call the function selected(), which returns a nonzero value if the
specified entry is a member of a range in the list. The selected() function
scans the selection list (Slist) one array element at a time until it finds
a range that contains its argument or until it finds a -1 flag that
terminates the list.
The source code for the selection functions is contained in
select.c:
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 189 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
In mkslist(), strtok() is called with a pointer that is initially set
to the start of the user-supplied list, to extract the first (possibly the
only) token. The pointer is then set to NULL for subsequent calls to
strtok() to extract additional tokens, if any, from the list. Acceptable
separators are comma, space, and tab. This gives the list-maker
considerable flexibility in forming the list string. If the list contains
embedded spaces or tabs, it must be quoted, so that DOS sees it as a single
argument. A range specification must not have any white space around the
hyphen (-), so that strtok() can parse it as a single token.
TSTSEL is a program that I used to debug the SELECT functions. It
accesses the Slist data structure directly, so that we can see what gets
stored in there for various list specifications. The operation of this
program is straightforward.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 192 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
The TSTSEL program produced the following output for one of my test
runs invoked with the command line
tstsel 1,2,3,5--10
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 193 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
The first line shows the argument containing the user's list. The next
block of lines displays the contents of the select list, Slist. Note the -1
sentinel that marks the list's end. The next block of lines shows the
results when selected() is used to query the select list to determine if a
value is a member of a range. We have shown the results as YES/NO
strings.
Setting Tab Stops
In processing files that contain text, it is sometimes necessary or
desirable to alter tab-stop positions, or to convert tabs to spaces or the
reverse.
The formula for determining regular tab-stop positions is 1 + kn,
where k is the requested interval and n is an integer in the range of 0
through some maximum number determined by the line length. Thus, for the
value k = 8, tabs fall at 1, 9, 17, and so on.
Variable tab stops are a bit more difficult to set up, but they have
their uses. We might appreciate being able to set the tab stops for a
FORTRAN program source file to 1, 7, 11, 15, 19, and 23, for example. We
can apply the selection-list technique just described to the setting of tab
stops at variable column positions in a line.
The file tabs.c contains the source code for three functions used to
set and test tab stops. A private array of characters holds the tab-stop
data. Each character represents a column position in a line and contains 1
if the associated column is a tab stop and 0 if it is not.
Two of the functions in the TABS module are used to set the tab stops.
The fixtabs() function installs tab stops at regular (fixed) intervals. The
interval is given as an integer argument to fixtabs(). The vartabs()
function works from a list to set tabs at variable intervals. We use the
SELECT functions to gather data from a user-supplied list and then convert
it to the needed form. A third function, tabstop(), is used to query the
Tabstops array.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 195 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
The TABS module uses C's natural base of 0 as the first element of an
array; therefore, all calculations are based on 0 as the leftmost column.
We accommodate the difference between the TABS module's view of the world
and the user's view of the world in the programs that handle the collection
and presentation of tab data.
The best way to see how this all works is to demonstrate it. The
showtabs.mk file tells MAKE how to put it all together. The SHOWTABS
program gets a user specification of the needed tab stops. A -f option flag
means SHOWTABS is getting a fixed interval specification, and a -v option
flag signals a variable tab list. These options are mutually exclusive, and
the parsing of the command-line options enforces the use of only one of
them.
#makefile for SHOWTABS program
LLIB=c:\lib\local
tabs.obj: tabs.c
msc $*;
select.obj: select.c
msc $*;
showtabs.obj: showtabs.c
msc $*;
showtabs.exe: showtabs.obj select.obj tabs.obj $(LLIB)\util.lib
link $* select tabs, $*, , $(LLIB)\util;
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 197 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
If SHOWTABS receives a variable list, it calls the SELECT functions to
do the necessary conversion from the string form of the program argument to
an array of integers that vartabs() understands. In the case of a -f
option, SHOWTABS uses atoi() to produce a single number, which is taken to
be a fixed interval specifier that is passed to fixtabs().
Once the Tabstops array is populated, SHOWTABS calls upon tabstop() to
display the status of every column in the first 80 columns of a line. A
plus sign (+) in the display marks columns that are nonzero multiples of
10. A T marks a tab stop, and a minus sign (-) marks all other positions.
Figure 7-4 on the next page shows the results of various tab-setting calls
to SHOWTABS.
C> showtabs -v 1,9,17,61-68
T-------T+------T--+---------+---------+---------+---------+TTTTTTTT-+
C>
FIGURE 7-4 ▐ Output from SHOWTABS
Because the functions in both the SELECT and TABS modules are
generally useful, we will add select.obj and tabs.obj to util.lib in
\lib\local.
In the next chapter, we will develop a group of utilities for DOS that
emulate some of the UNIX utilities; this will help ease the transition of
new users from one system to the other and will help those who are
constantly moving between the two operating systems, as is the case for
many software developers.
SECTION III File-Oriented Programs
Chapter 8 Basic File Utilities
This chapter presents a set of file-oriented utilities that satisfy
two primary goals. First, the commands give programmers who divide their
time between UNIX/XENIX and DOS a set of programs that make it easier to
switch back and forth between the two environments. The programs
essentially implement a UNIX/XENIX-style interface under DOS. Second, the
programs serve as models for other utility programs that you might like to
add to your toolkit. The programs employ many of the functions and
techniques we have developed thus far in the book plus a few that are
new.
The programs in this set include the following utilities:
═══════════════════════════════════════════════════════════════════════════
UTILITY ACTION
───────────────────────────────────────────────────────────────────────────
TOUCH │ update the modification times of files
TEE │ split a stream into two separate streams
PWD │ print the working directory pathname
RM │ remove files
LS │ list the files in a directory
───────────────────────────────────────────────────────────────────────────
Some of these programs perform functions already provided by DOS, but
in a way that is more familiar to UNIX/XENIX users. LS is similar to the
DOS DIR command. PWD does the job of the DOS CD command, when CD is typed
without an argument. The RM command provides some enhancements over the
standard DOS ERASE/DEL command. Other programs in this set are tools used
by programmers that have no DOS equivalents.
Update File Modification Time (TOUCH)
We have been using the MAKE command to assist us in creating and
maintaining programs. The MAKE command uses a file's last modification time
and instructions in a "makefile" as the basis of its decision-making
process. MAKE compares the modification time of an object with that of its
sources (specified in a dependency list in the makefile) to determine
whether an object is older than one or more of its respective sources. So,
when we modify a source file, every object that lists that source file in
its dependency list will be remade.
At times, we may want to force an object to be remade. We can do this
in several ways. We can edit a source file; even if we make no changes to
the file, the act of saving the file with an editor updates its
modification time. The source will then be newer than the object that is
created from it, so the object will be remade.
Another way to force an object to be remade is to remove it. MAKE will
discover that the object does not exist and will remake it from the recipe
in the makefile.
A third way is to use a program called TOUCH to update the file
modification time of the source file without making any other changes to
it. This will also cause MAKE to remake the object. TOUCH is particularly
handy when you want to force all the files in an entire project or
subproject to be remade. A single TOUCH command does the work of many
separate DEL or editor commands.
The following pseudocode describes how TOUCH works.
for (each named file)
update time
if (update not successful)
increment error count
if (verbose)
print error message
else
if (verbose)
print confirmation message
The manual page for TOUCH is presented in Figure 8-1.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the figure found on page ║
║ 207 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
FIGURE 8-1 ▐ Manual Page for TOUCH
The source code for the TOUCH program is in the file touch.c.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 208 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
Note the use of two dummy functions, _setenvp() and _nullcheck().
These functions reduce the final program code size slightly by eliminating
the code for environment processing and for checking attempts to reference
data through null pointers. The _nullcheck() function is inside a
preprocessor directive block that includes the code during debugging and
omits it when the program is finally compiled.
If we are really serious about minimizing code size, we will also
eliminate calls to printf-family functions because the formatting code is
rather large (about 2.5--8 KB, depending on the compiler and memory model
used). Using fputs() and fputc() to synthesize the fprintf() function, when
it is used to process only strings, will save several kilobytes in the
executable program. But be on the lookout for "hidden" formatting.
Depending on how the compiler supplier implements perror(), for example,
you may find that a call to fprintf() gets dragged in anyway, even though
you didn't use one directly in your code.
Tapping a Pipeline (TEE)
The TEE program is often called the "pipefitters' dream." The manual
page for TEE (Figure 8-2 on the next page) tells you why.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the figure found on page ║
║ 212 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
FIGURE 8-2 ▐ Manual page for TEE
TEE always writes a copy of its input to the standard output stream.
In addition, TEE attempts to open for writing any files given as command-
line arguments. If the option -a is given, TEE attempts to open the files
in append mode instead of write mode. By naming output files, the user
creates a multi-way split and sends copies of the same input stream to two
or more output streams. The pseudocode for TEE is
set file mode string
for (each name file)
open file per mode string
for each character received from stdin
copy the character to all open output streams
close all streams
TEE is especially useful in debugging programs. I use it to view the
output of a program on the screen while capturing a copy of the data stream
in a file for detailed inspection using DUMP (Chapter 10) or a text
editor. It is surprising what kind of garbage finds its way into the output
data stream of an ill-behaved program.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 213 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
The source code for TEE introduces the use of the standard library
function fcloseall(), which can be used to shut down all open streams
except the standard streams that were opened for you by the C run-time
startup system. The function returns -1 in the event that it cannot close a
stream. We report this to the operating system via the exit() function. The
ERRORLEVEL feature of batch files can be used to test the return value, but
DOS ignores it.
Print Working Directory Path (PWD)
The manual page for PWD (Figure 8-3) says all you need to
know about "what" but may leave you wondering "why." What's wrong with
typing CD without an argument to find out the name of the current
directory?
Nothing, really. But those of us who switch back and forth between DOS
and UNIX/XENIX have certain reflex reactions that cause problems. Under
UNIX/XENIX, a bare CD command reports nothing. Instead, it takes you back
to your "home" directory from anywhere in the directory hierarchy. The PWD
command is used to display the current directory pathname. I wrote a DOS
PWD command to make the two environments a bit more alike for some often-
used commands.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the figure found on page ║
║ 215 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
FIGURE 8-3 ▐ Manual page for PWD
The source for PWD, pwd.c, uses the standard library function getcwd()
to get the current "working" directory pathname. The first argument can be
the address of a user-supplied buffer that is long enough to hold a DOS
pathname (64 characters). If NULL is used instead, getcwd() creates its own
buffer, the length of which is specified by the second argument.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 216 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
Remove Files (RM)
One of the limitations of the DOS ERASE/DEL command is that it accepts
only a single file specification; if we want to remove several different
types of files, a series of ERASE commands must be issued. Another
limitation is the lack of an interactive deletion feature. RM does the job
of ERASE while adding these two useful features.
The manual page for RM is presented in Figure 8-4. The basic
behavior of RM is the same as ERASE except that you may provide any
number of exact and ambiguous filename specifications up to the limits
imposed by the DOS command line. Each command-line argument (following
command options, if any) is expanded if necessary. The -i option, when
used, puts RM into an interactive mode that is a great help in avoiding
the unwanted loss of files. Each deletion must be confirmed by a Y
response and a Return--more work for the user, but safer.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the figure found on page ║
║ 217 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
FIGURE 8-4 ▐ Manual page for RM
The pseudocode for the heart of RM is
for each file
if (interactive mode selected)
print file name
prompt user for reply (y/n)
read a line of input
if (no)
break
else
unlink file
if (error occurred)
print error message
The source for RM is composed of three functions: main(), do_rm(), and
affirm(). The return value of the call to unlink(), a standard library
function, is compared to -1, which flags an error condition. The perror()
library function is used to print an error message. There is no exit made
at this point because other files may still need to be removed.
The most likely error is an attempt to remove a non-existent file.
Also likely is the typical failure caused by attempts to access a floppy
drive that has no disk or has its door open. RM simply reports about non-
existent files; it lets the DOS error handler deal with drive problems.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 218 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
That's it for the easy utilities. The next is more powerful and useful
than the utilities we have just written and is appropriately more
complicated and more difficult to write as well. As the weight lifters say,
"No pain, no gain."
List Directories (LS)
Let's start with the manual page for LS (Figure 8-5 on the next
page); it shows us where we are headed. Note that this LS command has
six options. That may sound like a lot, but the UNIX/XENIX ls command has
more than three times as many! To keep things simple and usable, I elected
to leave many bells and whistles out of LS. Anyone who wants another 15
options is free to put them in.
The pseudocode for LS only hints at the complications of doing this
job in a DOS environment. Ignoring startup tasks (version control, option
parsing, setting up a Ctrl-Break handler, and so forth), LS follows this
general blueprint:
allocate a buffer for file data
allocate a buffer for directory data
if (no arguments)
get current directory pathname
else
for each named item
if (it's a directory)
add directory name to directory buffer
else if (it's a file)
add file name and data to file buffer
if (any files in file buffer)
sort entries
send file list to output
if (any directories in directory buffer)
for (each directory)
expand it to a list of files
sort entries
output list
The LS program resides in several files that are devoted exclusively
to LS-related chores. In addition, several DOS functions of more
farreaching application are used. These are new, and will be added to the
local DOS library that we created in Chapter 5.
The header file, ls.h, shows the data structures that describe file
data and the manifest constants needed to request and interpret file data.
Note that the LS program deals with information about files, rather than
the information that is in them.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the figure found on page ║
║ 223 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
FIGURE 8-5 ▐ Manual page for LS
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 223 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
The OUTBUF structure template is used to allocate storage for an
output buffer that contains the name of a file, its modification data and
time, the file size in bytes, and the file mode for each file in the
default directory and any named directories.
The basic substance of the LS program is in the file ls.c. It contains
a slew of include file directives, a set of global configuration variables,
the main() function, and a Ctrl-Break handler function. LS can produce
mountains of output; therefore, we must provide a convenient way to break
out if the user gets bored or finds the sought-after information part way
through the listing.
The main() function orchestrates everything, calling on other
functions to do most of the hard work. Its job is to collect the user's
requests and preferences and then to parcel out the work of getting file
data, expanding directory names to the contents of the directories, and
much more. Here is the ls.c file. Think what it might look like with 15
additional options!
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 224 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
Global variables are used to hold control information that is needed
by other functions in the LS program. Global variables should not be used
to pass data between functions.
The main() function calls on malloc() to allocate memory for the file
data structure and for an array of pointers to directory names. The
technique used here allocates a fixed amount. If that amount proves to be
too small to handle the job, additional space is obtained from realloc().
This guarantees that the expanded allocation is contiguous with the earlier
allocation (even if the whole block has to be moved to a new location in
memory), which makes it possible to use pointers and array indexes to
access elements in the data areas.
Two DOS functions, getdrive() and drvpath(), are called by main(). The
first gets the current drive number (0 = A, 1 = B, and so forth), and the
second appends to a copy of the current pathname onto a drive
specification.
/*
* getdrive -- return the number of the default drive
*/
#include <dos.h>
#include <local\doslib.h>
int getdrive()
{
return (bdos(CURRENT_DISK));
}
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 231 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
These two files should be compiled and the resulting object files
should be added to the dos.lib in the \lib\local directory.
After the file and directory buffers have been filled, main()
sequences the outputting of the data. All file data goes out first,
followed by the file and directory names associated with each named
directory. The simplest case is a list of files produced by a request
like
ls
which produces a one-column listing of the file and directory names in the
current directory. The output is done in a multi-column format if the
MULTICOLUMN variable is set to the value of logical 1 by using -C on the
command line or by renaming the LS program to either LC or LF under DOS
version 3.00 or later.
The output of LS is directed to the standard output stream, so it
appears on the screen unless it is redirected.
A default of 80 columns maximum is used to determine line length. This
fits easily on most display screens and printers. You might want to add a
feature that allows the line length to be controlled, either from the
command line or by a configuration file or an environment variable.
Output lists are sorted lexically by default. Options allow the user
to select reverse sorting (-r) and sorting by last modification time (-t).
The standard library sort routine, qsort(), does the sorting and calls on
ls_fcomp() to compare items being sorted. The file ls_fcomp.c contains the
source for the comparison function.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 233 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
After lists are sorted, they are sent to the output device by
functions in the ls_list module. Single-column output is done by
ls_single(). Multi-column output is handled by ls_multi(). The latter
function is the more interesting one. First, here is the source:
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 234 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
The ls_multi() function receives a sorted list of names, scans it once
to find the length of the longest name, and then outputs the list in
columns. The number of columns depends on the file data. Optimal column
width and number of columns are determined by the length of the longest
filename or subdirectory name to be output, thus maximizing the number of
columns output and minimizing the number of lines used. All names are
output in lowercase. Most people find words in all uppercase letters
difficult to read; if you don't, feel free to leave out the call to
strlwr().
The next function, ls_dirx(), is the workhorse to which falls the task
of expanding a directory name into a list of its subordinate file and
subdirectory names. This is not a trivial task. The file system under DOS
is fractured--part residing in a fixed directory at the "root" level of a
disk, and the rest in dynamically allocated subdirectories that are simply
special files in the file system. In addition, we must be concerned with
the use of a drive name (A, for example) as an alias for the current
directory on that drive. Note: The drive name is not an alias for the root
directory.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 238 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
To get the data it needs from DOS, ls_dirx() calls on three
DOS functions: setdta() to establish a disk transfer area in memory;
first_fm() to obtain the name of the first file that matches an ambiguous
filename specification; and next_fm() to get any subsequent matches to the
same specification. We will compile these three functions and add the
object modules to the DOS library.
/*
* setdta -- tell DOS where to do disk transfers
*/
#include <dos.h>
#include <local\doslib.h>
void
setdta(bp)
char *bp; /* pointer to byte-aligned disk transfer area */
{
union REGS inregs, outregs;
inregs.h.ah = SET_DTA;
inregs.x.dx = (unsigned int)bp;
(void)intdos(&inregs, &outregs);
}
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 241 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
In addition, ls--dirx() calls last--ch() to examine the last character
in the pathname. If the last character is not a \, a \ is appended to the
pathname. The last--ch() function is added to the \lib\local\util.lib
library.
/*
* last_ch -- return a copy of the last character
* before the NUL byte in a string
*/
char
last_ch(s)
char *s;
{
register char *cp;
/* find end of s */
cp = s;
while (*cp != '\0')
++cp;
/* return previous character */
--cp;
return (*cp);
}
To assist you in producing and maintaining the LS program, here is a
makefile, ls.mk:
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 243 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
The temptation to add features to LS is great. It (and its
equivalents, such as DIR) is a high-use program, typically accounting for
some 30 to 40 percent of command executions on a given system. Perhaps
because of its visibility, it is a frequent target for enhancements. Try to
avoid turning LS into a program that no one uses because it is too
complicated.
Chapter 9 File Printing
One essential programmer's tool is a program that displays or prints
the contents of text files--program source files, debugging listings,
program "map" files, and so on. The PR program developed in this chapter to
take care of this task is patterned after the UNIX utility of the same
name. It is designed to be flexible yet not encumbered with unnecessary
features. It provides a basic file-printing capability supplemented by a
collection of helpful options.
By default, PR produces output suitable for a generic line printer. It
uses only the standard format-effector codes (carriage return and linefeed)
and normal displayable characters (including space). The options enable you
to use special features of the Epson and Epson-compatible printers,
sequentially number lines, print a list of pages from a file, and control
basic page layout. Many options of PR allow the program to be used as a
filter, reading from its standard input and producing formatted pages on
its standard output or other output streams.
Program Specification
Figure 9-1 on the next page is the manual page that
describes the features and applications of PR. During the development of
the program, the manual page served as a wish list. A prototype of PR,
based on an earlier version of the manual page, was tested on some
"friendly users." Their feedback led to changes in the manual page and
subsequent revisions of the program that eventually resulted in the final
program presented here.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the figure found on page ║
║ 246 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
FIGURE 9-1 ▐ Manual page for PR
I will describe the program's basic features and its options in detail
as the PR functions are presented, but before doing that, we need to define
just what is meant by a "page" of output.
Figure 9-2 is a proposed page layout. The assumption that governs
choices of default values is that standard document paper is used. A
sheet of letter-sized paper is 8-1/2 inches wide by 11 inches long. (An
additional half-inch on either side of a sheet of pin-fed paper contains
the holes for the tractor feed, resulting in a raw page size of 9-1/2 by 11
inches.) A good guideline regarding page appearance is that at least 1/2
inch of unused space should be left on both sides and at the top and
bottom. Using less white space tends to make a printed page look too
crowded.
left margin right margin
┌──────┬─────────────────────────────┬──────┐
│ │▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒│ │║
(TOP 1)─┼──────┼────►▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒│ │║
│ │▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒│ │║
│ │ │ │║► header
│ │▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒│ │║
(TOP 2)─┼──────┼────►▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒│ │║
│ │▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒│ │║
│ │ │ │
│ │ │ │
│ │ │ │
│ │ text │ │
│ │ body │ │
│ │ │ │
(MARGIN)──┼──► │ │ ◄──┼──(MARGIN)
│ │ │ │
│ │ │ │
│ │ │ │
│ │ │ │
│ │▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒│ │║
(BOTTOM)─┼──────┼─►▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒│ │║► footer
┌──┐ │ │▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒│ │║ ┌──┐ ┌──┐
│ │ └──────┴─────────────────────────────┴──────┘ │▒▒│ │ │
│ │ print area non-print area │▒▒│ │ │
└──┘ └──┘ └──┘
FIGURE 9-2 ▐ Proposed page layout
The left side of a page may need to be punched for ring binding, so
some additional clear space might be needed there. For most purposes,
however, I find that the default of 1/2 inch is enough. This indentation or
offset is obtained by spacing in five columns for a 10-pitch font.
Condensed type, such as that produced by an Epson printer in condensed
mode, requires a larger number of columns (about eight) to obtain the same
physical offset.
Given the specification presented in the manual page and the values
obtained from the proposed page layout, we can now design a program to
produce nicely formatted listings of text files. We can start by noting
that PR follows a pattern of behavior that is similar to CAT: It takes some
input, applies some transformation, and produces some output. However, we
will have to treat lines a bit differently to accommodate optional line-
numbering, page offset, and other aspects of format.
If our design is done well, we should have enough flexibility to
handle 90 percent of all the printing needs of a programmer. The behavior
of the PR program will be controlled by reasonable built-in defaults that
may be modified by configuration files located in the current directory or
in a directory selected by the user and by command-line options.
After analyzing the requirements for PR and trying several simple
prototypes to investigate ways of implementing the features, I settled on
an organization for the program. Each program module is packaged in a
separate file, usually with one function per file. The program has the
following functions and calling hierarchy:
main()
getpname()
pr_gcnf()
fconfig()
getopt()
pr_help()
pr_file()
pr_cpy()
mkslist()
pr_getln()
fit()
lines()
spaces()
setfont()
selected()
pr_line()
exit()
As you can see, many functions that we developed in earlier chapters
are called by functions used in PR. These external names are resolved by
the linker, using util.lib as one of the searched libraries.
If the user gives an erroneous option, main() calls pr_help() to
display an abbreviated manual page on the stderr channel. The C source for
pr_help() is:
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 250 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
The function uses a static array of string pointers to message lines.
The calculation in the line
n = sizeof (m_str) / sizeof (char *)
sets n to the number of message lines in a way that lets us add message
lines without having to count them and declare how many are used. This
makes it easier to modify the function as the program evolves, as most
programs do.
To achieve the desired flexibility, we will use a global data
structure to hold the values of the important formatting and program-
operation control variables used by the program's various modules. Only
those modules that need to know about the data will be given access to the
data structure. Here is the header file, print.h, that contains the data
structure definition and some manifest constants used to initialize the
program:
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 251 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
A typedef is used to create a synonym, PRINT, for the struct pr_st
data structure. The structure contains about a dozen integer-sized variable
definitions and two string variable definitions. The header file defines
the printer data structure, but it does not declare any storage for it.
That is done in the configuration function, pr_gcnf(), which we'll examine
shortly. First, let's take a look at PR from the top.
The main() function is contained in the file pr.c. It declares and
initializes global variables, establishes the default output channel,
processes command-line arguments, and calls pr_file() with a list of files
named as input by the user. If no filenames are specified, PR uses the
standard input channel so that data can be routed through PR from a
redirected input or from a pipeline. Thus, PR fits the description of a
filter in the same way as the DOS programs MORE and SORT, except that PR
can be configured to send its output to someplace other than the standard
output. In such cases, PR is referred to as a sink instead of a filter,
because it is a termination point for data flow.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 252 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
The main() function calls getpname() to get the program name and then
calls pr_gcnf() to get the configuration data needed to initialize the
program. Next, it uses getopt() to process user-supplied options, if any,
and finally it passes a list of filenames (possibly none) to pr_file(). We
have seen the use of getpname() and getopt() before. There should be no
surprises here.
Option Handling
The pr_gcnf() function is specific to PR, but the design can be
applied to other programs. The pseudocode for this function is
if (a configuration file exists)
open the file
read numeric data
read string data
if (data good)
set flag
close file
if (flag is set)
initialize to values just read
else
use defaults from the header file
Here is the source code for pr_gcnf():
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 255 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
The PRINT type is used to declare a global data structure for the
printer, pcnf, which stands for printer configuration. Any other source
file that needs access to pcnf must declare it as external data.
If no configuration file is found, pr_gcnf() falls through to the
default assignments. The fconfig() function we created in Chapter 7 is
used to query DOS for an external configuration file. A NULL result
indicates that none was found. If a configuration file is found, it is read
in a line at a time.
Numeric values are obtained by converting the character
representations in the file to integers by using the standard library
function atoi(). Because atoi() accepts only characters that can be
considered part of a number, we can add comments to the lines. Thus, when
the line
66 number of lines per page
is retrieved from the configuration file and is given as the string
argument to atoi(), the function merely assigns the integer value 66 to a
temporary array.
String arguments are handled similarly. A line is read in and the
library function strtok() extracts the first token, which is the string
value we need to save. The remaining characters on the line, if any, are
ignored.
An example of a string variable is p--dest, which determines where
PR's output will go. This variable is a member of pcnf and is accessed
using the dot operator as in pcnf.p_dest. Each line of output is sent by
default to the device specified in p_dest. If p_dest is set to CON, or if
it is null (""), output is directed to the standard output channel and will
be displayed on the user's console screen unless program output is
redirected. (MS-DOS and UNIX permit the standard output of a program to be
redirected using the greater-than (>) symbol--see Chapter 3 or your DOS
manual for details. Thus, output that would otherwise be displayed on the
system console or a terminal screen may be sent to a printer port or
captured in another file.)
In this presentation of the PR program, I have set p_dest to PRN, the
default printer device, because that is where I usually want the output to
go. We can still send the output to the screen by selecting the -p
(preview) option, which uses CON as the destination for a single invocation
of PR.
To display the contents of a file named myfile.txt on the system
console, you would type
pr -p myfile.txt
To send the output to a printer that is attached to the default
printer port, you would instead type
pr myfile.txt
If PR is compiled with ssetargv.obj included in the linker object list
(as shown in the makefile, pr.mk), PR permits the use of wildcards in
filenames, letting you use abbreviated command lines to specify print jobs.
For example, to print hard copies of all the header files in the include
subdirectory and in the include\sys subdirectory, type
pr \include\*.h \include\sys\*.h
If you have a hardware or software print spooler on line (such as the
one provided with Microsoft Windows), you can turn your attention to
something productive while your printer dumps a mound of paper on the
floor. Otherwise, you'll have to mark time for a while.
File Handling
The processing loop in pr_file() receives a list of files and tries to
get each file formatted and printed. The function must handle errors
gracefully. If, for example, a user specifies a file that does not exist or
one that cannot be opened, the program sends an error message to the
standard error channel and then continues looking for more files to print.
The standard error channel is used because messages sent to it will appear
on the console screen even when normal program output is directed
elsewhere. The standard input channel is used as a data source if no files
are named on the command line.
The pr_file function is also responsible for connecting the output of
PR to the required stream. If the user has configured PR to send output to
one of the standard output channels (sdtout, stdprn, or stdaux), there is
no need to open the stream, because DOS has already done so when PR started
running. Other destinations, such as a named file, must be explicitly
opened for writing.
The source code for pr_file() is:
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 259 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
Notice the message "Cannot stat (filename)" after the call to pr_cpy()
(described next). This tells the user that file statistics could not be
obtained for the named file even though it was found and opened. This flags
a possible error with the disk directory and should inspire the user to
find out why the error occurred. It could be that a bad sector is
developing or that a disk formatting or updating problem has occurred.
What's My Line?
The work of orchestrating the formatting and copying of text pages
falls to pr_cpy(). A line in a text file may be thought of as a single
logical line that may be represented as one or more physical lines of
output. Long lines of output--those that exceed the width of the printable
or displayable area--may be handled in at least three ways. We can:
► let the line run long and leave the treatment up to the printer
and its driver software. This approach can result in some
bizarre looking output.
► "fold" a long line so that what cannot be displayed or printed on
the current line is moved to the next output line.
► truncate the input so that anything on a line that would have to
be displayed past the last column is effectively lost.
The PR program has been designed to fold lines so that nothing is lost
on output. If line-numbering is turned on, a logical line number is
displayed at the beginning of each line. If a line has to be folded, as
shown in Figure 9-3, the additional physical lines needed to
print or display it are indented as usual, but they do not have a number
field associated with them. Logical lines are numbered starting with one.
The number of a line indicates its relative offset from the beginning of
the file.
.
.
.
46 /* process command-line arguments */
47 while ((ch = getopt(argc, argv, "efgh:l:no:ps:w:")) !=
EOF) {
48 switch (ch) {
49 case 'e':
.
.
.
FIGURE 9-3 ▐ A folded line, as displayed by PR
Before going any further, here are the pseudocode and C source code
for pr_cpy():
set up source and destination streams
if (source is stdin)
get current time
else
get file modification time
for each line in source file or stream
if (line won't fit on page OR formfeed)
eject page
if (at top of a page)
print header
print the logical line
if (not at top of page)
eject page
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 262 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
The pr_cpy() function handles a few other tasks in addition to fold-
ing long lines. It detects that an input stream has ended before the output
page is full and emits enough extra blank lines to get back to the top of a
fresh page. That's so the next file will start printing in the right place.
It makes sure that there is enough room remaining on a page to accept the
next logical line. The static function fit() within the pr_cpy.c file does
the needed calculations.
The function also needs to handle a special situation that results
from a fairly widespread practice. Programmers often create source files
with two or more functions in them. Sometimes a literal formfeed control
character (Ctrl-L) is placed between functions. This causes most printers
to eject a page and to begin printing anew at the top of the next page.
Care must be taken to keep the file-relative line and page numbers
accurate.
The functions mkslist() and selected() obtain a selection list and
determine whether an item is in the list; they were explained fully in
Chapter 7. These functions are called by pr_cpy() to process a list of
selected pages, the Pagelist variable, to be formatted and printed by PR.
The special page number BIGGEST is used to supply the high end of an open
range specification (like 20--), which causes PR to print all pages between
20 and the end of the file.
The pr_getln() function obtains a line of text from the input stream
and expands it while reading it into the line array. The expansion alluded
to here is that of converting each tab character in the input into the
right number of output spaces so that column alignments are retained. The
expansion is necessary because we will be doing indenting (page offset) and
other formatting operations upon output, and because some older printers do
not handle tabs correctly. All positions in the line array that are used to
hold the text must be filled with characters that have a single unit of
displacement in the output. This facilitates character counting so that
fit() can determine how much space is required for the expanded line.
This is the source code for pr_getln():
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 266 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
The tab expansion is done by the functions in tabs.c that were
described in Chapter 7.
Top-of-page processing is done directly by pr_cpy(), which prints the
page header composed of some blank lines, a header line that contains the
name of the current file (or a substitute text string), the output page
number, and the date and time when the file was created or last modified.
The blank lines are produced by a call to lines(), a function that emits a
series of newlines to the specified stream.
/*
* lines -- send newlines to the output stream
*/
#include <stdio.h>
#include <stdlib.h>
int
lines(n, fp)
int n;
FILE *fp;
{
register int i;
for (i = 0; i < n; ++i)
if (putc('\n', fp) == EOF && ferror(fp))
break;
/* return number of newlines emitted */
return (i);
}
The filename as typed by the user is converted to uppercase. This is
done because DOS automatically converts filenames returned in response to
an ambiguous specification (using wildcards) in uppercase letters. We could
just as easily convert everything to lowercase.
The formatting of the header line differs from the text body when the
Epson-compatible mode is selected. The setprnt(), setfont(), and clrprnt()
functions described in Chapter 7 are employed to control the printer so
that headers may be printed in a different typeface than the main text.
(Although not a necessary feature, this demonstrates a practical use of
different typefaces on a single page.) The text body can be printed in a
compressed form, to permit long lines (up to about 137 characters) to be
printed on standard letter-sized paper.
The function pr_line() is called to output a logical line of text
composed of two or three parts: a left margin produced by a call to
spaces(), an optional file-relative line-number field, and finally the text
of the expanded line. If additional physical lines are needed, spaces() is
called at the beginning of each to obtain a uniform page offset.
Here is the source code for pr_line():
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 268 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
The pr_line() function returns the number of physical lines that were
used to print the single logical input line.
The spaces() function (on the next page) is almost identical to
lines(), except for the character that it emits (space instead of
newline).
/*
* spaces -- send spaces (blanks) to the output stream
*/
#include <stdio.h>
#include <stdlib.h>
int
spaces(n, fp)
int n;
FILE *fp;
{
register int i;
for (i = 0; i < n; ++i)
if (putc(' ', fp) == EOF && ferror(fp))
break;
/* return number of spaces emitted */
return (i);
}
Since the tasks performed by lines() and spaces() are needed
frequently in programs that send text to the screen or some other output
device, we will add the object files lines.obj and spaces.obj to our
utility library, util.lib, by compiling them and using the command
lib util +lines spaces;
Remember to copy the updated library into the \lib\local subdirectory so
that LINK can find it.
Program Maintenance
A special-purpose library called prlib.lib contains all but a few of
the object modules that comprise the PR program. The pr.obj file, which
contains the main() function, is kept separate from the rest because of the
way the DOS linker, LINK, works. The executable filename defaults to the
name of the first object module specified in the link list, and at least
one module must be named.
The makefile script for PR is in pr.mk. It automatically keeps the
needed objects and the prlib.lib file up to date as changes are made to the
program source files. Here is the text of pr.mk:
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 271 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
Possible Enhancements
As well endowed as PR is already, there is always some new wrinkle or
feature that some user would like to see. In trying to avoid making PR into
a program that serves everyone in every way but no one particularly well in
any way, I have chosen to stick with the feature set provided.
However, some users of PR have requested the following features, which
you may want to consider adding. First, it might be helpful to have a
multi-column output feature. This would take lines from a file and present
them in columns across the page. Each line would be truncated if it
exceeded the column width minus one character position for a separator. It
would be necessary to format an entire page in memory before sending
anything to the output.
Another feature of interest to some users is a more elaborate way of
controlling the appearance of the header and footer. This, to me, smacks of
word processing, and I have elected to use Microsoft Word if I need such
control over the output.
The addition of single-sheet control might be useful to some users. I
always use pin-fed, fan-fold paper, so I have not felt a compelling need
for this feature. It can be added easily by introducing a new numeric
(actually a Boolean) variable in the PRINT data structure and testing it
before outputting a new page.
One additional feature may make it into my next version of PR. Some
users would like to be able to do side-by-side printing of multiple files.
This is a kind of visual file comparison. It can also be used to generate,
in a somewhat restricted way, scripts for plays and motion pictures. This
could be done either a line at a time or by doing full-page makeup before
generating output.
Next, we will devise a program that lets us peer into nontext files
that cause PR and DOS text-file programs (TYPE, for example) to produce
strange and not particularly useful output.
Chapter 10 Displaying Non-ASCII Text
Our kit of programmer's utilities now contains some pretty useful
tools: the CAT program for concatenating files and PR to print them out on
paper; LS to list a directory in a variety of display formats; TOUCH to
assist the MAKE command in building programs; and RM to help us clean up
our disk directories easily. In addition, we have a raft of low-level and
mid-level routines to help us create other tools.
Thus far, we have been concerned only with text files and tools that
permit us to view and manipulate them. However, one of the essential tools
that a programmer needs to have at the ready is a utility that permits the
inspection of nontext files. Nontext files are produced by compilers,
tokenizing interpreters (such as Microsoft interpretive BASIC), assemblers,
program configurators (see Chapter 7), and many commercial word
processors, database-management systems, and spreadsheets.
If you were asked to convert a file produced by a word processor that
you do not have to a form that is acceptable to one that you do, how would
you go about it? Ignoring the possibility of buying the needed word
processor, we will have to do some detective work, which is what makes the
computer business such a lot of fun for some and such a pain in the neck
for others.
The form and content of the data file would have to be determined so
that it could be converted to a suitable form for the target word
processor. A straight ASCII text file would suffice as data to be merged
into the input stream of most word processors, so we would first attempt to
remove any non-ASCII characters from the data file and save the results of
the conversion in an intermediate file for later processing.
But how would we examine the data file? Our current tools are suitable
only for text files, so we need something else. In this chapter, we will
develop a program called DUMP that takes any file and produces an output
listing that looks like the dump output of the DOS DEBUG program.
Each line of the output listing includes a hexadecimal offset in the
leftmost column, a hexadecimal display of 16 (10 hex) bytes from the data
file in the center, and the equivalent data in extended ASCII in the
rightmost column. A character code that would cause problems on the display
and on a printer is converted to a dot (.) before being displayed. These
filtered codes include newline, carriage return, and most other control
codes.
The reason for developing a stand-alone program is that DUMP will
allow us to capture the converted output in a file or on paper for easy
examination. Our design will also permit DUMP to be used as a filter in
pipeline commands.
Before designing and programming DUMP, let's review some of the most
common data-presentation formats and prepare some functions to do needed
conversions.
Some Useful Conversion Functions
The primary data-presentation formats for numbers are the binary,
octal, decimal, and hexadecimal notations. While we humans take more or
less naturally to decimal (something to do with fingers and toes), our
machines tend to favor binary, in which things are on or off (1 or 0). The
octal (base 8) and hexadecimal (base 16) formats are simply notations that
make binary data more palatable to us. Octal number representations were
widely used for many years and still are in some quarters, but octal
notation has lost favor with the majority of programmers, who use
hexadecimal notation instead. All characters in the computer are
represented internally as binary numbers, however. That's all the computer
"understands." Our task is to receive data, convert it to the desired form,
and send it to the standard output. The output will be displayed on the
computer or terminal screen, or sent to some other device if stdout is
redirected.
Our programs will need to convert the computer's internal
representation of numbers into decimal, hexadecimal, or character forms,
depending upon what we are trying to see. We will use two functions to do
this. They are used in DUMP and will be placed in the utility library
(util.lib) for use by other programs. The first function, byte2hex(),
converts a byte-sized (8-bit) quantity to hexadecimal. The second,
word2hex(), performs the same job on a word-sized (16-bit) quantity. These
sizes are appropriate for the IBM PC and other 16-bit machines, but they
are not universal, so portability to other machines is not guaranteed.
Both byte2hex() and word2hex() are packaged in a single source file
named hex.c, which has the following contents:
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 275 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
The hextab array is a conversion lookup table. It uses a number in the
range of 0 through 15 as an index into the table, where the corresponding
character constant (number or letter) is found. Hence, the numeral 3 is
represented by a character constant of 3 (ASCII code 51--be sure you know
the difference), and the number 10 is represented by an A. These half-byte
quantities are comically referred to as "nibbles" because they're smaller
than bytes. The symbolic constant NIBBLE is defined as 0x000F. It is used
to mask off all but the four bits of interest in each conversion step.
Right-shift operations are used to pull the needed bits into the correct
position for conversion to a hexadecimal digit.
A byte can be conveniently expressed as two hex characters (two
nibbles) that represent values in the range of 0 (00 hex) through 255 (FF
hex). The same range in binary is 00000000 through 11111111, which
obviously lacks notational convenience. The hex notation is more compact,
requiring only two characters to represent any value in the range.
Now that we have a simple means of displaying data in hexadecimal, we
can move on to the development of the DUMP program.
A DEBUG-style File-dump Program
The design of DUMP is strongly influenced by the expectation that its
output will be easy to feed to other programs and to a generic printer. We
want to build in enough flexibility to permit DUMP to be used as a filter
or as a stand-alone program. In stand-alone operation, DUMP takes a list of
files as arguments and produces the hex/ASCII output for each in sequence
on its standard output. As a filter, it receives a stream of arbitrary
characters and transforms it to the hex/ASCII output stream.
The proposed format of DUMP's output looks a lot like that of DEBUG.
One difference is that the offset of each block of 16 bytes will be shown
relative to the start of the displayed file. We won't know where the file
is located in memory, so the offset value will stand alone. Another
difference is more subtle. DEBUG converts all non-ASCII characters in the
text display area to dots. DUMP will display all standard ASCII characters
plus the IBM extended ASCII character set when output is directed to the
screen. A command-line option will allow us to strip all nonprintable
characters from the output stream so that we can use any type of printer as
an output device.
Figure 10-1 on the next page is the manual page for DUMP.
Note the overall simplicity of the program. Therein lies its power, because
it can be used in a wide range of situations without change.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the figure found on page ║
║ 278 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
FIGURE 10-1 ▐ Manual page for DUMP
The code for DUMP is straightforward. DUMP follows the pattern
established in Chapter 8, where file-oriented utility programs used
similar command-line options to control program operation and write to the
standard output channel. Here is the pseudocode description of the main()
function of DUMP, excluding the generic items, such as getting the program
name from DOS and error checking at each step:
if (no files named)
hexdump(stdin)
else
for each file
open(file)
hexdump(file)
close(file)
The hexdump() function does the work of transforming the input into
the desired presentation format:
for each block of input [BUFSIZ]
for each block of bytes [NBYTES]
copy byte offset into output buffer
[word2hex()]
copy hex values into output buffer
[byte2hex()]
copy ASCII values into output buffer
copy output buffer to stdout
The main() function of DUMP uses getopt() to scan the command line for
optional arguments and filename arguments. If the -s option is found, the
Boolean variable sflag is set to TRUE to indicate that non-ASCII characters
should be stripped. If the -v option is found, the Boolean variable vflag
(verbose) is set to TRUE and a header consisting of a blank line followed
by the name of the file being dumped is output before the formatted data
for each file is listed. You may want to add other data to the verbose
output, such as file date and time, character count, and so on. If no
filenames are specified, DUMP reads from its standard input and the verbose
option is ignored because no name is associated with the data stream.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 279 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
One or more filenames can be specified either directly or by wildcard
specifications. Like CAT and PR, DUMP will operate sequentially on a list
of files in the order in which they are presented. Ambiguous filenames
under Microsoft C are expanded in lexical order, not directory order.
Files are opened in binary mode, not translated mode, because we will
most likely be reading nontext files. Even if we read a text file, we want
to be able to see all characters, so the translation of the CR/LF and Ctrl-
Z codes is inappropriate.
The hexdump() function uses an internal buffer, outbuf, to form a line
in memory before outputting it with the fputs() library function. Although
the buffering is not essential, it collects the writing operation into a
single function call instead of having five separate calls to several
different library routines. This buffering approach makes it easier to
change the output code if we decide to use a different means of writing the
output to the screen.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 282 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
Figure 10-2 on the next page is a sample of the output
produced by DUMP. The figure shows the hexadecimal and equivalent text
output produced by DUMP when it uses the hex.obj object file as
input. DUMP was not instructed to strip all nonprintable characters from
the output listing.
╓┌─────────┌────────────────────────────────────────────────┌────────────────►
00000000 80 07 00 05 68 65 78 2E 43 BE 88 07 00 00 00 4D │Ç...hex.C╛ê....M│
00000010 53 20 43 6E 88 05 00 00 9F 45 4D 42 88 09 00 00 │S Cnê...ƒEMBê...│
00000020 9F 53 4C 49 42 46 50 10 88 08 00 00 9F 53 4C 49 │ƒSLIBFP.ê...ƒSLI│
00000030 42 43 64 88 07 00 00 9F 4C 49 42 48 B3 88 06 00 │BCdê...ƒLIBH│ê..│
00000040 00 9D 30 73 4F E3 88 06 00 00 A1 01 43 56 37 96 │.¥0sOπê...í.CV7û│
00000050 2E 00 00 06 44 47 52 4F 55 50 05 5F 54 45 58 54 │....DGROUP._TEXT│
00000060 04 43 4F 44 45 05 5F 44 41 54 41 04 44 41 54 41 │.CODE._DATA.DATA│
00000070 05 43 4F 4E 53 54 04 5F 42 53 53 03 42 53 53 3F │.CONST._BSS.BSS?│
00000080 98 07 00 28 CA 00 03 04 01 67 98 07 00 48 10 00 │ÿ..(╩....gÿ..H..│
00000090 05 06 01 FD 98 07 00 48 00 00 07 07 01 0A 98 07 │...²ÿ..H......ÿ.│
000000A0 00 48 00 00 08 09 01 07 9A 08 00 02 FF 03 FF 04 │.H......Ü... . .│
000000B0 FF 02 56 9C 0D 00 00 03 01 02 02 01 03 04 40 01 │ .V£..........@.│
000000C0 45 01 C0 8C 2D 00 0A 5F 5F 61 63 72 74 75 73 65 │E.└î-..__acrtuse│
000000D0 64 00 09 5F 62 79 74 65 32 68 65 78 00 09 5F 77 │d.._byte2hex.._w│
000000E0 6F 72 64 32 68 65 78 00 08 5F 5F 63 68 6B 73 74 │ord2hex..__chkst│
000000E0 6F 72 64 32 68 65 78 00 08 5F 5F 63 68 6B 73 74 │ord2hex..__chkst│
000000F0 6B 00 A8 A0 14 00 02 00 00 30 31 32 33 34 35 36 │k.¿á.....0123456│
00000100 37 38 39 41 42 43 44 45 46 A8 A0 CE 00 01 00 00 │789ABCDEF¿á╬....│
00000110 55 8B EC B8 04 00 E8 00 00 56 8A 46 04 2A E4 89 │Uï∞╕..Φ..VèF.*Σë│
00000120 46 FE 8B 46 06 89 46 FC 8B D8 FF 46 FC 8B 76 FE │F■ïF.ëFⁿï╪ Fⁿïv■│
00000130 B1 04 D3 EE 81 E6 0F 00 8A 84 00 00 88 07 8B 5E │▒.╙εüµ..èä..ê.ï^│
00000140 FC FF 46 FC 8B 76 FE 81 E6 0F 00 8A 84 00 00 88 │ⁿ Fⁿïv■üµ..èä..ê│
00000150 07 8B 5E FC C6 07 00 8B 46 06 5E 8B E5 5D C3 55 │.ï^ⁿ╞..ïF.^ïσ].U│
00000160 8B EC B8 04 00 E8 00 00 56 8B 46 04 89 46 FE 8B │ï∞╕..Φ..VïF.ëF■ï│
00000170 46 06 89 46 FC 8B D8 FF 46 FC 8B 76 FE B1 0C D3 │F.ëFⁿï╪ Fⁿïv■▒.╙│
00000180 EE 81 E6 0F 00 8A 84 00 00 88 07 8B 5E FC FF 46 │εüµ..èä..ê.ï^ⁿ F│
FIGURE 10-2 ▐ Hex dump sample (HEX.DMP)
When the -s option is selected, the ASCII text display is surrounded
by vertical lines formed from the vertical bar symbol (|). The normal
output, which assumes that the destination device is the PC screen, uses
the IBM drawing character (code 179) for a single vertical line; this looks
pretty on the screen, but cannot be printed correctly by many printers.
The makefile for DUMP is contained in dump.mk:
# makefile for dump utility
LIB=c:\lib
LLIB=c:\lib\local
dump.obj: dump.c
msc $*;
hexdump.obj: hexdump.c
msc $*;
dump.exe: dump.obj hexdump.obj $(LLIB)\util.lib
link $* hexdump $(LIB)\ssetargv, $*, nul, $(LLIB)\util;
To compile DUMP, first compile hex.c and add the object file to the
utility library (\lib\local\util.lib); then type
MAKE DUMP.MK
The resulting executable file, DUMP.EXE, should be placed, as usual, in a
directory that is accessible to DOS via the PATH variable, so that it can
be called from anywhere in the directory hierarchy.
The DUMP program can now be used for one of its intended purposes--
determining the file format of a special-purpose data file. This will help
us build another useful tool, SHOW, which lets us see nonprintable
characters in files in an unambiguous way and recover text from specially
formatted files.
The SHOW Program
The next program is called SHOW because it shows us things that we
might not otherwise see. SHOW closely resembles DUMP in construction and
purpose, but its output is very different from DUMP's.
If the task at hand is to retrieve the essential text from a document
file, rather than to determine how the formatting was done, we need a tool
that filters out the nonprintable codes and emits a stream of ASCII-only
text. In some situations, we might also want to see the formatting codes
represented visibly.
The SHOW program takes files that may contain special (usually
nonprintable) characters in addition to normal text and produces an ASCII
text output. Nonprintable codes are converted to their displayable
hexadecimal notation--a backslash followed by two hex digits.
Two options allow us to strip out special codes and produce an output
that is pure text. The -s option strips all special codes except the usual
format effectors (newline, tab, and space).
The -w (word-processor) option strips special codes (by setting the -s
option), but it handles several additional elements of word-processor data
files as well. For example, when forming paragraphs, many word processors
use a carriage return as a "soft" return (line break) and a combined
carriage return and linefeed as a "hard" return (paragraph terminator). For
compatibility with such word-processor file formats, the -w option of SHOW
converts a lone carriage return to a newline, thus breaking long lines into
a sequence of shorter ones. Also, some word processors use the eighth bit
of some bytes to signal formatting options (bold, underline, end-of-word,
and so on). To make these bytes visible, SHOW (with the -w option set)
turns off the high bit of all bytes and displays the bytes that then fall
into the printable ASCII range.
The full manual page for SHOW is presented in Figure 10-3.
Notice that SHOW is also a filter: It can read from its standard input
channel and write the transformed output to the standard output
channel.
The pseudocode description for SHOW is nearly identical to that of
DUMP. The primary differences are found in the showit() function, which is
described (without options) by the following pseudeocode:
for each input character
if it's ASCII
if it's printable or a format effector
send it to stdout
else
send printable equivalent to stdout
The options -s and -w add some minor complications to the description.
The effects of the options are best seen in the code for the main() and
showit() functions. In main() (in the file show.c), getopt() is once again
used to process command-line options. A variable, wflag, controls whether
SHOW attempts to convert word-processing data files. It is initially FALSE.
The call to getopt() requires the addition of w to the allowed option list.
The value of wflag is passed to showit() as an argument, rather than being
treated as global data. If SHOW had a lot of functions that needed to know
about wflag, we would probably make it a global variable.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the figure found on page ║
║ 287 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
FIGURE 10-3 ▐ Manual page for SHOW
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 287 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
The showit() function receives a FILE pointer and the values of sflag
and wflag. If neither of the flags is TRUE, showit() passes anything that
is printable (including format effectors) to the standard output channel
and converts everything else to a displayable hexadecimal byte
representation. Thus, the null byte is shown as \00, the ASCII Del
character is shown as \7F, and so on. If -s was used on the command line,
sflag (and therefore strip) is TRUE, which causes showit() to eliminate the
hexadecimal conversions.
Setting the -w option sets the wp variable in showit() to TRUE,
causing the function to alter the default treatment of carriage returns and
extended characters.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 290 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
The makefile for SHOW is similar to the one for DUMP:
# makefile for the show program
LIB=c:\lib
LLIB=c:\lib\local
show.obj: show.c
msc $*;
showit.obj: showit.c
msc $*;
show.exe: show.obj showit.obj $(LLIB)\util.lib
link $* showit $(LIB)\ssetargv, $*, nul, $(LLIB)\util;
To see the output of SHOW, try the program on a data file from a word
processor or spreadsheet with and without the optional processing features.
The results should be at least intriguing and will probably be enlightening
to those who have not snooped around in such files before.
In the next section, we move from the line-by-line realm of
file oriented programs to the intensely visually oriented programs that
make the PC so popular with the average user.
SECTION IV Screen-Oriented Programs
Chapter 11 Screen Access Routines
In this chapter and the next two, we will explore some of the many
options available to designers for presenting information on a PC screen.
The method of screen access presented in this chapter is relatively easy to
implement, produces excellent results, and costs little in terms of program
size and complexity. Its use is not recommended for programs that will
operate in a windowing environment, however, because it violates one of the
primary rules of good behavior: "Thou shalt not write directly to the
screen."
We will purposely break this rule at first to show what level of
performance we are striving for. In Chapter 12, we will explore some
interesting display buffering techniques and show how we can be "good" boys
and girls by using BIOS-based screen routines as an alternative to direct
access. Then, in Chapter 13, we will seek to be downright virtuous by
using the ANSI standard interface that is portable to nearly every PC that
runs a version of MS-DOS (and PC-DOS).
Determining the Display System Type
One of the challenges that has always faced PC software designers is
writing a program that determines what type of display adapter and monitor
is in use before it does anything else. It is unfortunate that many
programs ignore this crucial first step.
Many of the offending programs were designed by programmers working on
monochrome-only systems. When run on a color/graphics-equipped system,
these programs at least alter the user's selected video attributes so that
either during or following the program's running, the screen is unpleasant
to look at or has a whole new shade of reality. Sometimes, but not too
often, the screen gets completely messed up by being left in an
inappropriate video mode.
Programs designed for color-only systems can produce similarly
unpleasant effects on monochrome systems. For example, light blue is a very
pleasing color for text, but it causes everything on a monochrome system to
be underlined and intense. Then there's the problem of a program that
assumes the needed display adapter is in use and does not check for its
presence. This can leave the user with a system that appears to be
frozen.
And it isn't just amateur programmers who are guilty of these
transgressions. Although most commercial software offerings check the
hardware, many supposedly "commercial quality" programs have the bad
manners to ignore the user's color preferences, video mode settings, and so
on.
We can do something about this situation. Following are some routines
and program examples that show how to detect, save, and restore the user's
operating conditions. The routines use BIOS functions and simple memory
tests to determine what equipment is installed. You can call the functions,
as demonstrated in the DSPYTYPE program that follows and the ScreenTest
(ST) program later in this chapter, and save the video state at the time
your program begins execution. The original operating conditions can then
be restored before the program returns control to DOS.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 296 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
The DSPYTYPE program is a test driver for the functions memchk() and
ega_info(). These functions supplement the BIOS equipchk() and getstate()
functions we saw in Chapter 5. The memchk() function looks for memory at a
specified segment and offset. It is a small-model function that calls on
the library function movedata() to write and read the contents of memory
locations. A large-model version of memchk() would use memcpy() in place of
movedata(). The memcpy() function takes long pointers used in large-model
programs; movedata() requires segmented addresses to access far data items
in small-model programs.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 298 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
The technique used to detect the presence of a monochrome display
adapter (MDA) or a color/graphics adapter (CGA) involves a sequence of
operations that save, write, read, compare, and restore the contents of a
memory location that is known to be in the range of the given adapter. The
save and restore operations ensure that the test is non-destructive. The
write-read-compare operation determines whether there is any active memory
at the test location. The value 55H is a bit pattern of alternating 0s and
1s. Because unoccupied memory locations tend to look like 1s to programs
that try to read them, the pattern of the test value used by memchk() is
not likely to be duplicated. If you want to feel more sure of the results,
use sequential tests at the same address with two or more different
values.
But why bother looking at the memory--why not simply read the mode
value returned by getstate()? We will probably want to do both in our
programs. Use getstate() to find out what the operating video mode is, and
look for memory at the other adapter locations. Some PC users (programmers
in particular) have both monochrome and color display systems installed in
a PC. A program that was designed to work exclusively with a CGA system
might find the system in mode 7 (monochrome). The program can respond to
this in two ways: It can print a message stating its need for a CGA and
quit, or it can look for a CGA system and switch to it if one is found. I
favor the first approach because the user may have the color system turned
off. He or she can use the DOS MODE command to select the needed display
mode (MODE MONO to select monochrome; MODE CO80, MODE CO40, and so on, to
select a CGA mode).
The memchk() function has other uses, too, such as looking around for
blocks of memory that might be separated from the main memory of a system.
It cannot be used to detect the presence of read-only memory (ROM), of
course, because ROM cannot be written to and it will fail our tests. You
can use memchk() to check memory locations at one-kilobyte increments to
count up how much RAM is available to DOS and how much might be
noncontiguous. Many add-in memory boards permit splitmemory addressing,
allowing you to set up stand-alone print spoolers and other special memory
allocations. With memchk(), we have a convenient way to find them. Add
memchk() to the utility library to make it readily accessible to your
programs.
The enhanced graphics adapter (EGA) complicates the video-adapter
detection problem a bit. The address space of the EGA nominally starts at
segment A000. However, an EGA's addressing modes are very flexible, and the
adapter usually masquerades as a CGA or MDA while operating at the DOS
command level, setting itself up to start at either the B800 or B000 memory
segments. The problem is in determining whether an EGA or some other
adapter is installed at a particular location. The function ega_info(),
based on information provided by IBM, looks for an EGA by calling the BIOS
video interrupt 10H and invoking the Alternate Function Select function
AH = 18 (12H), which is an EGA BIOS feature (the EGA BIOS is located at
segment C000H). When AL equals 10H upon entry, this function returns EGA
information, which includes the EGA mode (color or monochrome) and memory
size (64 to 256 KB) in addition to feature and switch-setting information.
An EGA display system is absent if ega_info() returns mode values other
than 0 and 1 and memory size values outside the range of 0 to 3.
If an EGA is found, checking the video mode returned by getstate()
tells us whether it is emulating a CGA or MDA. If we need to know if
another adapter is installed, we can use memchk() to do the test. If an EGA
is emulating a CGA, you cannot have a real CGA installed, so we need check
only for the adapter type not being emulated by the EGA.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 301 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
DSPYTYPE is a simple program that calls ega_info() and memchk() to
determine what display adapters are installed in a system and to report the
results to the user. DSPYTYPE also reports the current video mode, screen
width, and screen page number. In practice, we would use the functions to
get the video mode and the display adapter configuration, and then set up
the needed display conditions for our program or abort with an explanatory
message about the lack of needed hardware.
Next we will consider a method of direct screen access for reading and
writing that serves as an alternative to using BIOS and DOS methods of
screen access. The BIOS and DOS functions automatically deal with the wide
variety of display configurations that are possible in a PC system, but
they introduce a lot of processing overhead because an adapter test occurs
with every character (or dot) that is written or read. The display-adapter
detection methods just described are important to direct screen access
because they let us do the adapter tests once at program start-up, cutting
out a huge amount of processing during subsequent screen read and write
operations.
Direct Screen Access
A fast but inherently nonportable way of updating the PC screen is to
write directly to its associated memory. Using one or more off-screen
buffers in the program's data space to create images before displaying them
can produce excellent visual performances. A block-copy routine quickly
copies the buffered-memory image to physical display memory, producing an
"instant screen" effect. The IBM monochrome display adapter exhibits no
problems with this strategy. Neither do the IBM enhanced graphics adapter
and many third party monochrome and color/graphics adapters, all of which
have been designed to permit simultaneous access to display memory by both
the CPU and the display-refresh circuitry.
The original CGA in either 40-column or 80-column text mode poses a
problem for designers because, unlike the monochrome adapter, the CGA
exhibits visible interference when a program tries to access display memory
while the monitor screen is being updated (refreshed) from the same memory.
The interference looks like "snow"--an irritating pulsing of short line
segments covering all or portions of the screen. Several methods of
avoiding the interference have been developed. One is to synchronize the
display accesses during both reading and writing operations with the time
periods within a display-refresh cycle that are considered "safe." The safe
times are the horizontal and vertical retrace periods of each displayed
frame. Another method involves blanking (turning off) the raster scan while
the display memory is being written to. As we'll see, each approach has
advantages and disadvantages.
Display Adapter Basics
We begin with a brief examination of the color/graphics adapter to see
why the retrace periods are the only safe times for display memory
accesses. This discussion is not applicable to the IBM enhanced graphics
adapter because it uses faster memory devices and additional logic that
prevent problems that occur when the CPU and video-refresh circuitry try to
access the video buffer simultaneously.
The memory on the standard CGA is placed within the address space of
the central processor. The CGA memory starts at segment B800H and extends
upward for 16 KB, enough memory for one high-resolution graphics screen
(128,000 picture elements), or four screen pages in 80-column color text
mode. The text mode is the focus of this discussion.
╔══════════════════════════════════════════╗
║ ║
║ Figure 11-1 is found on page 303 ║
║ in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
FIGURE 11-1 ▐ Typical horizontal sweep signal
Figure 11-1 on the previous page represents the horizontal
sweep signal, which is the signal within the display device that is
responsible for the horizontal deflection of the electron beam that paints
the screen. The figure depicts one horizontal scan period. The dependent
(vertical) axis depicts the amount of beam deflection as a function of time
shown along the independent (horizontal) axis. On a computer display
device, the image on the screen is under-scanned so that the image is
completely visible within the normal viewing area, resulting in a framed
picture with a visible border. Television sets, on the other hand, use
over-scanning to make the image "bleed," leaving no border. The beam is
turned off completely (blanked) during retrace to avoid leaving unwanted
residue on the screen.
The IBM display is not interlaced, so there are 262.5 horizontal scans
per frame (one full screen image), and frames occur at a rate of 60 per
second. With 15,750 horizontal scans per second, each one takes about 63.4
microseconds. Only a small portion of a single scan, typically 20 percent
or less, is allocated to the horizontal retrace--one of the safe times for
display memory accesses, as can be seen in the figure.
The horizontal sweep signal moves the electron beam from side to side,
and if it were the only sweep signal affecting the beam, there would be
only a single straight line on the display surface. Another kind of sweep
signal is needed to move the beam up and down the face of the tube. The
vertical sweep has the same basic sawtooth shape as the horizontal sweep,
but a slower rate of change. At minimum deflection, the beam is at the top
of the screen; it moves toward the bottom with increasing deflection.
There is a vertical retrace period at the end of each frame that
occurs during the vertical-sync pulse period. During this time, the
electron beam is blanked and moved from the lower right corner of the
screen back to the upper left corner. The vertical-sync period (typically a
little more than one millisecond for the standard American TV signals used
by the CGA) is long enough to permit a block of about 250 data words (2-
byte character and attribute pairs) to be transferred to or from display
memory without interference. Most video controller designs disable the
horizontal sweep during the vertical retrace, but some let it run
continuously.
Programming Considerations
A few important choices affect the way programs that interact tightly
with the display system are designed and written. There are enough choices
at every step in the process of designing a video application so that no
two designers are likely to do the job in exactly the same way. The
following program implements one way of designing a video interface. It is
not the only way. Other methods that are even more intimately tied to
specific hardware can run as much as four times faster than the method
described here, but they are much less portable to other hardware
configurations.
I decided to use a buffered screen interface. This means that the
image to be sent to the display is assembled in the program's own data
space. When complete, the image is copied in its entirety as quickly as
possible to display memory.
Conversely, an unbuffered approach is used by many programs and is
adequate for most purposes. In the typical unbuffered scheme, characters
are written into display memory via DOS and BIOS routines, but no memory
image is retained by the application program. The DOS and BIOS routines can
also be used in a buffered screen-management system but will slow things
down quite a bit compared to direct methods. As we'll see in the next
chapter, careful design can greatly improve the apparent speed of the DOS
and BIOS routines under many operating conditions.
The routines described in this chapter assume that programs calling
them have already done an equipment inventory and have set up the display
system in an 80-column text mode. The getstate() function obtains the
current video mode, screen width, and visual display page values. Mode
values of 2 or 3 indicate 80-column text modes (monochrome and color,
respectively) on a CGA, and a value of 7 indicates a monochrome adapter.
The DOS MODE command may be used to select an appropriate video mode before
calling programs that require a particular mode.
Alternative Solutions
Available methods of synchronization to avoid visual interference on a
CGA depend on the use of the status register at I/O address 3DA hex. This
is a read-only register on the CGA that has two bits of interest to the
block-copy routine described below. When high (1), bit 0 indicates that a
horizontal retrace is in progress. When high, bit 3 indicates that a
vertical retrace is in progress.
Another register--a write-only register at I/O address 3D8 hex on the
CGA--has a bit that may be reset (made 0) to disable video. Bit 3 must be
set to a value of 1 to turn on the beam that paints the screen. Turning off
the beam is an effective way to prevent visual interference from reaching
the viewer's eye. However, the beam cannot be left off for more than about
three character rows' worth of data before a flicker becomes apparent. The
BIOS video scroll routines use this technique and are not pleasant to look
at if the background color is anything but black. Even normal text on a
black background appears to dim somewhat in the upper half on the display
when the blanking method is used.
A Synchronized Block-copy Routine
The direct method of screen access uses a memory buffer that holds the
same amount of data as one display page on the standard CGA. The block-copy
routine, cpblk(), copies the contents of the memory buffer to display
memory only during safe times. The memory buffer has a total of 4000 bytes;
2000 bytes are for characters (25 rows by 80 columns), and the other 2000
bytes hold the attributes associated with the characters. Display memory
has 4096 bytes per page (four pages in 80-column mode), but the last 96
bytes of each page are unused (except by some programs that hide
information in them).
The C-language source for the block-copy routine is cpblk.c. An image
is copied from application memory to display memory as a series of ten
blocks of 200 words each. Each word represents a character and its
associated video attribute. (An assembly-language version of this routine
is described in my "Instant Screens" article in the June 1986 issue of
PC Tech Journal. Portions of this chapter are based on that article.)
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 306 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
The C version of cpblk() is about 60 percent slower than its assembly-
language counterpart, but it is fast enough for most purposes, copying an
entire screen in about two-tenths of a second. The assembly-language
version uses both horizontal and vertical retrace periods to reduce the
number of blocks needed to six instead of ten. The use of ten blocks is
conservative and will work with nearly all IBM-compatible display reduce
the number of blocks needed to six instead of ten. The use of ten blocks is
conservative and will work with nearly all IBM-compatible display hardware.
On all of the IBM machines I tested, eight blocks were sufficient; however,
some compatibles required smaller blocks because of differences in the
timing of the retrace signal used to determine the safe time for copying
data.
Double Buffering
To obtain the snappiest looking performance from a buffered screen-
interface technique, programs can use an in-memory screen buffer that is
updated out of view of the user and then is copied to display memory as
fast as possible. A way of achieving nearly instant CGA updates is shown in
Figure 11-2. The technique is called double buffering because two levels
of buffers are maintained in the application program and elsewhere in main
memory. A two-step process is used to form a composite image in a screen
buffer before it is copied to physical display memory.
Data source buffers may be of any size and are usually thought of as
being rectangular in shape although they are simply sequences of bytes in
memory. They are mapped to the off-screen buffer as needed--a technique
that permits windows for help frames, menus, and the like to be easily
overlaid onto another image. In the next chapter, we will write a set of
library functions that handle the needed operations, such as writing
characters, attributes, and strings, scrolling regions, and so on, and
demonstrate the technique in a sample program.
╔══════════════════════════════════════════╗
║ ║
║ Figure 11-2 is found on page 308 ║
║ in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
FIGURE 11-2 ▐ Double buffering
Once the off-screen buffer has all of its characters and attributes in
the right places, the task of getting the data to the visual display is
handled by the cpblk() routine described above. If the screen buffer is
copied directly to the part of display memory being viewed, the user will
see the screen being updated, albeit very quickly. If the contents of the
before and after images vary only in small areas, it is difficult to notice
that an update occurs.
If, however, there are massive image changes, such as switching
background colors, the user will detect the update. For some purposes, the
visible updating of screen displays is desirable because it reassures the
user that the program is doing something. In other situations, we will want
screen images to snap instantly into place. We can have it either way.
Demonstration Program
The file st.c contains the C-language source for a test driver program
called ST (for ScreenTest). The driver program uses a static screen buffer
to hold text images that are copied en masse to display memory after the
image is fully constructed.
The getkey() function (see Chapter 5) calls DOS function 7 hex, which
returns the next available character in the keyboard buffer. The function
waits for a keypress if nothing is ready. The driver program displays a
help message for any nonprintable keypress except Esc (the Quit command)
and Ctrl-Break (Abort). As each character is keyed in, its value is used to
fill the screen buffer. Differing color attributes derived from the
character code are used to show the effects of massive color and intensity
changes. The attribute for a given character is the code for the character
shifted left 8 bit positions. The high bit is masked off to prevent screen
blinking.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 309 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
The driver program uses a trick called "page flipping" to produce the
appearance of instant screen displays. It actually takes a few tenths of a
second to copy a screen buffer in the application data space to the display
adapter using the cpblk() routine. Although this is fast when compared to
all other methods that avoid video interference, it is still far from
instantaneous. The page flipping method is possible because the CGA has
enough display memory to hold multiple screen pages simultaneously.
An illustration of page flipping is presented in Figure 11-3.
The method depends on having a means of telling the display system to
view one page of display memory while the application program is writing to
another. The ROM BIOS video interrupt includes a function (5) that sets the
visual page.
╔══════════════════════════════════════════╗
║ ║
║ Figure 11-3 is found on page 313 ║
║ in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
FIGURE 11-3 ▐ Page flipping
COMMENT
Ignore the Technical Reference statement that the
function sets the "active" page. To be consistent with
the way BASIC describes video pages, the active page
should be the one being written and the visual page
should be the one being viewed. Most frequently, the
visual and active pages are one and the same display
page.
Notice that the fprintf() standard library function writes to standard
error, which appears on the visual page. The cpblk() routine, however, is
directed to write to the active page, which is effectively hidden from
view. When the active page has been fully written, it is revealed to the
user by flipping the pages. The function swap_int() exchanges the values of
the apg and vpg variables. The source for swap_int() is contained in the
file swap_int.c. The function is useful in other contexts, so we will add
it to our utility library.
/*
* swap_int -- exchange the values of the two integers
*/
void
swap_int(p1, p2)
register int *p1;
register int *p2;
{
int tmp;
/* exchange the values */
tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
After building the active page, ST then calls setpage(), another of
our bios library routines from Chapter 5, to switch to the new visual
page. The effect from the computer user's perspective is that of an
instant update. There is a short delay while the active page is being
updated before the page swap, but it's not noticeable to the user. The
screen is repainted in one-sixtieth of a second, far faster than the human
eye can follow, and the response to a keypress is usually completed before
the key is released.
The special efforts to synchronize with the vertical-retrace signal
taken by the cpblk() routine are not needed when an MDA is being used.
Therefore, the driver program, ST, checks for mode 7 and uses a standard
block-copy routine that invokes the string-copy feature of the 8086/88
processor. A string copy of 4 KB is done very quickly. There is no visible
flicker and no apparent delay when this approach is used.
An option on the invocation command line for ST permits a single
argument (anything will do) to be given to turn off the special copy
feature when operating on an EGA or on a compatible computer that does not
experience display interference. Using the command ST x, for example, will
tell the program to use a standard block-copy routine instead of the
special one. If this option is selected on a system with a standard CGA,
visible interference will be quite noticeable, especially if you hold down
a key to repeatedly write the same data to the display.
The makefile for ST, st.mk, contains the instructions needed by MAKE
to build and maintain the ST programs.
# makefile for ScreenTest (ST) program
LLIB=c:\lib\local
swap_int.obj: swap_int.c
cpblk.obj: cpblk.c
st.obj: st.c
st.exe: st.obj swap_int.obj cpblk.obj $(LLIB)\bios.lib
$(LLIB)\dos.lib
link $* swap_int cpblk, $*, nul, $(LLIB)\bios $(LLIB)\dos;
Design Considerations
Because it takes a few tenths of a second to copy data from a screen
buffer to display memory, programs should not attempt to write one
character at a time from the keyboard. This would result in a maximum
update rate of a few characters per second. Even a very slow typist would
get way ahead of such a program.
A better way to handle this situation is to use a modified cpblk()
function that updates only the changed character or the rectangular region
in which changes have been made to the buffer. We could also use routines
based on the BIOS and DOS interrupts to update the visual display page
(they do so without causing interference) and use a separate routine to
update the in-memory buffer so that it continues to track what is being
displayed.
The screen-update routines mentioned above can be fashioned from the
cpblk() function. For example, a routine that copies a single line or a
small range of lines, or one that copies a small rectangular region from a
screen buffer to display memory, would be useful in doing selective screen
updates with shorter delays. The next chapter describes screen-buffer
routines that do these and other screen-buffer management tasks.
Remember that the cpblk() routine described here is inherently less
portable than the DOS and BIOS calls and thus should be used only when
necessary for speed and effect. The symbolic constant that holds the
display memory segment could be replaced by a variable. It would then be a
simple matter to change the value for machines that put display memory in a
nonstandard place, if a reliable method can be developed for determining
the identity of the host hardware and its display memory location(s). There
is no foolproof method of doing the hardware identity tests; the best we
can do is look for copyright notices and company names, but finding out the
exact machine type remains an elusive goal. In my programs, I make the
assumption that displays are in the standard IBM-specified locations. This
works on 90 percent of the machines currently in use. For the other 10
percent I produce customized versions of the programs and charge a little
extra to compensate for the added effort.
Chapter 12 Buffered Screen-Interface Functions
The purpose of this chapter is to extend the screen interface
presented in Chapter 11 by building a set of routines that let us
directly access a screen buffer as a virtual screen.
A Buffered Screen-Interface Package
We have already seen how a memory image of a text screen can be
quickly copied to physical display memory, giving the appearance of instant
or at least fast screen updates. Our concern now is how to form the memory
image of the desired display. We want to form the images in a way that
simplifies the effort needed to manage several distinct areas of the
screen. For example, a screen may be divided into viewing areas devoted to
user commands, program status, and text. A provision might also be made to
display help frames in a region that partially or completely overlays
another area of the screen.
While developing the interface presented in this chapter, I wrote a
test driver program (SB_TEST) that exercises each interface function to
assure correct action. The test program creates several screen regions (or
windows) that can be filled, cleared, scrolled, and so on, under control of
the user at the keyboard. We'll develop the functions first, then apply the
driver to show off some of the capabilities of the screen interface.
An element in display memory consists of two bytes: one representing a
character and the other an associated attribute. This design is
considerably more flexible than the design used by many low-cost terminals
that use a single byte for each screen location. The latter design requires
that some screen positions be "wasted" (they appear blank) because they are
used to select attributes for all positions that follow until the next
attribute-setting position. (The attribute positions are referred to by
some as "magic cookies.") The PC design requires twice as much storage but
permits attributes to be assigned on a character-by-character basis.
We will form a virtual-screen buffer as a two-dimensional array of
"cells," with each cell being represented as a union. This allows our
programs to access each cell as individual character and attribute bytes or
as a single character/attribute pair. The definitions of a cell, the
virtual-screen buffer, and other components of the buffered screen
interface are contained in sbuf.h.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 318 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
Buffer Management Functions
One of the other components referred to above is a definition of a
REGION, a structure that effectively defines a window, which in this
implementation is simply a rectangular region of the screen buffer. To keep
things reasonably simple, this interface keeps all screen data (characters
and attributes) in the screen-buffer array, which is created by sb_init().
There are no "shadow" buffers for individual windows.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 320 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
The screen buffer is allocated as global data in sb_init.c. It has
type union CELL. The declaration
union CELL Scrnbuf[SB_ROWS][SB_COLS];
uses the constants defined in sbuf.h to allocate 4000 bytes of storage for
the screen buffer. The sb_init.c file also allocates the Sbuf structure
that holds screen-buffer control information. Sbuf is of type struct
BUFFER. It contains "cursor" row and column position variables, a pointer
to the screen-buffer array, 2 bytes (unsigned short) of status flags, and
arrays of column positions that mark the beginning and end of changes to
each screen-buffer row. The range limits allow the routine that copies the
screen buffer to physical-display memory to restrict the number of
characters that must actually be written, thus minimizing the time needed
to do display updates.
The sb_show() function (in sb_show.c) is capable of using several
methods to copy the screen-buffer contents to display memory. The buffer-
copying method that is actually selected depends upon the user-specified
access type (UPDATEMODE equal to DIRECT or BIOS access) and the type of
display system that is installed in the host machine (IBM monochrome
display adapter, or MDA, versus all other types).
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 321 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
The sb_show() function in DIRECT update mode checks to see how many
screen-buffer rows differ from those currently displayed. If there are two
or fewer, they are copied individually. Any greater number of changed
screen rows causes a block-copy operation (based on the cpblk() function
that is described in Chapter 11) to be performed. If the DOS variable
UPDATEMODE is not set, BIOS is assumed. Adding the line
set updatemode=direct
to one's AUTOEXEC.BAT file (or typing it on the DOS command line) causes
screen updates to use the faster, but less portable, direct-access
method.
General Window Functions
Now that we can create a screen buffer and copy it to display memory,
we can proceed to prepare functions that place the data to be displayed
into the buffer. The user sees nothing until the image is fully formed and
copied. This allows programs to handle overlapping windows and special
effects without distracting the user. By writing the least amount of data
to the screen, this buffered-screen interface also speeds up the apparent
performance of programs. The following functions do the work.
► sb_new(). As noted earlier in this chapter, screens are easier
to manage if they are divided into separate regions where
each region is devoted to a single purpose. The function
sb_new() is used to describe a new window by allocating a window-
control structure that contains row and column values for two
opposite corners (upper left and lower right) of the window and a
scrolling region within it, a "cursor" position, and a status flag
word. Initially, the scrolling region is set to the same dimensions
as the window itself. It can be set to other dimensions by calling
sb_set_scrl().
Space for the control structure is allocated by a call to
malloc(). NULL is returned if there is not enough memory
satisfy the request. A successful request results in a pointer to
the window-control structure (struct REGION *) being returned. All
positioning, character and string operations, scrolling, and
clearing is done with respect to this window pointer.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 324 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
► sb_move(). This function is used to move the cursor to a specified
location within a window. Unreasonable requests (outside window
boundaries) elicit an error return code (SB_ERR). If the request is
in bounds, both the window-relative and buffer-relative cursor row
and column values are updated and SB_OK is returned.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 325 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
► sb_fill(). A window may be filled with a given character and
attribute pair by calling sb_fill(). Every cell in the specified
window is set equal to the character and attribute values passed as
arguments. Two variations on the theme, sb_filla() and sb_fillc(),
are used to fill a window with either an attribute or a character
while leaving the other component of each cell undisturbed.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 326 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
► sb_putc(). This function puts a character into a window at the
current cursor position and advances the cursor. Accommodation is
made for wrapping at the end of a window row, and scrolling is
forced when a character is written to the last position of the last
row of a window. Scrolling can be disabled by clearing the
SB_SCROLL bit in the window flag word (the default state). Standard
format-effector control codes (newline, carriage return, linefeed,
and tab) are treated normally.
► sb_puts(). A string may be added to a window using sb_puts(), which
simply calls sb_putc() to do its work for each character in the
string.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 328 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
► sb_rca(). Used to read the character and attribute values from the
screen-buffer cell at the current window cursor location. The
functions sb_ra() and sb_rc() read the individual attribute and
character values, respectively. This is a simple task because all
the data is available in the screen buffer. In the case of
sb_rca(), an unsigned short quantity is returned. The sb_ra() and
sb_rc() functions return an unsigned char value.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 331 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
► sb_scrl(). To scroll a window vertically, use sb_scrl(). This
function scrolls a region within a window. The scrolling region is
frequently made smaller than the full window size to allow for a
boxed border and a title line and other such trimmings. A
comparable effect can be obtained by defining a window within a
window, but then the regions must be managed individually, adding
to program overhead.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 332 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
► sb_wca(). The sb_wca() function writes both a character and an
attribute to a window at the current cursor position. A repetition
count tells the function the extent of the write operation. The
cursor position is not changed. The sb_wa() and sb_wc() functions
write an attribute and a character, respectively, to a window. Note
that sb_wca() combines the two unsigned char values into a single
word by shifting the attribute left eight positions and bitwise
ORing the result with the character value. We could achieve the
same result by using separate assignment statements for the
attribute and character components of the screen-buffer cell. The
"write" routines are useful for such tasks as displaying horizontal
portions of character graphics and changing the highlighting of
text strings like those used in menu bars and pop-up selection
menus.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 335 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
Box-drawing Functions
A portion of the screen that demands special attention is often
highlighted by drawing a box around it. Some screen designs require that
particular areas of the screen be set off by box designs that signify their
purposes. For example, a program that displays multiple windows might
identify inactive windows by surrounding them with single-line boxes while
highlighting the currently active window with a double-line box.
The IBM extended-ASCII character set (codes 128 through 255), which is
supported by nearly all PC-compatible computers, includes a group of line-
drawing characters that are suitable for drawing boxes and other shapes
with various combinations of single and double lines, full and partial
blocks, and various regular dot patterns.
The sb_box() function can be called to draw a box around the perimeter
of a window. The box is drawn entirely within the bounds of the specified
window; therefore, to avoid overwriting it, text functions should be
instructed to write to an area at least one character within the box. Box
corners and edges are formed from characters that match correctly at all
junctions. The box types are defined in the box.h header file.
/*
* box.h -- header for box-drawing functions
*/
typedef struct box_st {
short ul, ur, ll, lr; /* corners */
short tbar, bbar; /* horizontal bars */
short lbar, rbar; /* vertical bars */
} BOXTYPE;
/* box types */
#define BOXASCII 0
#define BOX11 1
#define BOX22 2
#define BOX12 3
#define BOX21 4
#define BOXBLK 5
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 338 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
The Screen-Buffer Library
The sbuf.lib is a library that contains the object modules for each of
the screen-buffer functions just described. The library has wide
applicability to screen-oriented programs. It will reside in the \lib\local
subdirectory for easy access by our programs. The makefile, sbuf.mk, using
the inference rules in tools.ini, keeps all objects and the library file
current and puts files in the required places. If you use a different
directory scheme, you should modify the makefile to accommodate the
differences.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 340 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
# inference rules for SB_TEST program
# (small model with function prototyping enabled)
[make]
.c.obj:
msc -DLINIT_ARGS $*;
A Test Driver Program
The SB_TEST program (sources are in files sb_test.c and sb_test.h)
alluded to earlier is one of several programs I used to test the SBUF
library functions as they were being developed. The makefile (sb_test.mk)
presumes that the screen-buffer function library is up to date. To use the
test driver program, simply type its name at the DOS prompt.
SB_TEST creates several windows and places some text in each. On a
color display, each window sports its own color scheme. On a monochrome
display, the status window displays in reverse video and the help window
contains bold text; the other windows display normal text.
/*
* sb_test.h -- header for sb_test program
*/
/* dimensions of the display windows */
#define CMND_ROW 0
#define CMND_COL 0
#define CMND_HT 1
#define CMND_WID SB_COLS
#define STAT_ROW CMND_HT
#define STAT_COL 0
#define STAT_HT 1
#define STAT_WID SB_COLS
#define TEXT_ROW (CMND_HT + STAT_HT)
#define TEXT_COL 0
#define TEXT_HT (SB_ROWS - TEXT_ROW)
#define TEXT_WID SB_COLS
#define HELP_ROW 5
#define HELP_COL 5
#define HELP_HT 18
#define HELP_WID 70
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 342 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
# makefile for SB_TEST driver program
LLIB = c:\lib\local
LINC = c:\include\local
sb_test.obj: sb_test.c $(LINC)\sbuf.h sb_test.h
sb_test.exe: sb_test.obj $(LLIB)\sbuf.lib $(LLIB)\bios.lib
$(LLIB)\dos.lib
link $*, $*, nul, $(LLIB)\sbuf $(LLIB)\bios $(LLIB)\dos;
After compiling and linking the program, try using scrolling and
clearing commands in the help and text windows to see how the command
actions are implemented. Of course, a real program would display real text
rather than the repeated sequences of letters that are produced by the test
driver.
The commands are described in the following table:
═══════════════════════════════════════════════════════════════════════════
COMMAND DESCRIPTION
───────────────────────────────────────────────────────────────────────────
Up and down arrows │ Scroll up and down by one row
PgUp, PgDn │ Scroll up and down by one page (a windowful
│ minus one row)
Alt-c │ Clear the current window
Alt-h │ Display a help frame
Alt-s │ Fill the status window
Alt-r │ Read in a text file
Alt-t │ Fill the text area
Esc │ Clean up the display and quit
───────────────────────────────────────────────────────────────────────────
The file-reading operation in SB_TEST demonstrates how implicit
scrolling works. The scroll is called by the sb_putc() function, called
either directly or indirectly by sb_puts().
When used in the BIOS update mode, the screen-buffer function library
is reasonably portable. Direct mode is, of course, portable only to highly
PC-compatible computers. In the next chapter, we will look at the most
portable of the screen management tools: the ANSI device driver.
Chapter 13 The ANSI Device Driver
DOS versions 2.00 and later can be extended to use peripheral devices
not planned for in the basic design of DOS. The use of installable device
drivers is the basis of this extendability. ANSI.SYS, the best known of
these device drivers, is supplied with DOS to handle special keyboard and
screen features. This chapter focuses on ANSI.SYS and its uses.
ANSI Basics
The American National Standards Institute (ANSI), long before becoming
involved in efforts to standardize the C programming language, proposed
terminal and computer-equipment standards that have since become adopted by
the computer industry. Adherence to the standards is voluntary, but
terminal and computer manufacturers who choose to ignore them do so at
their own peril. Nearly all terminal equipment built since the late 1970s
conforms, more or less, to the ANSI standards.
Conformance to the ANSI standards can be claimed as long as functions
that are implemented use the specified codes to invoke them; it is not
necessary to implement every function and capability described by the
standards. In addition, the ANSI standards permit the implementation of
private functions that are invoked by codes outside the range of those
specified. Thus, a Digital Equipment Corporation VT100 terminal, which does
not have insert- and delete-line capabilities, and which has many private
capabilities, is considered to be an ANSI-conforming terminal.
The standard that concerns us most directly with regard to our program
designs is X3.64--1979. (The number following the dash represents the year
in which the standard became effective or was last revised.) This standard
defines a set of "additional controls" for use with the ASCII character set
defined in X3.4--1977 (a revision of X3.4--1968) and the code extensions
defined in X3.41--1974. The additional controls are invoked by using
control-function sequences and strings, which are commonly referred to as
escape sequences because the ASCII escape code (ESC, 27 decimal, 16 hex) is
used as the initial character. The escape code is the signal that
identifies the codes that follow as control information rather than
ordinary graphic symbols.
The uses of ANSI control sequences are primarily those of screen and
keyboard handling, but other control tasks, such as switching to alternate
character sets and responding to requests for terminal type identification
("Who are you?"), are covered. The standard accommodates change with built-
in mechanisms for adding controls and with periodic review and revision as
circumstances dictate.
ANSI control sequences have the general form shown in Figure 13-1.
The ASCII escape (shown as ESC--a single code, not three characters) is
the introducer. It is followed by a string called the intermediate
bit combinations, usually a mixture of numeric and alphabetic characters,
and a final character that represents the command to be executed.
EXAMPLE: cursor position (CUP)
ESC [20;42H
▒▒▒ ▒▒▒▒▒ ▒▒
│ │ │
│ │ │
┌───────────────────────────────────┘ │ │
│ │ └─┐
│ ┌───────────────────┘ │
FORMAT: │ │ │
│ │ │
ESC (code 27) (optional parameters) (command name)
│ │ │
┌──────┴──────┐ ┌───────┴───────┐ ┌──────┴──────┐
│ │ │ │ │ │
│ introducer │ │ intermediate │ │ final │
│ character │ │ string │ │ character │
│ │ │ combinations │ │ │
└─────────────┘ │ │ └─────────────┘
The Escape code └───────────────┘ The letter H is the
introduces the [ begins the intermediate "cursor position"
control sequence. string, which includes command.
the row and column
numbers separated by
a semicolon.
FIGURE 13-1 ▐ ANSI control sequence formation
The string in the middle of a control sequence provides variable data
that modifies the behavior of the basic command. For example, the sequence
for moving the cursor forward (CUF) is ESC[#C. The sharp sign (pound sign
or octothorpe, if you prefer) is a replaceable numeric parameter. If a
value is given, it specifies the number of columns to move the cursor. If
the value is zero or not specified, the default value of one is used.
Attempts to move past the end of the line are ignored.
The example given in Figure 13-1 shows how to form the sequence
that moves the cursor to an absolute position on the screen. Impossible
requests are simply ignored.
Display Memory
Figure 13-2 shows how a character and its associated attribute
are stored in a PC's display memory. In a 25-line by 80-column text
mode, each display-screen image requires a total of 4000 bytes of
memory, half devoted to characters and half to attributes. The even-
numbered byte (starting at zero) of each display-memory word holds the
code for the character. The odd-numbered byte holds the attribute.
7 6 5 4 3 2 1 0
┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐
│ │ │ │ │ │ │ │ │ character byte (even)
└──┬──┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
│ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
│ │
│ ASCII character code
│
└──extended character flag
7 6 5 4 3 2 1 0
┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐
│ B/I │ R │ G │ B │ I │ R │ G │ B │ attribute byte (odd)
└──┬──┴─────┴─────┴─────┴──┬──┴─────┴─────┴─────┘
│ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ │ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
│ │ │ │
│ background color │ foreground color
│ │
│ └────────────────────foreground intensity
└──blink or background intensity
FIGURE 13-2 ▐ Character and attribute bytes
The low seven bits (0--6) of a character byte uniquely specify one
of the possible 128 character codes (0--127) defined in the ASCII character
set.
The most significant bit of the character byte, when set (1),
indicates an extended-ASCII code, one that falls in the range 128 through
255. On an IBM PC and most compatibles, these are foreign alphabetics,
block graphics, and other special characters.
The attribute byte is used to control monochrome and color
attributes for each character on an individual basis. As Figure 13-2
shows, bits 0 through 2 specify the foreground color, and bit 3 indicates
the intensity level of the foreground. Together, these four bits determine
the color value of the character, providing a range of 16 colors. On an
IBM monochrome adapter, only three of the values produce unique results
(0 = black, 1 = underlined, and 7 = white). All other values below 7
produce normal white, while values between 8 and 15 produce "bright"
versions of their respective values in the low range. Thus, the value 15
produces a bright white foreground.
The background color (or grey level) of each character cell is
specified by bits 4 through 6. The background can have one of only eight
colors.
Bit 7 serves one of two purposes depending on whether blinking is
enabled or disabled. When the blink feature is enabled, setting background
values above 7 (bit 7 set) produces quite an obnoxious blink in the
foreground of the associated character. Disabling the blink produces a far
more pleasing result: a stable background that can be any of 16 different
colors on suitable display equipment. Chapter 5 presents routines for
controlling the blink feature.
ANSI.SYS
As mentioned previously, the ANSI.SYS file provided with DOS versions
2.00 and later is an installable device driver that provides a subset of
ANSI-standard screen and keyboard control functions. When loaded into the
computer's memory, the ANSI driver monitors keyboard input and screen
output data streams.
For screen output, when one of the escape sequences handled by the
ANSI driver is received, the driver performs the associated action, such as
moving the cursor or setting a display attribute. During keyboard
operations, individual keys and combinations of keys can be intercepted and
converted into customized character strings. Most codes are simply passed
on without anything being done to them.
ANSI escape sequences can be called from within our C programs or even
from DOS batch files. The SetColor program described later in this chapter
is an example of a C program that uses the ANSI driver to do most of the
work involved in controlling the attributes of a color text display.
Installing ANSI.SYS
Installation of ANSI.SYS is trivial. DOS uses the file CONFIG.SYS to
configure the system when it is started. If the ANSI.SYS file is in the
directory c:\dos, for example, adding the line
device=c:\dos\ansi.sys
to CONFIG.SYS will cause DOS to load the ANSI device driver each time the
system is turned on ("cold boot") or restarted using Ctrl-Alt-Del ("warm
boot").
A program that depends on the ANSI device driver should test for its
presence. If the ANSI driver is not loaded into memory, the program should
abort and provide instructions to the user about installing it. A method of
testing for the presence of the device driver is presented in the next
section of this chapter.
The ANSI Control Codes
Figure 13-3 (on the next page) summarizes the ANSI standard
control capabilities and indicates which are supported by the DOS
ANSI device driver. Note that some of the supported capabilities (for
example, erase in display, ED) are implemented in a simplified way, and
that others (for example, set mode, SM, and reset mode, RM) are decidedly
implementation-dependent, being tied intimately to the IBM PC hardware
architecture.
The following descriptions are intended to supplement the
information presented in the documentation provided with DOS, particularly
with respect to usage guidelines. The subsequent section of this chapter
develops an interface package to the ANSI driver and provides practical
examples of these codes in use.
CONTROL SEQUENCES WITH NUMERIC PARAMETERS
╓┌─────────────┌───────────┌─────────────────────────────────────────────────╖
ANSI.SYS Mnemonic Description
ANSI.SYS Mnemonic Description
───────────────────────────────────────────────────────────────────────────
No CBT Cursor Backward Tabulation
No CHA Cursor Horizontal Absolute
No CHT Cursor Horizontal Tabulation
No CNL Cursor Next Line
No CPL Cursor Preceding Line
Yes CPR Cursor Position Report
Yes CUB Cursor Backward
Yes CUD Cursor Down
Yes CUF Cursor Forward
Yes CUP Cursor Position
Yes CUU Cursor Up
No CVT Cursor Vertical Tabulation
No DA Device Attributes
No DCH Delete Character
No DL Delete Line
No ECH Erase Character
No FNT Font Selection
No GSM Graphic Size Modification
No GSS Graphic Size Selection
ANSI.SYS Mnemonic Description
No GSS Graphic Size Selection
No HPA Horizontal Position Absolute
No HPR Horizontal Position Relative
Yes HVP Horizontal and Vertical Position
No ICH Insert Character
No IL Insert Line
No NP Next Page
No PP Preceding Page
No REP Repeat
No SD Scroll Down
No SL Scroll Left
No SPI Spacing Increment
No SR Scroll Right
No SU Scroll Up
No TSS Thin Space Specification
No VPA Vertical Position Absolute
No VPR Vertical Position Relative
CONTROL SEQUENCES WITH SELECTIVE PARAMETERS
╓┌─────────────┌───────────┌─────────────────────────────────────────────────╖
ANSI.SYS Mnemonic Description
─────────────────────────────────────────────────────────────────────────
No CTC Cursor Tabulation Control
No DAQ Define Area Qualification
Yes DSR Device Status Report
No EA Erase in Area
Yes ED Erase in Display
No EF Erase in Field
Yes EL Erase in Line
No JFY Justify
No MC Media Copy
No QUAD QUAD
Yes RM Reset Mode
No SEM Select Editing Extent Mode
Yes SGR Set Graphic Rendition
Yes SM Set Mode
No TBC Tabulation Clear
FIGURE 13-3 ▐ ANSI control sequences
Cursor Position (CUP) ESC[#;#H
Horizontal and Vertical Position (HVP) ESC[#;#f
These two control sequences do the same job. They move the cursor to
the position designated by the parameters: the row (the first #) and the
column. Illegal requests are ignored. The upper left corner of the screen
is designated row 1, column 1. Missing or zero-valued parameters default to
1; therefore, a CUP or HVP sequence with no stated parameters is a synonym
for "homing" the cursor.
Cursor Up (CUU) ESC[#A
Cursor Down (CUD) ESC[#B
Cursor Forward (CUF) ESC[#C
Cursor Backward (CUB) ESC[#D
Each of these control sequences moves the cursor by # positions (the
default is 1) in the specified direction. Attempts to move beyond either
end of a row or off the top or bottom of the screen are ignored. For
example, if a request to move the cursor down exceeds the number of rows
between the one containing the cursor and the bottom row of the screen, the
cursor is deposited on the bottom row. No scrolling occurs.
Device Status Report (DSR) ESC[6n
Save Cursor Position (SCP) ESC[s
Restore Cursor Position (RCP) ESC[u
These three control sequences are designed to assist in cursor
control. The first two determine where the cursor is located, and the third
returns the cursor to a saved location. The DSR request causes the ANSI
driver to place a string, in the form ESC[#;#R plus a trailing carriage
return, into the keyboard buffer. The position parameters are stored as
two-character representations of the numbers, so the string contains a
total of nine characters. The ansi_cpr() function described in the next
section can be used to retrieve the row and column data.
The SCP/RCP pair of control sequences can be used to save a cursor
position so that it can be restored after an intervening operation moves
the cursor from the saved position. Only one position can be saved for
subsequent restoration. If multiple calls are made to SCP, a call to RCP
will restore the cursor position obtained by the most recent call to
SCP.
Erase in Display (ED) ESC[2J
Erase in Line (EL) ESC[K
The ED control sequence erases the entire screen by displaying blanks
in the prevailing video attribute (see SGR). The cursor is deposited at the
upper left corner of the screen. EL erases the characters from the cursor
position to the end of the current row. The "erased" positions are set to
blanks in the prevailing attribute, and the cursor is left at its current
position.
CAUTION
Some versions of the ANSI device driver provided with
MS-DOS (for example, MS-DOS 2.11 for the AT&T PC6300)
erase by setting characters to blanks in the normal
(white on black) attribute rather than the attribute
set by SGR.
Set Graphic Rendition (SGR) ESC[#; ... ;#m A graphic rendition is
the visual style of displaying a graphic symbol (number, letter,
punctuation mark, and so forth), which in the IBM PC context translates
into the attribute associated with a character. The ANSI driver uses the
SGR control sequence to set the intensity, blink status, and color
attributes of characters on a color adapter. On a monochrome system, SGR
can be used to set intensity, blink status, and underlining. Some IBM-
compatible machines (COMPAQ and AT&T are two examples) permit grey-scale
settings on monochrome monitors in response to color attribute requests.
SGR can take a series of numeric parameters in a single call. The
effect of a single call to SGR with multiple parameters is the same as that
produced by a series of calls to SGR with one parameter per call, but the
former will be faster because of reduced function-call overhead.
Set Mode (SM) ESC[=#h
Reset Mode (RM) ESC[=#l
The mode control sequences that are supported by the ANSI driver
set the IBM PC and compatible hardware-dependent video modes for the
color/graphics adapter and a "wrap-at-end-of-line" mode. The driver does
not support switching to a monochrome adapter from a color/graphics
adapter. Both SM and RM can be used to set a video mode with the parameters
0 through 6. Setting a different video mode clears the current mode. Using
SM with a parameter of 7 enables wrap; RM given the same value disables
wrap.
Pros and Cons of ANSI.SYS
The primary reasons for using the ANSI device driver are simplified
programming and portability of programs to other versions of MS-DOS and
PC-DOS and to the machines that run DOS. The amount of programming effort
required to do many screen operations is reduced to simple calls to ANSI
control sequences. Compare the short cursor-positioning macro shown in the
next section to the BIOS-interrupt-based function presented in Chapter 5;
both do the same job.
Unfortunately, some limitations of the ANSI driver make it unsuitable
for many applications. For some purposes--full-screen text editing is a
good example--it is simply too slow. In addition, ANSI.SYS implements only
a very limited subset of ANSI X3.64. It has no concept of fields, protected
display areas, display regions (useful for windowing), and many other
important capabilities.
An ANSI Interface Package
In spite of its limitations, the ANSI driver has its place. It
comprises a useful but limited subset of ANSI capabilities that can do a
surprising amount of work. The interface to the ANSI driver described here
uses mostly macros in a header file to accomplish the tasks of moving the
cursor, finding its position, setting video attributes, clearing the
display, and setting video modes. A few supporting C functions built on the
ANSI driver take care of some higher level tasks, such as testing to see
whether the driver is installed.
The macros and definitions of the video attributes and modes are
contained in the header file ansi.h, which resides in the \include\local
directory. You should #include the file in any C program or function that
will need to access the keyboard or screen through the ANSI device
driver.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 358 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
ANSI color attributes follow a different sequence from the standard
IBM attribute set. The ansi.h file contains definitions for the basic
colors and "shift" values (offsets) for foreground and background attribute
specifications. In addition, there are definitions for normal, bright,
blink, reverse, and invisible attributes. The normal attribute effectively
clears all other special attributes. Thus, to set the foreground to bright
green, one uses the statements
ANSI_SGR(ANSI_GREEN + ANSI_FOREGROUND);
ANSI_SGR(ANSI_BRIGHT);
which will cause all subsequent characters sent to the screen to be
displayed in the requested attribute until a new specification is
given.
A typedef is used to create a POSITION data type that is used by some
of the functions in the SetColor program to keep track of which color
attribute position (foreground, background, or border) is being attended to
in the processing of attribute specifications. Each position must be
handled in its own special way, as we'll see shortly.
In addition to the macros defined in ansi.h, our interface employs
several functions. The ansi_cpr() function is used to request a device
status report (ANSI_DSR) and then to read the cursor-position data stored
in the keyboard buffer. The format of the response is a string of nine
characters, starting with ESC and ending with a carriage return. This is
called the cursor position report, hence the name ansi_cpr() for the
function.
The ansi_cpr() function calls getkey(), a DOS interrupt-based routine
(INT 21H) that was defined in Chapter 5, to gather input from the keyboard
buffer. Here is the code for ansi_cpr():
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 361 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
Because the characters are placed in the buffer in a reasonable order,
successive keyboard read calls can be used to gather and process the
numeric values easily. We must read every character in the string, even
though we're really interested in only four of them, so that no residue is
left behind to befuddle other parts of our program's input processing. The
formal parameters row and col are pointers to storage outside the function.
A character representation of the tens digit of a number (row or
column) is obtained by a call to getkey(). It is converted to its numeric
representation by subtracting 0 (decimal 48) and then multiplying by ten
(its numeric weight). The value is then added to the converted value of the
next character obtained by getkey(), which is the units digit, to produce
the needed number in integer format.
We now have the ability to determine the cursor's position and to save
it or pass the information on to other parts of our programs. But, if the
ANSI device driver is not already loaded into memory, calling ansi_cpr()
will cause the computer to appear to lock up. We need to be sure the driver
is installed before calling functions that depend on its presence. The
function ansi_tst() calls ANSI_CUP, which sets the cursor to a known
location, and then calls our bios library function readcur() to read the
cursor location. If the row and column positions match, the driver is
probably loaded. I say "probably" because the routine will falsely claim
the driver is loaded if the cursor happens to be at the test location
before the ANSI_CUP call. (However, this is not likely for the test values
used.) To be certain, a series of two tests at differing locations could be
used.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 362 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
The code for ansi_tst() aborts with an error message and instructions
to the user about installing the ANSI driver. An alternative design might
simply return a true/false response and let the program deal with the lack
of a driver in some other way, such as switching to a mode of operation
that does not depend on the ANSI driver being loaded. The file ansi.mk is
the makefile to compile the separate components of the ANSI interface
package.
# makefile for the ANSI library
LINC=c:\include\local
LLIB=c:\lib\local
ansi_cpr.obj: ansi_cpr.c $(LINC)\ansi.h
msc $*;
lib $(LLIB)\ansi -+$*;
ansi_tst.obj: ansi_tst.c $(LINC)\ansi.h $(LINC)\video.h
msc $*;
lib $(LLIB)\ansi -+$*;
The SetColor Program
Now we'll do something useful with the ANSI interface. SetColor,
invoked as SC to save keystrokes (in the UNIX tradition), is designed to be
used with a computer that has a color display system. With minor
modifications it can be used in a limited way with monochrome systems,
too.
Figure 13-4 is the manual page for SetColor. The program
sets foreground and background attributes via ANSI control sequences. The
ANSI driver provides no way to control the border attribute, so we resort
to a BIOS routine, palette(), developed in Chapter 5, to do that job. The
use of palette() may make the program nonportable to some nominally IBM-
compatible equipment.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the figure found on page ║
║ 364 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
FIGURE 13-4 ▐ Manual page for SetColor
The following pseudocode description of SetColor reveals that the
program exhibits extreme schizophrenia. On the one hand, it operates in an
interactive mode when invoked as SC without arguments. The user selects
attributes by pressing function keys. Screen updates immediately show the
user what the combined attribute selections look like. However, when called
with attribute specifications as arguments, SetColor runs in batch mode.
All attributes and intensity modifiers, if any, are taken from the command
line. In this mode, SetColor can be used to control screen appearance from
within DOS batch files.
if ANSI driver not loaded
print message
exit
if not in color text mode
print message
exit
if command-line argument given
run in batch mode
else
run in interactive (menu) mode
clear the screen
exit
The SetColor program consists of several functions, each packaged in
its own file. Some of the functions have general applicability to other
tasks and are, therefore, placed in local libraries.
Starting at the top, the source file sc.c contains the main()
function. In main(), two tests are made that determine whether the SetColor
program runs to completion or quits early. The first test, ansi_tst(),
checks to see that the ANSI device driver is installed. If not, the program
terminates. If the driver is installed, the second function, iscolor(),
finds out what video mode is in use. Rather than do anything fancy at this
point, we opt to simply terminate program execution with a help message if
the mode is not a standard color-text mode. The user's selection of batch
or interactive operation, based upon the presence or absence of command-
line arguments, is also detected here.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 367 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
If attribute arguments are given, the function parse() is called with
the argument list obtained by main(). The job of parse() is to analyze each
argument and form attribute or color specifications that can be processed
by the low-level ANSI interface functions. If no arguments are given, the
menumode() function is called. Let's first examine the batch processing
functions.
Batch Mode
If parse() receives a single attribute specification, it checks to see
whether it is one of the special attributes (normal, reverse, invisible, or
blink). None of these takes an optional intensity modifier, and each can be
handled easily by a single call to ANSI_SGR. If the lone argument is not
one of these special attributes or if there are two or more arguments,
parse() falls through to a loop that analyzes each argument in turn and
creates attribute specifications in a form suitable for processing by the
ANSI interface functions.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 368 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
The basic problem we face is receiving color/attribute specifications
as text strings and converting them to numbers. We will also need to
account for the differences between the standard IBM attribute numbers and
the ANSI attribute numbers described earlier. The IBM attribute numbers are
defined in the header file ibmcolor.h, which resides in the \include\local
directory.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 370 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
The colornum() function converts text strings into these IBM color/
attribute numbers. Given the string green as an argument, for example,
colornum() returns the defined value IBM_GREEN, which is the number 2. Two
synonyms for bright (bold and light) are allowed, and yellow is treated as
a synonym for bright brown. The following is the source code for colornum():
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 371 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
The function uses a simple sequential search through a table of
string/number pairs that compares the first three letters of the string
argument with the table entries. The strlwr() standard library function
converts the incoming argument to lowercase before any comparisons are
made. Its return type is cast to void because the pointer to a character is
not used. If no matching string is found, colornum() complains by returning
-1 instead of a valid color number.
A color number thus produced may be passed as an argument to
setattr(), which takes a position indicator and an attribute value as
input. It outputs an ANSI control sequence by applying the ANSI_SGR macro
to a properly converted attribute number. The process sounds more confusing
than it really is. First, the code for setattr():
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 373 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
The pos argument has type POSITION (defined in the ansi.h header
file), and it tells setattr() which rules to apply to the attr argument.
The attribute argument contains two kinds of information lumped together:
The low three bits (0--2) represent the base color or video attribute, and
bit 3 indicates the intensity.
An intensity modifier is treated differently when it is applied to a
foreground attribute than when it is applied to a background attribute. In
the foreground case, bit 3 of the intensity modifier sets the foreground
intensity bit in the attribute byte (bit 3). In the background case, it
sets the blink bit (bit 7). If blinking is enabled (the default), setting
the blink bit causes the foreground to blink and does not change the
background intensity, resulting in a useful range of eight background
colors. If blinking is disabled, setting the blink bit causes the
background intensity to be bright. The full range of 16 background colors
can be obtained with blinking disabled. The setattr() function calls
ANSI_SGR once if attr is for normal intensity or twice for high
intensity.
A table of foreground and background attribute values in the array
ibm2ansi[] contains the conversions from IBM attributes--represented by the
index into the array--to the ANSI values needed by ANSI_SGR. Thus, the
expression ibm2ansi[IBM_BLUE], where IBM_BLUE is defined in
ibmcolor.h as the number 1, yields the value represented by ANSI_BLUE,
which is defined in ansi.h as the number 4.
The border, on display systems that support it, can be set to any of
16 colors by using palette(). There is no need to separate the intensity
information from the rest of the attribute when setting the border because
palette() is a BIOS-based function and uses the IBM attribute values
directly.
That explains batch-mode processing. The other processing path that
can be taken from main() is interactive mode, which is handled by
menumode(). The menumode() function calls upon palette(), setattr(),
getkey(), and a new function, sc_cmds(), to do the bulk of the work.
Interactive Mode
In interactive mode, attributes are selected by pressing function
keys. F1 decrements the foreground color and F2 increments it. The color
values wrap around at the boundaries; therefore, a request to increment
foreground = 15 (white) produces foreground = 0 (black). Similarly, the
F3/F4 pair of function keys are used to select the background attribute,
and the F5/F6 pair cycle through border attributes.
The mainmenu() function returns control to DOS when the Return key is
pressed, leaving the screen cleared to the most recently selected attribute
combination.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 375 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
Because the color/attribute specifications are presented in
interactive mode as numbers rather than as text strings, setattr() can
process them directly. The process of updating the screen after each set of
calls to setattr() means that the list of commands and the current status
information must be rewritten following each keypress that affects the
foreground or background color. This is done by the function sc_cmds(),
which contains a static array that converts IBM color numbers into text
strings suitable for humans. All cursor positioning is done via the
ANSI_CUP macro. Text strings are written to the display by the standard
library function fputs(), which is directed to send its output to
stdout.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 377 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
The file sc.mk is the makefile for SetColor. It collects several of
the interface functions into a special-purpose library, color.lib. This is
done to minimize the number of items that must be specified on the
dependency and link lines that create SC.EXE and keep it up-to-date. A
linker-response file could be used to achieve the same result. To rebuild
the SetColor program after any changes are made to its source files or
libraries, type
make sc.mk
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 378 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
Using SetColor
Aside from being a good demonstration of the ANSI device-driver
interface, SetColor is a useful program. I use it most frequently in batch
files to restore the screen to match my peculiar tastes after some other
program has made a mess of things. Most programs, especially those that do
full-screen operations, have built-in default settings for video
attributes. These programs have no idea what attributes were in use before
they were run; therefore, when they exit, they either leave the attributes
as they are when the program terminates or set them to white on black.
It would be better to have the colors set to the user's preferences,
if possible. This can be achieved in a couple of ways. The first way uses a
DOS batch file to intercept the call to a program. The batch file calls the
real program and then calls SetColor to make things right again. For
example, assume the program in BADPROG.EXE does bad things to the screen
when it exits. A batch file called BADPROG.BAT with the lines
badprog.exe
sc white blue blue
will clear the screen and set the foreground to white on a field of blue
when the program returns to DOS.
This can be generalized further by the use of DOS environment
variables. If environment variables having the names FGND, BKGND, and
BORDER are defined, the batch file can access the values and control the
SetColor program automatically. DOS variable values are obtained in batch
files by surrounding each variable name with percent signs. The revised
batch file looks like this:
badprog.exe
sc %fgnd% %bkgnd% %border%
Of course, SetColor may be called from the DOS command line with
literal attribute specifications. I sometimes change the attributes just to
minimize eyestrain during long programming and writing sessions. It's
remarkable what a good effect a simple change in screen colors can have
(unless it's to something like magenta on red!).
A User-Attribute Function
Here's a way for programmers to be kind to those who will be using
their programs. The function userattr() that follows can be used in
programs to restore the user's attribute preferences just before the return
to DOS. It gets values directly from the DOS environment, just as the batch
file in the previous section does. If no attribute variables are defined,
userattr() uses some reasonable defaults as parameters. The userattr()
function calls on colornum() and setattr() and uses the standard library
functions getenv() and strtok() to look for user preferences and parse them
into recognizable values.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 380 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
The processing of the strings by strtok() is necessary because the
attribute specifications may contain an intensity modifier. The strtok()
function returns a pointer to a null-terminated string token taken from its
first string argument; if no tokens are found, it returns NULL. Acceptable
token separators are spaces and tabs, in any combination. Subsequent calls
to strtok() use NULL as the first argument to request further searching in
the string given in argument one of the first call. If the first call to
strtok() retrieves an intensity modifier, a second call is made to strtok()
on the same string to get the second token, which should be a valid
attribute specifier. Error-checking code prevents bad attribute names from
causing problems.
The next time someone tells you the ANSI.SYS device driver is a
worthless piece of software, you can show them how useful it really
is!
As a fitting end to this development session, echo ESC[2J (look it
up).
Chapter 14 Viewing Files
The next program to be added to our kit of tools is a file viewer.
ViewFile, called VF by its close friends and associates, is such a
tool.
The design for VF has two objectives: to obtain a convenient window
into a file that permits us to look at the file without harming it and to
experiment with some programming techniques that we can apply to other
tasks. We will apply the linked-list techniques to the management of an in-
memory text buffer. In addition, we will use the standard library function
strncmp() to implement a simple but fast search capability.
Some trade-offs made in VF's design are noteworthy. First, we
sacrifice loading speed to obtain very quick response to user commands. By
using an internal buffer, instead of buffering to disk, we provide speedy
response to positioning requests but limit files to a size determined by
the available memory of the host machine. Fortunately, most PCs these days
have lots of memory. Second, VF uses some of the BIOS routines described in
Chapter 5 to handle display interactions. It does not use the buffered
screen interface described in Chapters 11 and 12. Even so, the display
updates are tolerably quick, taking about a second or less for most
operations on a standard PC for a typical file.
ViewFile (VF) Features
The features of ViewFile are summarized in the manual page below
(Figure 14-1).
╔══════════════════════════════════════════╗
║ ║
║ Click to view the figure found on page ║
║ 384 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
FIGURE 14-1 ▐ Manual page for ViewFile
In addition to offering vertical scrolling by screen pages, VF
provides convenient horizontal scrolling as a way of dealing with long
lines. VF accepts lines up to MAXLINE characters (defined as 256 in our
std.h header file) including the null-byte terminator. You can make this
value larger if you wish. The horizontal scrolling feature lets you move
right and left, 20 columns at a time. The horizontal offset is maintained
when vertical movements are made.
If a list of files is presented to VF, the program will view the files
sequentially. Pressing the Escape key tells VF to read the next file in the
list, if any. To quit without viewing any remaining files in the list, type
Q instead. DOS wildcards may be used to specify ambiguous file names.
Therefore, the command
vf \src\*.h
forms a list consisting of all header files in the \src directory on the
current disk drive. VF then reads in the first matching file and displays
the first screen page of its contents.
The forward and reverse search feature looks through each line in the
buffer for a literal string. VF searches around the end of the buffer,
stopping at the first line that contains a match, or returning to the
current line if no match is found. Searches in either direction may be
repeated by pressing a carriage return in response to the "Search for:"
prompt.
VF has a built-in help "frame." When a user types a question mark,
Alt-h, or H, VF overlays the help frame on the text-display area. The next
keypress restores the covered text. Although it is only a brief memory-
jogger, the help frame contains all the information needed to use VF
successfully.
VF should be compiled using the compact memory model so that it can
handle large files. Compact model programs are distinguished by small code
space (less than 64 KB) and large data space (whatever available memory
permits). Files that exceed the capacity of VF produce the message "File
too big to load." To use the compact memory model, you will have to create
compact-model versions of the bios, dos, and util libraries by recompiling
all functions with the -AC option. It is probably best to follow the
pattern established by Microsoft of having separate library files for each
version of a given library. Thus, for example, we might have the files
bios.lib, cbios.lib, lbios.lib, and so on. The linker gets clues from the
object files, so it will use the correct versions of the standard
libraries. The makefile, vf.mk, contains the instructions required by MAKE.
The library of object modules used to construct VF is maintained by vf.mk,
which enables us to bypass the command-line length limitation imposed by
DOS.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 386 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
The initialization file tools.ini in the VF directory instructs MAKE
to verify the number and types of function arguments and to use the compact
memory model. Renaming or deleting this file causes the default small-model
tools.ini file to be read for inference rules.
# inference rules for VF program make
# enable argument checking and compact memory model
[make]
.c.obj:
msc -DLINT_ARGS -AC $*;
Implementation Details
VF is a modest-sized program. Some of its general-purpose functions
are available to other programs.
VF uses an in-memory text buffer. The decision to use an in-memory
buffer has a profound impact on how a few of the functions are designed,
but virtually no impact on the rest. We should try to keep the number of
functions that actually know how the text is stored to a minimum; this
permits us to apply them to a wider range of problems. The functions in the
message module message.c, for example, don't know anything about the text
buffer. Therefore, these message functions can be used unchanged in any
program that uses the BIOS video routines.
The functions in vf.c are also ignorant of the buffer mechanism. The
main() function does the customary work of preserving the user's
preferences for display type, video attributes, and cursor type. It also
aborts the program if the operating environment is a DOS version earlier
than 2.00, because VF is designed to accept pathnames in file
specifications.
The main() function also clears the screen and sets up the basic
display appearance for the VF session. All program and status information,
user input, and feedback messages are confined to the top two lines of the
screen. The remaining 23 lines are used to display text and optional file-
relative line numbers. When help is requested, the help frame is
temporarily overlaid on a rectangular portion of the text display area.
Because the cursor is turned off by VF (except during user input
operations), it is necessary to restore its shape when the program ends.
The clean() function in vf.c is called when the program terminates normally
or when some condition (out of memory, for example) causes an abnormal
termination. The user's original video attribute selection is restored by
clean(), and the cursor is then sent to the home position.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 388 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
A set of manifest constants that controls placement of visual elements
of the VF display, search direction, and the data structure that defines
the text buffer are contained in vf.h. The buffer data structure and its
application will be described in detail later. The constants determine
where the program name and messages to the user appear, which screen line
is the "header" row, and how many screen rows of text display and scrolling
overlap are to be used. To simplify VF a bit, we do not add automatic
configuration to the program. The information and samples in Chapters 7
and 9 give detailed information about configuring programs.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 391 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
For each file named on the command line (either literally or
ambiguously), main() calls vf_cmd(), the ViewFile command module. The
vf_cmd.c file, which contains the source for vf_cmd() and for the auxiliary
function prtshift(), is a large file but it does only a few jobs--all
related to controlling and accessing the text buffer. Chief among these
jobs is setting up the text buffer for the file that is about to be read.
Another major job is handled by the big switch statement within the while
loop in vf_cmd(), which accepts keypresses as commands. Simple positioning
commands are handled directly by vf_cmd(). Commands that require user input
are handled by separate functions.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 392 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
Within vf_cmd() there are several calls on putstr(). This is a BIOS-
based function that displays a string in the prevailing video attribute on
a specified screen page. It advances the cursor by the number of characters
written. (We could also use the BIOS writetty() function to help us with
this task, but writetty() disturbs the video attribute, setting it to the
default, which may not be what we want.)
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 399 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
As noted earlier, the vf.h header file contains the definition of the
buffer node structure. In addition to having forward and backward pointers
to other nodes, each node contains a pointer to a character string, a file-
relative line number, and a "flag" word. The flag word is not used in this
implementation of VF, but it could be used to mark lines for highlighting.
The text buffer management functions are in vf.list.c:
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 399 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
The text buffer used by VF consists of three parts: a group of
"control nodes" that form a doubly linked list, pointers to the buffer
"head" and the "current" position, and independent character strings for
each line in the file being viewed. The node pointed to by head is a dummy
node that is used to ease the work necessary to set up and maintain the
list. Figure 14-2 depicts the text buffer. Note that the control nodes
are in a circular list, which makes searching across the buffer
boundaries easy. It takes the same amount of time to move backward in
the buffer as it does to move forward, because of the use of a doubly
linked list. Most other text buffer designs would exact a rather stiff time
penalty for scrolling backward in a file.
Figure 14-3 shows another aspect of the text buffer-allocation
scheme used by VF. An initial pool of 256 "free" nodes is allocated by a
call to vf_alloc(); the free nodes are controlled by the symbolic
constant N_NODES. The list-header node is established by vf_mklst().
Both vf_mklst() and vf_alloc() call the library function malloc()
to obtain needed storage.
control nodes
│
▒▒▒▒▒▒▒▒▒▒▒▒▒▒ text string buffers
┌─head ┌────────┐ │
│ │ ┌───┐ │ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
│┌──┐ │ │ ┌▼─┬┼─┬─────┬──┐
└│■─┼──┼─┼─►│■ │■ │ │■ │
└──┘ │ │ └┼─┴─┴─────┴──┘
┌──┐ │ │ ┌▼─┬┼─┬─────┬──┐ ┌───────────────────────────────────────┐
┌┤■─┼──┼─┼─►│■ │■ │ │■─┼──►│ │
│└──┘ │ │ └┼─┴─┴─────┴──┘ └───────────────────────────────────────┘
│ │ │ ┌▼─┬┼─┬─────┬──┐ ┌──────────────────────────────┐
└─cur- │ │ │■ │■ │ │■─┼──►│ │
rent │ │ └┼─┴─┴─────┴──┘ └──────────────────────────────┘
│ │ ┌▼─┬┼─┬─────┬──┐ ┌──────────────────────────────────────────┐
│ │ │■ │■ │ │■─┼──►│ │
│ │ └┼─┴─┴─────┴──┘ └──────────────────────────────────────────┘
| | | |
│ │ ┌▼─┬┼─┬─────┬──┐ ┌────────────────────────────┐
│ │ │■ │■ │ │■─┼──►│ │
│ │ └┼─┴─┴─────┴──┘ └────────────────────────────┘
│ └───┘ │
└────────┘
FIGURE 14-2 ▐ Text buffer management
┌────────┐
│ ┌───┐ │
┌──┐ │ │ ┌▼─┬┼─┬────────┐
head│■─┼─┼─┼─►│■ │■ │ │ Inserting a node into the list:
└──┘ │ │ └┼─┴─┴────────┘
│ │ ┌▼─┬┼─┬────────┐ if (insertion will exhaust the
│ │ │■ │■ │ │ freelist)
│ │ └┼─┴─┴────────┘ extract first node from the freelist
│ │ ┌▼─┬┼─┬────────┐ insert the node after the "current" node
│ │ │■ │■ │ │ return a pointer to the next free node
│ │ └┼─┴─┴────────┘
│ │ ┌▼─┬┼─┬────────┐
│ │ │■ │■ │ │
│ │ └┼─┴─┴────────┘
┌──┐ │ │ ┌▼─┬┼─┬────────┐
current│■─┼─┼─┼─►│■ │■ │ │
└──┘ │ │ └┼─┴─┴────────┘
│ └───┘ │
└────────┘ ◄───────────────┐
│
┌──┐ ┌──┬──┬────────┐ │
freelist│■─┼─────►│■ │■ │ │ ►────┘
└──┘ └┼─┴──┴────────┘
┌▼─┬──┬────────┐
│■ │■ │ │
└┼─┴──┴────────┘
┌▼─┬──┬────────┐
│■ │■ │ │
└┼─┴──┴────────┘
┌▼─┬──┬────────┐
│■ │■ │ │
└──┴──┴────────┘
|
|
┌▼─┬──┬────────┐
│■ │■ │ │
└──┴──┴────────┘
FIGURE 14-3 ▐ Buffer-allocation scheme
The header-node pointers are both initially set to point to the header
node itself. This constitutes the empty list. The freelist pointer points
to the chained free nodes. Only the "next" pointers are used for chaining,
and the last free node contains a NULL next pointer. Each line of text is
effectively added to the buffer by the function vf_ins(), which inserts a
free node into the list after the position of the current node. When the
supply of free nodes is about to be exhausted, another block of nodes is
allocated and added to the chain. This process continues as long as there
are more lines to read from the file and there is enough memory to satisfy
the allocation requests.
Looking at vf_cmd.c, we see that after a new node has been inserted
into the list by vf_ins(), there is still more work to be done. The
Microsoft C standard library function strdup() is used to duplicate the
text of the most recently read line from the file. This function also calls
malloc() to obtain an array of characters that is large enough to
hold a copy of the received string, including the terminating null
byte.
COMMENT
Since strdup() is not a UNIX System V library function,
many C compilers may not have it or an equivalent
function in their run-time support libraries. You can
easily mimic the function using malloc(), strlen(), and
strcpy():
#include <stdio.h>
#include <malloc.h>
#include <string.h>
char *
strdup(str)
char *str;
{
char *buf;
buf = malloc(strlen(str) + 1);
return (buf == NULL ? buf : strcpy(buf, str));
}
We are careful to check for error returns that let us know if memory
is exhausted. VF simply cleans up and quits with an error message if it
runs out of memory.
The function vf_del() takes a node out of the list and puts it back on
the freelist. We call vf_del(), and free(), a standard library function,
when the command loop is terminated by a "next file" (Escape) command. This
frees the memory used by the current file, leaving the largest possible
amount for the next file in the list. If we do not free the memory, we
might eventually receive a "File too big to load" error message.
After any command that affects the current buffer position, vf_dspy()
is called to update the text display area of the screen. The version of
vf_dspy() presented here uses the bios library functions that we
constructed in Chapter 5 to position the cursor, control display
attributes, and display text and optional line numbers. I elected to use
global variables only for the video subsystem based on the BIOS interface.
The Boolean variable numbers defaults to FALSE but may be set to TRUE
for the duration of a VF session by using the command-line option -n. The
numbers variable controls the displaying of line numbers. The initial
setting of numbers is passed as an argument from main(), relayed through
vf_cmd(), to vf_dspy(). The user may toggle line-numbering on and off while
viewing a given file, but the state of line-numbering goes back to the
initial setting when the next file from the list is brought into the
viewing buffer.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 405 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
The vf_dspy() function updates the text-display area by calling
putfld(), which outputs a string of characters to a field starting at the
current cursor position. The putfld() function uses a compression technique
that is made possible by BIOS. All sequences of repeated characters are
output by a single call to writec(). This technique reduces the time needed
to update a screen that contains, for example, the large amounts of white
space found in C and Pascal source files. Assembly-language source
listings, on the other hand, usually do not benefit from this compression
technique because they contain long lines and fewer repeated character
sequences.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 407 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
This function becomes part of the bios library described in
Chapter 5. It can be used by any program that uses BIOS to access the PC
screen.
We have to watch out for the boundary conditions imposed by physical
realities. When there are not enough lines left in the buffer to fill the
screen, vf_dspy() must be careful not to leave residue from previous
displays. After displaying the last line of the text buffer, vf_dspy()
clears each remaining screen line and deposits a tilde in the leftmost
column to show that the line is an unused screen line, as opposed to an
empty buffer line.
Two utility functions that are specific to VF reside in vf_util.c. The
first is gotoln(), which implements the absolute go-to-line feature. This
function prompts the user for a line number. If the number is within the
range of lines in the buffer, the current position is updated and the line
sought by the user is brought to the top of the text display area.
The second function is called showhelp(). To assist the user with a
brief memory-jogger of command names and actions, showhelp() displays the
essence of the manual page at the top of the text display area. When the
user has finished reading the help frame, a keypress restores the screen to
its former condition by rewriting all of the text lines.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 408 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
The task of gathering the user's input for the gotoln() function falls
to getstr(), a function that is constructed from several of the dos and
bios library functions presented in Chapter 5. The getstr() function takes
buffer and width parameters as arguments. It echoes keystrokes as it
receives them and interprets a few keys as commands rather than as input.
The user cancels input by pressing the Escape key, and signals the end of
input with a carriage return. Errors may be corrected by pressing Ctrl-H
(backspace). All input is in the form of character strings. If a program
requires numeric input, such as a line number, it must convert the string
to numeric form before using it.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 411 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
Notice that getstr() is actually a simplified version of the
getreply() function that we used in Chapter 6. We don't need a sliding
window or other fancy gimmicks, so we took out all the superfluous
windowing and editing features, made the prompting external, and eliminated
the response stack. What's left is a simple but effective function that
collects a string from the keyboard. Two boundary conditions are addressed:
First, we don't allow the user to backspace beyond the left column of the
response field, and second, we return the response immediately if the user
types past the right column of the response field. You may prefer to
modify the latter condition to require an explicit carriage return before
returning the response.
Additional assistance is provided to the user via the message line on
the right half of the top screen line. The simple message-line manager
consists of three functions contained in message.c. The function initmsg()
establishes the message display area by filling in the values called for by
the MESSAGE data structure defined in the message.h header file. The
message line can be placed anywhere on the screen by changing the values
presented to initmsg(). The showmsg() function displays a message, and
clrmsg() restores the message area to its pristine state. A flag in the
message structure is set when a message is displayed and is reset when the
message area is cleared. If a call is made to clrmsg() and the flag is not
set, no action is taken.
/*
* message.h -- header for message-line manager
*/
typedef struct msg_st {
int m_row;
int m_col;
int m_wid;
int m_pg;
int m_flag;
unsigned char m_attr;
} MESSAGE;
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 413 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
The message-line manager functions are based on bios library routines.
They can be generalized to use either standard library routines or the
screen-buffer routines described in Chapters 11 and 12, which permit
their use in programs that do not use the BIOS interface.
A simple but useful search feature is implemented by the functions in
vf_srch.c. The getsstr() function prompts for a search string and reads it
using getstr(). The string must be literal in this implementation. The
string is presented to search(), which tries to find a line in the
specified search direction that contains a match for the search string.
Searching starts on the line following the current line (or the preceding
line for a reverse search) and continues until a match is found or until
search() returns to the current line. The search is circular because of the
way the buffer is implemented.
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 415 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
/* nlerase -- replace the first newline in a string
* with a null character */
char *nlerase(s)
char *s;
{
register char *cp;
cp = s;
while (*cp != '\n' && *cp != '\0')
++cp;
*cp = '\0';
return (s);
}
╔══════════════════════════════════════════╗
║ ║
║ Click to view the listing found on page ║
║ 417 in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
Figure 14-4 shows VF in operation.
ViewFile/1.0 H=Help Q=Quit Esc=Next
File: vf.c (126 lines)
/*
* vf -- view a file using a full-screen window onto
* ┌──────────────────────────────────────────────────────────┐
*/ │ PgUp (U) Scroll up the file one screen page │
│ PgDn (D) Scroll down the file one screen page │
#include│ Up arrow (-) Scroll up in the file one line │
#include│ Down arrow (+) Scroll down in the file one line │
#include│ Right arrow (>) Scroll right by 20 columns │
#include│ Left arrow (<) Scroll left by 20 columns │
#include│ Home (B) Go to beginning of file buffer │
#include│ End (E) Go to end of file buffer │
│ Alt-g (G) Go to a specified line in the buffer │
extern i│ Alt-h (H or ?) Display this help frame │
int Star│ Alt-n (N) Toggle line-numbering feature │
unsigned│ \ (R) Reverse search for a literal text string │
unsigned│ / (S) Search forward for a literal text string │
unsigned│ Esc Next file from list (quits if none) │
│ Alt-q (Q) Quit │
main(arg│ -------------------------------------------------------- │
int argc│ << Press a key to continue >> │
char **a└──────────────────────────────────────────────────────────┘
{
int ch;
FIGURE 14-4 ▐ The ViewFile program with the help frame displayed
Considerations and Alternatives
If you need to view very large files, the use of external storage is
the best alternative. A theoretical maximum file size of 32 megabytes can
be achieved under current versions of DOS, but some of that space will
probably be needed to manage the data storage.
With external storage, a file would be loaded from disk faster than by
the current VF because only a small piece of the file need be in memory at
one time. However, it's likely that there would be a significant increase
in the time needed to access a particular line in the file because the disk
must be accessed and read for each new portion of the file that is to be
displayed.
An area of the VF design that could use some enhancement is the search
feature. Although literal-string searches satisfy most positioning
requirements, a regular-expression capability can be extremely handy.
Regular expressions are, in essence, text formulas that can be used to form
patterns rather than fixed strings.
Another option to consider is an incremental search. As the user types
characters, the program accumulates them in the search pattern and moves
immediately to a string in the text, if there is one, that matches the
accumulated search string. The advantage of the incremental search is that
the user need only type enough characters to get the match. You would need
to provide an "escape" command to terminate the search when the desired
string is found.
The implementation of VF presented in this chapter wraps around the
buffer boundaries. In most cases this action is appropriate, but not
always. It may be useful to have a "no wrap" option that would terminate
the search at the end (or beginning) of the buffer to avoid unwanted
repositioning.
SECTION V Appendixes
Appendix A Microsoft C Compiler, Version 4.00
The current version of the Microsoft C Compiler at the time of this
writing is version 4.00. The compiler is noted for producing very fast and
compact executable program files. It trades compile time for a high degree
of optimization and produces object code that is arguably in the same
league as that produced by expert assembly-language programmers. "Hand
tuning" to improve performance is virtually unnecessary.
Technical Highlights
The following material presents a general overview of the Microsoft C
Compiler, version 4.00. The compiler system encompasses a three-pass
compiler, extensive runtime library support, and a set of development
tools. See Chapter 1 for additional information about the compiler, the
impact of the proposed ANSI standard for C, and information about DOS and C
compiler programs that assist programmers in the development of
programs.
Two compiler drivers are available with the Microsoft C Compiler. The
MSC driver executes the preprocessor and compiler passes. The CL driver is
similar to the CC driver on XENIX and UNIX, which compiles and links
programs automatically. Either driver can take advantage of a variety of
compile-time options.
Program Size
The Microsoft C Compiler now provides five distinct memory models--
small, medium, compact, large, and huge--plus extensions to produce mixed
models.
► The small model generates tight, efficient code for programs with
up to 64 KB of code and up to 64 KB of data.
► The medium model supports programs with up to 1 MB of code but only
64 KB of data.
► The compact model handles programs with up to 64 KB of code and up
to 1 MB of data.
► The large model extends program space to 1 MB of code with 1 MB of
data.
► The huge model is similar to the large model, but allows individual
arrays to be larger than 64 KB.
Mixed memory models are made possible through the use of near, far,
and huge pointers. These pointers change the addressing conventions for one
or more elements without changing them for the program as a whole.
Overlays
The Microsoft C Compiler single-level overlay linker allows memory
space to be shared in turn by several program modules. Overlays may be
called from the "root" program or from another overlay and are
automatically loaded when needed.
Multiple Math Libraries
The Microsoft C Compiler offers several options for floating-point
operations. If an 8087/80287 math coprocessor is available, the smallest
and fastest floating-point library can be used. And even though a math
coprocessor may not be present, identical math results can still be
obtained from an emulator.
This versatility gives programs the same high degree of precision
whether or not a math coprocessor is available. For maximum speed, the
emulator routines automatically use the math coprocessor if it is
installed.
When full 80-bit precision and consistency with the 8087/80287 math
coprocessor are not required, the alternate math package can be selected.
Supporting a compatible interface for single- and double-precision IEEE
numbers, this math library is optimized for speed.
Network File Sharing
Under MS-DOS version 3.1 or higher with Microsoft Networks version
1.0, or under IBM PC Network, the Microsoft C Compiler supports multi-user
network access with both file and record locking.
Direct Interlanguage Calls
Routines written in the Microsoft C Compiler (version 3.00 or higher),
Microsoft FORTRAN (version 3.30 or higher), Microsoft Pascal (version 3.30
or higher), or Microsoft Macro Assembler can be linked together. So you can
share the investment you've made in developing libraries in a different
language.
This interlanguage calling lets you use the best features of each
language, or upgrade to a new language while retaining libraries written in
the old one. Selected routines can be recoded as needed.
Library Manager
Entire program-development libraries can be built in any mix of C,
Pascal, FORTRAN, and assembly language by using the library manager that's
included with the Microsoft C Compiler.
Once a library is constructed, the library subroutines can be called
directly from your C program. Subroutine calls are resolved when the
finished program is linked.
Utility Programs
The Microsoft C Compiler includes several utility programs; two are
used to increase the efficiency of .EXE files:
► EXEPACK removes null characters from .EXE files and optimizes the
relocation table. This results in smaller files on disk and faster
loading at execution time.
► EXEMOD allows fine-tuning of file header information usually set by
default, such as stack size.
Language Details
This is a summary of data types, keywords, compiler options,
preprocessor directives, and other important aspects of the Microsoft C
Compiler. Implementation-dependent items are grouped separately. Items that
are new in version 4.00 are flagged, as are library routines that are
implemented as macros.
═══════════════════════════════════════════════════════════════════════════
KEYWORDS
───────────────────────────────────────────────────────────────────────────
auto else long switch
break enum register typedef
case extern return union
char float short unsigned
continue for signed void
default goto sizeof while
do if static
double int struct
The keywords const and volatile are reserved for future use.
═══════════════════════════════════════════════════════════════════════════
IMPLEMENTATION-DEPENDENT KEYWORDS
___________________________________________________________________________
cdecl fortran near pascal
far huge
═══════════════════════════════════════════════════════════════════════════
FUNDAMENTAL TYPES
───────────────────────────────────────────────────────────────────────────
char int signed void
double long unsigned
float short
Enumeration types are also included.
═══════════════════════════════════════════════════════════════════════════
TYPE SPECIFIERS
───────────────────────────────────────────────────────────────────────────
char int struct unsigned
double long typedef name void
enum short union
float signed
═══════════════════════════════════════════════════════════════════════════
DATA FORMATS
───────────────────────────────────────────────────────────────────────────
The standard C data types are implemented as follows:
Type Length in Bytes Range
───────────────────────────────────────────────────────────────────────────
char 1 -128 to 127
double 8 10**±306
float 4 10**±38
int 2 -32768 to 32767
long 4 -2**31 to 2**31-1
short 2 -32768 to 32767
unsigned char 1 0 to 255
unsigned int 2 0 to 65535
unsigned long 4 0 to 2**32-1
unsigned short 2 0 to 65535
═══════════════════════════════════════════════════════════════════════════
POINTER SIZES
───────────────────────────────────────────────────────────────────────────
Bytes Address Arithmetic
───────────────────────────────────────────────────────────────────────────
Near Pointer 2 16 bits (offset)
Far Pointer 4 16 bits (offset)
Huge Pointer 4 32 bits (segment offset)
═══════════════════════════════════════════════════════════════════════════
PREPROCESSOR DIRECTIVES
───────────────────────────────────────────────────────────────────────────
#define #endif #ifndef #pragma
#elif #if #include #undef
#else #ifdef #line
═══════════════════════════════════════════════════════════════════════════
STORAGE CLASSES
───────────────────────────────────────────────────────────────────────────
auto extern register static
═══════════════════════════════════════════════════════════════════════════
EXTENDED TYPE MODIFIERS
───────────────────────────────────────────────────────────────────────────
cdecl fortran near pascal
far huge
═══════════════════════════════════════════════════════════════════════════
COMPILER STATEMENTS
───────────────────────────────────────────────────────────────────────────
break default for return
case do goto switch
continue else if while
═══════════════════════════════════════════════════════════════════════════
COMPILER OPTIONS
───────────────────────────────────────────────────────────────────────────
The Microsoft C Compiler supports a large number of compile-time options.
Here is a partial list.
/A string Sets program memory model
/Dname [=text] Defines preprocessor macro
/Fx Selects listing options for source, object, assembly,
and testing
/FPstring Selects floating-point options
/Gx Specifies options for code generation; generates
8086/8088, 80186/80188, or 80286 instructions;
disables stack checking
/I path Adds to #include search path
/Ox Controls optimization for speed and size
/Wn Sets warning message level
/Zx Selects language options for disabling extensions,
emitting debugging information, and structure packing
═══════════════════════════════════════════════════════════════════════════
LIBRARY FUNCTIONS
───────────────────────────────────────────────────────────────────────────
The Microsoft C Compiler contains an extensive set of UNIX System V
compatible library routines. This set includes many of the functions
included in the emerging ANSI standard. Functions new in this version
are designated by an exclamation point (!) before the function name.
Functions implemented as Macros are designated by a double exclamation
point (‼).
Buffer Manipulation
───────────────────────────────────────────────────────────────────────────
memccpy memcmp !memicmp movedata
memchr memcpy memset
Character Classification and Conversion
───────────────────────────────────────────────────────────────────────────
‼isalnum ‼isgraph ‼isupper ‼toupper
‼isalpha ‼islower ‼isxdigit ‼_toupper
‼isascii ‼isprint ‼toascii
‼iscntrl ‼ispunct ‼tolower
‼isdigit ‼isspace ‼_tolower
Data Conversion
───────────────────────────────────────────────────────────────────────────
atof ecvt itoa !strtol
atoi fcvt ltoa ultoa
atol gcvt !strtod
Directory Control
───────────────────────────────────────────────────────────────────────────
chdir getcwd mkdir rmdir
File Handling
───────────────────────────────────────────────────────────────────────────
access fstat remove unmask
chmod isatty rename unlink
chsize locking setmode
filelength mktemp stat
I/O Stream Routines
───────────────────────────────────────────────────────────────────────────
clearerr fopen ‼getchar !setvbuf
fclose fprintf gets sprintf
fcloseall fputc getw sscanf
fdopen fputchar printf !tempnam
‼feof fputs ‼putc !tmpfile
‼ferror fread ‼putchar !tmpnam
fflush freopen puts ungetc
fgetc fscanf putw !vfprintf
fgetchar fseek rewind !vprintf
fgets ftell !rmtmp !vsprintf
‼fileno fwrite scanf
flushall ‼getc setbuf
Low-Level Routines
───────────────────────────────────────────────────────────────────────────
close dup2 open tell
creat eof read write
dup lseek sopen
Console and Port I/O Routines
───────────────────────────────────────────────────────────────────────────
cgets cscanf inp putch
cprintf getch kbhit ungetch
cputs getche outp
Math (Floating Point)
───────────────────────────────────────────────────────────────────────────
acos !_control87 frexp sin
asin cos hypot sinh
atan cosh ldexp sqrt
atan2 exp log !_status87
bessel fabs log10 tan
cabs floor matherr tanh
ceil fmod modf
!_clear87 !_fpreset pow
Memory Allocation
───────────────────────────────────────────────────────────────────────────
!alloca !_fmsize malloc !_nmsize
calloc free !_memavl realloc
!_expand !_freect !_msize sbrk
!_ffree !halloc !_nfree !stackavail
!_fmalloc !hfree !_nmalloc
MS-DOS Interface
───────────────────────────────────────────────────────────────────────────
bdos int86 intdos segread
dosexterr int86x intdosx
Process Control
───────────────────────────────────────────────────────────────────────────
abort execve !onexit spawnv
execl execvp signal spawnve
execle !execvpe spawnl spawnvp
execlp exit spawnle !spawnvpe
!execlpe _exit spawnlp system
execv getpid !spawnlpe
Searching and Sorting
───────────────────────────────────────────────────────────────────────────
bsearch !lfind !lsearch qsort
String Manipulation
───────────────────────────────────────────────────────────────────────────
strcat strdup strncmp strrev
strchr !strerror strncpy strset
strcmp !stricmp !strnicmp strspn
strcmpi strlen strnset !strstr
strcpy strlwr strpbrk strtok
strcspn strncat strrchr strupr
Time
───────────────────────────────────────────────────────────────────────────
asctime ftime localtime tzset
ctime gmtime time utime
!difftime
Miscellaneous
───────────────────────────────────────────────────────────────────────────
‼abs getenv perror setjmp
‼assert labs putenv srand
‼FP_OFF longjmp rand swab
‼FP_SEG
───────────────────────────────────────────────────────────────────────────
Appendix B Other C Compilers
There are many C compilers for DOS in today's PC marketplace. If you
are in the process of choosing one, I recommend that you read all you can
before making a purchase. Price is not the only consideration. Look for
features that will make your job easier: a complete standard library of
functions and macros; DOS and BIOS interface functions; program development
tools; and product support.
If you are going to produce programs commercially, look for a compiler
that produces compact, fast-running programs. Ultimately, it is the user of
your programs who will benefit from your choice of a quality product. Using
a compiler that compiles faster than others but produces sloppy object code
is probably not in your best long-term interest if you intend to make a
living by writing programs. It may save you a little development time, but
it will penalize your program's users every time they run the
program.
Interactive tools such as C interpreters are now available and provide
a comfortable testing and experimenting environment that helps a programmer
to quickly examine a variety of problem-solving approaches. You may want to
use a C interpreter for early development work. The current crop of C
interpreters are, however, less appropriate for large programming projects,
particularly projects that involve more than one programmer. Analyze your
needs carefully before selecting a C compiler or interpreter.
Here are some magazine review articles that may help you decide which
C compiler is right for you:
"The State of C," William Hunt, PC Tech Journal, January 1986.
"Software Reviews (Department)--21 C Compilers," S. Leibson, J.
Reed, and J. Kyle, Computer Language, February 1985 (page 73).
If you already have a C compiler and plan to stick with it, you have a
different concern. The task at hand is to use the available compiler
features and support tools to produce functioning programs. I have used
several C compilers in addition to the Microsoft C Compiler to compile the
programs presented in this book. They are the C86 C Compiler (Computer
Innovations), The C Programming System (Mark Williams Company), and the
MS-DOS C Compiler (Lattice). Each has its own way of doing things, although
nearly all C compiler vendors are starting to converge on the proposed ANSI
standard for C.
Although it is possible to write source code with conditional
compilation directives to accommodate various compilers, I chose not to do
so in the source in Proficient C because that tends to obscure the meaning
of the code and makes the source files nearly unreadable. The following
material briefly describes each compiler system, the major observable
differences from Microsoft C, and what has to be done to accommodate those
differences. Others may exist. The ones mentioned are those relevant to the
programs and routines developed in Proficient C.
The compilers mentioned here deliver full compatibility with the de
facto specification of C presented in the book The C Programming Language,
by Brian Kernighan and Dennis Ritchie, commonly referred to as "K&R" by the
C programming community. Each compiler varies from the others in the level
of support for the proposed ANSI standard for C.
Computer Innovations--Optimizing C86, Version 2.3
One of the first C compiler entries into the PC marketplace was
Computer Innovations' C86, which was later replaced by Optimizing C86. The
compiler features good compatibility with the standard UNIX library (pre-
System V), support for many DOS features (all versions), object libraries
in MS-DOS object format, and is delivered with all library source (C and
assembler) in compressed form on disk.
The compiler is due for an update at the time this is being written to
bring it up to System V and DOS (version 3.00 and higher) compatibility.
Optimizing C86 differs fundamentally from Microsoft C in that it:
► Has no void function return specifier--use a typedef of an int to
get around this.
► Has no enum keyword--either avoid the use of enums or synthesize a
palatable substitute using a typedef of an int.
► Uses the sysint() function instead of intdos() and uses sysint21()
instead of int86(). The calling sequence of parameters is
different.
► Provides movblock() instead of movedata() for segmented address
memory moves. The parameter ordering is different.
► Has no means of determining the DOS version provided by the
compiler--use the ver() function described in Chapter 5.
► Has no structure assignment and no passing of entire structures as
parameters or function return values.
► Has no perror()--use fprintf() and a message of your own making.
Optimizing C86 supports small and large memory models in a way that is
consistent with the equivalent Microsoft models. Programs may also be
compiled in an 8080 .COM memory model, which is called "compact." It is not
the same as the Microsoft compact memory model. There are no equivalents to
the Microsoft medium and huge models. No data object can exceed 64 KB in
size.
Compiling programs under C86 involves the use of batch files. There
are currently no compiler driver programs and no MAKE-like facility to
control processing. C86 can be given path information for the default
location of header files.
Mark Williams--The C Programming System, Version 3.0.7
The Mark Williams C Programming System (MWC from now on) is a complete
C programming system adapted from the Mark Williams Coherent operating
system, a UNIX Version 7 work-alike system. MWC incorporates some of C's
newer features such as enum and void and up-to-date library functions.
The compiler can be told to produce object files in either the Mark
Williams or Microsoft object file formats. Libraries for both formats are
provided with the compiler. MWC produces both small and large model (32-bit
pointers) programs. It offers nothing comparable to the compact, medium,
and huge Microsoft models.
A public domain full-screen editor, MicroEMACS, is included in the
package along with a good selection of UNIX-style support tools (make, cc,
ld, pr, wc, and others).
A trimmed-down version of MWC called Let's C is also available from
Mark Williams. It compiles small-model programs only and is supported by
fewer utilities, but it is a full K&R C compiler that is a good entry
vehicle for programmers who are new to C.
The primary differences between MWC and Microsoft C are that
MWC:
► Has no structure assignment and no passing of entire structures as
parameters or function return values.
► Uses intcall() instead of intdos(). It has no equivalent to the
Microsoft int86(), but intcall() with an interrupt number of 21H
will do the job (a bit slower, however).
► Provides port access via inb() and outb() (equivalent to
Microsoft's inp() and outp() routines).
► Has no perror()--use fprintf() and a message of your own making.
The global variable errno produces valid values in connection with
math library functions only. It is not generally useful with other
standard I/O library routines.
Lattice--MS-DOS C Compiler, Version 3.00H
The Lattice C Compiler, particularly the library support package, has
undergone significant changes. The version 3.00 Lattice C compiler is very
compatible with Microsoft C, versions 3.00 and 4.00; it differs mostly in
minor details:
► The standard printer device is called stdprt instead of stdprn.
► The Microsoft _osmajor and _osminor are handled by the external
character variable _DOS, which may be accessed as a single
character or a two-character array to obtain the DOS major and
minor version numbers. You can use the ver() function described in
the Proficient C DOS library to get the operating system version
numbers.
The MS-DOS interface functions emulate virtually all of the PC
operating system functions provided by Microsoft C, so calls to kbhit(),
int86(), intdos(), and so on require no modifications to compile under
Lattice C.
The compiler is accompanied by a library that classifies each function
by its "environment" (i.e, origins and compatibility). The following
classes are defined by Lattice:
═══════════════════════════════════════════════════════════════════════════
CLASS DESCRIPTION
───────────────────────────────────────────────────────────────────────────
ANSI Conforming to the proposed ANSI standard
UNIX Conforming to AT&T's UNIX System V standard
XENIX Compatible with Microsoft XENIX
LATTICE Available on all systems for which Lattice provides a
C compiler
iAPX86 Available only on iAPX32 machines
MS-DOS Designed for use under generic MS-DOS and compatible
with PC-DOS (a superset of MS-DOS)
PC-DOS For use under PC-DOS but not guaranteed to work under
MS-DOS
───────────────────────────────────────────────────────────────────────────
Lattice offers four memory models: small, program, data, and large.
The models are comparable to the Microsoft small, medium, compact, and
large models, respectively. Objects are limited to 64 KB in size. There are
no equivalents to Microsoft's huge keyword and huge model.
Compiling programs with the latest Lattice C compiler is easier than
under previous versions. A compiler driver program, LC, does most of the
work of compiling by calling the passes in the correct order and bailing
out in the event of compile-time errors. The linking step is still manual
and is best handled by batch files tailored to the particular linking task
and the selected memory model.
Maintaining Programs
For owners of C compilers that do not provide a MAKE command, several
alternatives are available. The first is to use DOS batch files to control
compilation and linking steps. The DOS ERRORLEVEL (program exit code) can
be used to abort processing when errors occur (if the compiler programs use
the error-return facility and you are running DOS version 2.00 or higher).
The problem with batch files is that the burden of knowing which program
modules have to be remade is left to you.
Another alternative is to use a third-party MAKE utility. An excellent
MAKE program called PolyMake is available from Polytron. It is as UNIX-like
as any DOS MAKE I have seen and can be made to work with any C compiler via
rules placed in a control file. Because PolyMake is closer to the UNIX
model that I am familiar with, I prefer to use it rather than the MAKEs
provided with most C compilers (with the exception of the Mark Williams
MAKE program, which is excellent). Several other MAKE utilities for DOS are
available at reasonable cost. Note: The makefiles in Proficient C are
written for the Microsoft version of MAKE. They will probably need to be
rewritten to run successfully with other MAKE programs.
Appendix C Characters and Attributes
A character code is a numeric value that is used to represent a
character in computer memory and to control peripheral devices such as
terminals, the console display screen, and printers. We are interested in
two primary character-coding schemes: the ASCII character set, because it
is the standard to which most commercial terminal and computer equipment
designs adhere, and the IBM extended ASCII character set used by the IBM PC
and work-alike computers.
In dealing with terminals and PC console display devices, numeric
codes are also used to control the appearance of a displayed character
(video attribute), to prevent specified screen regions from being updated
(protect), and even to make certain areas of the screen invisible to the
user (nondisplay). IBM PCs do not directly support protected fields,
although our programs can simulate the effect. On a PC, a nondisplay field
is one that carries a special video attribute which sets the foreground
color to the same value as the background color.
This appendix is a detailed summary of the characters and attributes
available to programmers of the IBM PC family of computers.
ASCII Character Codes
The ASCII (American National Standard Code for Information
Interchange) character set is defined as a table of 7-bit codes that
represent a collection of control characters and printable characters. In a
7-bit environment all data bits are significant. In the 8-bit environment
typical of the IBM PC and similar equipment, the lower seven bits (0
through 6) are used to represent ASCII characters. The high bit (bit 7) is
used for IBM code extensions, which are discussed later in this appendix.
Figure C-1 shows the relationship between various character
codes and the data bytes that hold them.
Bit # 7 6 5 4 3 2 1 0
┌──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┐
│▒▒▒▒▒▒│ │ │ │ │ │ │ │ Character
│▒▒▒▒▒▒│ │ │ │ │ │ │ │ Byte
└──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┘
Weight 128 64 32 16 8 4 2 1
▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
│ │
Code Exten- 7-Bit ASCII Codes
sion Bit
╔═══════════════════════════════════════════════════════╗
║ Character Code Ranges: ║
║ ║
║ 0-31 Control Characters ▓ ║
║ 32-126 Graphic Characters ▓► ASCII ║
║ 127 Delete Character ▓ ║
║ 128-255 IBM Special Graphic Characters ║
╚═══════════════════════════════════════════════════════╝
FIGURE C-1 ▐ Character codes and bytes
ASCII Control Character Table
ASCII codes 0 through 31 and 127 decimal (shown in Figure C-2,
next page) are called control codes because they are used to start,
stop, or modify some action. Several of the control characters have
meanings that vary with the context of their use, but most can be
categorized as being one or more of the following types:
► Format Effector (FE)--controls the printed or displayed layout of
graphic information
► Communications Control (CC)--controls the operation of
communication devices and networks
► Information Separator (IS)_controls the logical separation of
information
It may seem a bit odd to have one control code (DEL = 127) isolated
from all the others. This is due to its earliest use with paper-tape
punches for which the DEL code means "erase or obliterate" an unwanted
character, which on a medium like paper tape could be done only by punching
out all seven of the holes (hence code 127 or 7F hex).
In its design of the PC, IBM has attached special meanings to most of
the ASCII control codes so that they have displayable graphic content when
placed directly into display memory. Thus, the format effector CR (code 13
decimal), in addition to its "carriage return" meaning when embedded in
data streams, can also be used to place a musical-note symbol on the
display screen. Each of the control codes except NUL (0) have an associated
displayable symbol on the IBM PC.
╓┌─────┌─────────────────────────┌──────────┌─────────┌──────────┌───────────╖
NAME DESCRIPTION DEC HEX IBM KEY
(TYPE) CODE CODE GRAPHIC
───────────────────────────────────────────────────────────────────────────
NUL Null 0 00 nothing
SOH Start of heading 1 01 ☺ ^A
STX Start of text 2 02 ☻ ^B
ETX End of text 3 03 ♥ ^C
EOT End of transmission 4 04 ♦ ^D
ENQ Enquiry 5 05 ♣ ^E
ACK Acknowledge 6 06 ♠ ^F
BEL Bell 7 07 • ^G
BS Backspace 8 08 ◘ ^H
HT Horizontal tab 9 09 ^I
LF☼1 Linefeed 10 0A ^J
NAME DESCRIPTION DEC HEX IBM KEY
(TYPE) CODE CODE GRAPHIC
LF☼1 Linefeed 10 0A ^J
VT Vertical tab 11 0B ^K
FF Formfeed 12 0C ^L
CR Carriage return 13 0D ^M
SO Shift out 14 0E ♫ ^N
SI Shift in 15 0F ☼ ^O
DLE Data link escape 16 10 ► ^P
DC1 Device control 1 17 11 ◄ ^Q
DC2 Device control 2 18 12 ↕ ^R
DC3 Device control 3 19 13 ‼ ^S
DC4 Device control 4 20 14 ¶ ^T
NAK Negative acknowledge 21 15 § ^U
SYN Synchronous idle 22 16 ▬ ^V
ETB End transmission block 23 17 ↨ ^W
CAN Cancel 24 18 ↑ ^X
EM End of medium 25 19 ↓ ^Y
SUB Substitute 26 1A → ^Z
ESC Escape 27 1B ← ^[
FS File separator 28 1C ∟ ^\
NAME DESCRIPTION DEC HEX IBM KEY
(TYPE) CODE CODE GRAPHIC
FS File separator 28 1C ∟ ^\
GS Group separator 29 1D ↔ ^]
RS Record separator 30 1E ^^
US Unit separator 31 1F ^_
DEL Delete 127 7F Del
FIGURE C-2 ▐ ASCII control character table
Printable Character Table
The ASCII codes in the range of 32 to 126 decimal are designated
"graphic" characters to show that they have a visible representation on a
display device. Only the meaning of code 32, space (SP), which prints or
displays as a blank location in a graphic sequence, is defined by the ASCII
standard. All of the remaining graphic characters have meanings that are,
in effect, enforced by consensus. We simply accept the fact that code 65
decimal represents the letter 'A', for example.
Nothing in the standard demands that the graphic symbols be shown as
Gothic or Roman or any other type style. That decision is left to the
terminal/computer maker and usually is determined by whatever type style
the producer of the video controller chip supplies. The following table
(Figure C-3) shows the accepted definitions of the graphic characters.
╓┌──────────┌────────┌───────────────────────────────────────────────────────╖
ASCII DEC HEX
──────────────────────
<space> 32 20
! 33 21
" 34 22
# 35 23
$ 36 24
% 37 25
& 38 26
' 39 27
( 40 28
) 41 29
* 42 2A
+ 43 2B
, 44 2C
ASCII DEC HEX
, 44 2C
- 45 2D
. 46 2E
/ 47 2F
0 48 30
1 49 31
2 50 32
3 51 33
4 52 34
5 53 35
6 54 36
7 55 37
8 56 38
9 57 39
: 58 3A
; 59 3B
< 60 3C
= 61 3D
> 62 3E
? 63 3F
ASCII DEC HEX
? 63 3F
@ 64 40
A 65 41
B 66 42
C 67 43
D 68 44
E 69 45
F 70 46
G 71 47
H 72 48
I 73 49
J 74 4A
K 75 4B
L 76 4C
M 77 4D
N 78 4E
O 79 4F
P 80 50
Q 81 51
R 82 52
ASCII DEC HEX
R 82 52
S 83 53
T 84 54
U 85 55
V 86 56
W 87 57
X 88 58
Y 89 59
Z 90 5A
[ 91 5B
\ 92 5C
] 93 5D
^ 94 5E
_ 95 5F
` 96 60
a 97 61
b 98 62
c 99 63
d 100 64
e 101 65
ASCII DEC HEX
e 101 65
f 102 66
g 103 67
h 104 68
i 105 69
j 106 6A
k 107 6B
l 108 6C
m 109 6D
n 110 6E
o 111 6F
p 112 70
q 113 71
r 114 72
s 115 73
t 116 74
u 117 75
v 118 76
w 119 77
x 120 78
ASCII DEC HEX
x 120 78
y 121 79
z 122 7A
{ 123 7B
| 124 7C
} 125 7D
~ 126 7E
FIGURE C-3 ▐ The ASCII standard character set
IBM Extended ASCII Codes
Because the ASCII character set is based on a 7-bit code, the high bit
of each byte used by the IBM PC is available for other uses. When bit 7
(the high bit) is a logical 1, the character codes range from 128 to 255
decimal (Figure C-4). IBM uses these extra 128 codes for special characters
(international symbols, line-and-block drawing characters, special symbols
for mathematics, and so forth).
╓┌──────────┌─────────┌──────────────────────────────────────────────────────╖
IBM
GRAPHIC DEC HEX
───────────────────────
Ç 128 80
ü 129 81
é 130 82
â 131 83
ä 132 84
à 133 85
å 134 86
ç 135 87
ê 136 88
ë 137 89
è 138 8A
ï 139 8B
î 140 8C
ì 141 8D
Ä 142 8E
Å 143 8F
É 144 90
IBM
É 144 90
æ 145 91
Æ 146 92
ô 147 93
ö 148 94
ò 149 95
û 150 96
ù 151 97
ÿ 151 98
Ö 152 99
Ü 154 9A
¢ 155 9B
£ 156 9C
¥ 157 9D
₧ 158 9E
ƒ 159 9F
á 160 A0
í 161 A1
ó 162 A2
ú 163 A3
IBM
ú 163 A3
ñ 164 A4
Ñ 165 A5
ª 166 A6
º 167 A7
¿ 168 A8
⌐ 169 A9
¬ 170 AA
½ 171 AB
¼ 172 AC
¡ 173 AD
« 174 AE
» 175 AF
░ 176 B0
▒ 177 B1
▓ 178 B2
│ 179 B3
┤ 180 B4
╡ 181 B5
╢ 182 B6
IBM
╢ 182 B6
╖ 183 B7
╕ 184 B8
╣ 185 B9
║ 186 BA
╗ 187 BB
╝ 188 BC
╜ 189 BD
╛ 190 BE
┐ 191 BF
└ 192 C0
┴ 193 C1
┬ 194 C2
├ 195 C3
─ 196 C4
┼ 197 C5
╞ 198 C6
╟ 199 C7
╚ 200 C8
╔ 201 C9
IBM
╔ 201 C9
╩ 202 CA
╦ 203 CB
╠ 204 CC
═ 205 CD
╬ 206 CE
╧ 207 CF
╨ 208 D0
╤ 209 D1
╥ 210 D2
╙ 211 D3
╘ 212 D4
╒ 213 D5
╓ 214 D6
╫ 215 D7
╪ 216 D8
┘ 217 D9
┌ 218 DA
█ 219 DB
▄ 220 DC
IBM
▄ 220 DC
▌ 221 DD
▐ 222 DE
▀ 223 DF
α 224 E0
ß 225 E1
Γ 226 E2
π 227 E3
Σ 228 E4
σ 229 E5
µ 230 E6
τ 231 E7
Φ 232 E8
Θ 233 E9
Ω 234 EA
δ 235 EB
∞ 236 EC
φ 237 ED
ε 238 EE
∩ 239 EF
IBM
∩ 239 EF
≡ 240 F0
± 241 F1
≥ 242 F2
≤ 243 F3
⌠ 244 F4
⌡ 245 F5
÷ 246 F6
≈ 247 F7
° 248 F8
∙ 249 F9
· 250 FA
√ 251 FB
ⁿ 252 FC
² 253 FD
■ 254 FE
255 FF
FIGURE C-4 ▐ The IBM extended ASCII character set
Line-Drawing Characters_Quick Reference
Among the IBM extended ASCII characters are some line-drawing
characters that allow us to draw boxes and other shapes that can be formed
from various corners and straight-line segments. The codes are a bit
scattered, so to make them accessible, Figure C-5 visually groups the
codes for single- and double-line drawing characters. The origin of
these design aids can be traced to Rich Schinnell in an item published
in PC World (November 1983).
205 186 196 186
═══════ ║ ─────── ║
║ ║
201 203 187 214 210 183
╔═════ ══╦══ ═════╗ ╓───── ──╥── ─────╖
║ ║ ║ ║ ║ ║
204 206 185 199 215 182
║ ║ ║ ║ ║ ║
╠════ ═══╬═══ ════╣ ╟──── ───╫─── ────╢
║ ║ ║ ║ ║ ║
200 202 188 211 208 189
║ ║ ║ ║ ║ ║
╚═════ ══╩══ ═════╝ ╙───── ──╨── ─────╜
196 179 205 179
─────── │ ═══════ │
│ │
218 194 191 213 209 184
┌───── ──┬── ─────┐ ╒═════ ══╤══ ═════╕
│ │ │ │ │ │
195 197 180 198 216 181
│ │ │ │ │ │
├──── ───┼─── ────┤ ╞════ ═══╪═══ ════╡
│ │ │ │ │ │
192 193 217 212 207 190
│ │ │ │ │ │
└───── ──┴── ─────┘ ╘═════ ══╧══ ═════╛
FIGURE C-5 ▐ Line-drawing character sets
Block Characters--Quick Reference
The treasure trove of special characters in the IBM extended ASCII
character set also contains eight block characters that can be used to good
effect in screen displays. They are detailed in Figure C-6.
╔══════════════════════════════════════════╗
║ ║
║ Figure C-6 is found on page 445 ║
║ in the printed version of the book. ║
║ ║
╚══════════════════════════════════════════╝
FIGURE C-6 ▐ Block character sets
Video Attributes
Both the monochrome and color display systems used with PCs give us a
considerable degree of control over the appearance of characters on the
screen. Each character byte in memory is accompanied by an attribute byte
that specifies the visual characteristics of each displayed
character.
Figure C-7 shows how the attribute byte is
interpreted.
Bit # 7 6 5 4 3 2 1 0
┌──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┐
│ BL │ R │ G │ B │ I │ R │ G │ B │ Attribute
└──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┘ Byte
░ 128 64 32 16 8 4 2 1
Weight ◄░
░ 8 4 2 1 8 4 2 1
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
│ │
Background Foreground
╔═══════════════════════════════════════════════════════╗
║ Bit designations: ║
║ ║
║ BL Foreground blink or background intensity ║
║ I Foreground intensity ║
║ R Red ║
║ G Green ║
║ B Blue ║
╚═══════════════════════════════════════════════════════╝
FIGURE C-7 ▐ Video attribute byte interpretation
The following table summarizes the text mode attributes for both
monochrome and color/graphics systems. The codes between 0 and 7 set the
foreground (low 4 bits) or background (high 4 bits) attribute. Thus,
storing the code 0x02 in an attribute byte produces a green foreground and
a black background.
The high bit in each 4-bit (nibble) attribute component determines the
intensity. In the foreground component, setting bit 3 causes the foreground
to be high intensity. Setting bit 7 of an attribute byte controls either
foreground blinking or background intensity. On a CGA, port 3D8, bit 5
enables foreground blinking when set and disables blinking when cleared.
The power-on default is blink (bit 5=1). The equivalent MDA port is 3B8.
We can combine attribute values using bit shifting and ORing to
produce various compound attributes. The combined code
(0x01 << 4) | 0x07 | 0x08
yields a bright white character on a blue background.
═══════════════════════════════════════════════════════════════════════════
PRIMARY ATTRIBUTES
───────────────────────────────────────────────────────────────────────────
Dec Hex Binary Attribute Description
Code Code Code CGA MDA FG MDA BG
───────────────────────────────────────────────────────────────────────────
0 00 0000 Black Black Black
1 01 0001 Blue White Underline White
2 02 0010 Green White White
3 03 0011 Cyan White White
4 04 0100 Red White White
5 05 0101 Magenta White White
6 06 0110 Brown White White
7 07 0111 White White White
───────────────────────────────────────────────────────────────────────────
═══════════════════════════════════════════════════════════════════════════
INTENSITY MODIFIERS
───────────────────────────────────────────────────────────────────────────
Dec Hex Binary Description
Code Code Code
───────────────────────────────────────────────────────────────────────────
8 08 1000 High Intensity
128 80 10000000 Blinking Foreground (or High Intensity
Background). [This is just (0x08 << 4) and
is referred to as the "blink" bit or
background intensity bit depending upon
whether blinking is enabled (default) or
disabled.]
───────────────────────────────────────────────────────────────────────────
Appendix D Local Library Summary
Proficient C is largely about stockpiling routines to augment those
provided in the standard library. In the course of 14 chapters, we have
developed numerous functions and macros for a wide range of purposes. To
make the routines accessible to programmers, a brief manual page for each
routine is presented here.
The routines are grouped by library category and organized
alphabetically within categories. The libraries we developed are the
ansi.sys interface (ansi.lib), the operating system interfaces (bios.lib
and dos.lib), the buffered screen interface (sbuf.lib), and the utility
routines (util.lib).
═══════════════════════════════════════════════════════════════════════════
ANSI LIBRARY
───────────────────────────────────────────────────────────────────────────
The ANSI device driver is accessed primarily through the macros defined in
the ansi.h header file that resides in \include\local. Two additional
routines are implemented as functions in \lib\local\ansi.lib. The ANSI
device driver and this interface are described in Chapter 13.
───────────────────────────────────────────────────────────────────────────
Function
ansi_cpr--cursor position report
Synopsis
#include <local\ansi.h>
void ansi_cpr(row, col)
int *row; /* pointer to row value */
int *col; /* pointer to column value */
Description
Request a cursor position report from the ANSI device driver. The
driver must have been previously installed. The cursor position values
are saved in the row and col variables whose addresses are passed as
parameters.
Notes
This function will fail (hang the system waiting for keyboard input)
if the ANSI device driver is not properly installed.
───────────────────────────────────────────────────────────────────────────
Macro
ANSI_CUP--position the cursor
ANSI_HVP--horizontal and vertical position
Synopsis
#include <local\ansi.h>
ANSI_CUP(r, c)
ANSI_HVP(r, c)
short r, c; /* row and column */
Description
These two macros achieve the same result. They attempt to move the
cursor to the specified screen position. Illegal requests are ignored.
───────────────────────────────────────────────────────────────────────────
Macro
ANSI_CUB--move cursor back (left)
ANSI_CUD--move cursor down
ANSI_CUF--move cursor forward (right)
ANSI_CUU--move cursor up
Synopsis
#include <local\ansi.h>
ANSI_CUB(n)
ANSI_CUD(n)
ANSI_CUF(n)
ANSI_CUU(n)
short n; /* number of repetitions (columns or rows) */
Description
These four macros attempt to move the cursor in the specified
direction. If n is larger than the number of rows or columns remaining
in the direction of travel, the cursor is moved to the extreme
position.
───────────────────────────────────────────────────────────────────────────
Macro
ANSI_DSR--request a device status report
ANSI_RCP--restore screen position
ANSI_SCP--save screen position
Synopsis
#include <local\ansi.h>
ANSI_DSR
ANSI_RCP
ANSI_SCP
Description
A call to ANSI_DSR causes the ANSI driver to deposit a string
containing the cursor row and column data into the keyboard buffer
where it can be read (see the ansi_cpr() function). ANSI_SCP and
ANSI_RCP are cooperating macros that communicate through private
storage to save and restore the cursor position.
───────────────────────────────────────────────────────────────────────────
Macro
ANSI_ED--erase in display (clear screen)
ANSI_EL--erase in line (clear to end of line)
Synopsis
#include <local\ansi.h>
ANSI_ED
ANSI_EL
Description
ANSI_ED sets all character locations in the display to blanks,
effectively clearing the screen. Similarly, ANSI_EL sets all
character positions from the cursor to the end of the line to blanks.
Notes
Most ANSI drivers use the prevailing video attribute for cleared
character positions, but at least the MS-DOS version of the ANSI
driver provided for the AT&T 6300 uses the "normal" (white on black)
attribute.
───────────────────────────────────────────────────────────────────────────
Macro
ANSI_RM--reset mode
ANSI_SM--set mode
Synopsis
#include <local\ansi.h>
ANSI_RM(m)
ANSI_SM(m)
short m;
Description
These two macros are used to set and reset (clear) video modes. In
addition to the usual video modes that can be set from the DOS command
line using the MODE command, ANSI_SM and ANSI_RM permit programs to
control "wrapping" at the end of display lines.
───────────────────────────────────────────────────────────────────────────
Macro
ANSI_SGR--set graphic rendition (video attribute)
Synopsis
#include <local\ansi.h>
ANSI_SGR(a)
unsigned char a; /* video attribute */
Description
ANSI_SGR can be used to set any of the video attributes allowed for
text modes.
───────────────────────────────────────────────────────────────────────────
Function
ansi_tst--check for the presence of ANSI.SYS
Synopsis
#include <local\ansi.h>
void ansi_tst()
Description
Uses cursor positioning and reading to determine whether the ANSI
device driver is installed. If not, the function displays an error
message and returns control to DOS. If the ANSI driver is installed,
control is returned to the calling program.
Notes
This function uses the BIOS readca() function to find out where the
cursor is located because ansi_cpr() cannot be called unless the ANSI
driver is known to be installed. Thus, ansi_tst() may fail on PCs that
are not completely BIOS-compatible with the IBM PC family.
───────────────────────────────────────────────────────────────────────────
Function
colornum--convert a color string to a number
Synopsis
#include <local\ibmcolor.h>
int colornum(name)
char *name; /* color string */
Description
Using a built-in lookup table, colornum() converts a color name in
string form, "red" for example, to its IBM numeric equivalent (4 in
this case).
Return
A number that specifies the IBM color value for the specified color
name.
───────────────────────────────────────────────────────────────────────────
Function
setattr--set video attribute
Synopsis
#include <local\ansi.h>
#include <local\ibmcolor.h>
void setattr(pos, attr)
POSITION pos;
int attr;
Description
Using information provided by the caller about the attribute position
(pos), setattr() sets the foreground, background, or border attribute
to the specified value.
───────────────────────────────────────────────────────────────────────────
Function
userattr--set a user-specified attribute
Synopsis
#include <local\ansi.h>
int userattr(foreground, background, border)
char *foreground, *background, *border;
Description
A program can call userattr() just before exiting to set the video
attributes to those specified by the user in the DOS environment
(using FGND, BKGND, and BORDER DOS variables). The calling function
must specify attribute values to be used in the event that none are
specified in the environment.
Return
An indication of SUCCESS or FAILURE.
═══════════════════════════════════════════════════════════════════════════
BIOS LIBRARY
───────────────────────────────────────────────────────────────────────────
The BIOS library is composed of selected routines supported by the PC's ROM
BIOS. Primary coverage is given to BIOS video and equipment-determination
functions. In addition, keyboard status and some composite video functions
are included in the library. Most other needed services can be obtained via
routines in the standard libraries provided with nearly all C compilers for
DOS.
Most of the BIOS routines are designed to return the value of the carry
flag, which is a nonzero value if an error occurs duing the interrupt
operation. A few use the return value to pass back the information
requested by the calling function.
───────────────────────────────────────────────────────────────────────────
Function
clrscrn--clear the entire screen to spaces
clrw--clear a "window" region to spaces
Synopsis
int clrscrn(a)
int clrw(t, l, b, r, a)
unsigned char a; /* video attribute */
int t, l; /* upper left corner */
int b, r; /* lower right corner */
Description
The two functions, clrscrn() and clrw(), set all characters to spaces
in the entire display memory or a specified region, respectively. The
current "visual" page is cleared.
Return
The value of the carry flag.
───────────────────────────────────────────────────────────────────────────
Function
delay--provide a machine-independent time delay
Synopsis
void delay(d)
float d; /* delay duration */
Description
The delay() function loops to waste the specified number of seconds
and fractional seconds. The delay has a practical resolution of about
.06 seconds.
───────────────────────────────────────────────────────────────────────────
Function
drawbox--draw a single-line text graphics box shape
Synopsis
int drawbox(top, lft, btm, rgt, pg)
int top, lft; /* upper left corner */
int btm, rgt; /* lower right corner */
int pg; /* screen page number */
Description
The drawbox() function draws a fine-rule box (using the IBM single-
line drawing characters) around the perimeter of the region specified
by the corner parameters. The pg parameter specifies the screen page
(active) to draw on. On a standard monochrome display adapter, this
value must be 0.
Notes
This function is usually not appropriate for use in graphics modes
because the special drawing characters are not defined unless you
create your own table of "extended" character definitions.
───────────────────────────────────────────────────────────────────────────
Function
ega_info--gather EGA-related information
Synopsis
#include <local\video.h>
int ega_info(memsize, mode, features, switches)
int *memsize; /* memory size index (0-3) */
int *mode; /* color (0)/mono (1) mode */
unsigned int *features; /* feature bit settings */
unsigned int *switches; /* EGA switch settings */
Description
This function uses an EGA BIOS routine to gather and save EGA-related
information including whether an EGA adapter is installed in the
system. Memory size is reported by an index (from 0 = 64 KB up to
3 = 256 KB in 64-KB increments) and mode is indicated only as color
(0) or monochrome. Use the getstate() function to find out what the
video mode number is.
Return
A logical 1 if the reported memory size is valid or 0 otherwise. This
is considered by IBM to be an adequate indicator of the presence or
absence of an EGA adapter.
───────────────────────────────────────────────────────────────────────────
Function
equipchk--get equipment list
Synopsis
#include <local\equip.h>
int equipchk()
Description
The equipchk() function queries the system data areas and fills in the
global Eq structure to indicate what equipment is installed. The data
structure holds the number of logical disk drives, parallel printer
ports, and serial ports. It also indicates whether the system has a
game port and shows how much of the installed memory is on the system
board. In addition, the current video mode is indicated by the BIOS
video mode number (see getstate() for details).
Return
The value of the carry flag.
───────────────────────────────────────────────────────────────────────────
Function
getctype--get the current cursor type
Synopsis
#include <local\bioslib.h>
int getctype(start_scan, end_scan, pg)
int *start_scan; /* starting cursor scan line */
int *end_scan; /* ending cursor scan line */
int pg; /* "visual" page */
Description
The getctype() function retrieves the values of the starting and
ending cursor scan rows.
Return
The value of the carry flag.
───────────────────────────────────────────────────────────────────────────
Function
getstate--get the current video state
Synopsis
#include <local\video.h>
int getstate()
Description
This function queries the system data areas in memory and updates the
global video state variables.
Return
The value of the carry flag.
───────────────────────────────────────────────────────────────────────────
Function
getticks--get the current timer "tick" count
Synopsis
long getticks()
Description
This function queries the BIOS data area via the time-of-day service
to get a value that is the number of timer ticks since midnight. If
the rollover flag is set, a full day's worth of ticks is added to the
count.
Return
The current timer "tick" count.
───────────────────────────────────────────────────────────────────────────
Function
kbd_stat--get keyboard status information
Synopsis
#include <local\keybdlib.h>
unsigned char kbd_stat()
Description
The kbd_stat() function obtains keyboard status information and makes
it available to the calling function. Each bit in the status byte is
significant and is defined in the keybdlib.h file. This function lets
the program determine the current state of the shift, control (Ctrl),
alternate shift (Alt), and lock (Num, Caps, Scroll) keys.
Return
The value of the AL register, which contains the keyboard status
information (bit-significant).
───────────────────────────────────────────────────────────────────────────
Function
memsize--get memory size in kilobytes
Synopsis
int memsize()
Description
The memsize() function reads the system data area to determine the
total amount of memory installed in the system.
Return
The total memory size (system board plus I/O channel) in kilobytes.
───────────────────────────────────────────────────────────────────────────
Function
palette--select graphics palette or text border color
Synopsis
int palette(id, color)
unsigned int id; /* palette identifier */
unsigned int color; /* color number */
Description
The palette() function can be used in either graphics or text
(alphanumeric) modes. It selects the palette of foreground drawing
colors in graphics modes. In text modes, it selects the border color.
Return
The value of the carry flag.
───────────────────────────────────────────────────────────────────────────
Function
putcur--move the cursor to a specified row and column
Synopsis
int putcur(r, c, pg)
int r, c; /* row and column */
int pg; /* "active" screen page */
Description
This function places the cursor at the specified row and column of the
active page given by pg. Illegal requests are silently ignored by the
BIOS routine.
Return
The value of the carry flag.
───────────────────────────────────────────────────────────────────────────
Function
putfld--display a string in a field
Synopsis
int putfld(s, w, pg)
register char *s; /* string to write */
int w; /* field width */
int pg; /* screen page for writes */
Description
The putfld() function calls other BIOS functions to display a string
within a field starting on page pg at the current cursor position and
extending for w columns. Runs of a single given character, such as a
series of spaces, are compressed to a single call on writec() to
reduce function-call overhead. The field is padded with spaces if the
string does not completely fill it. The string is truncated if it is
too long to fit in the field. The cursor location is updated to the
last position written.
Return
A 0 if all goes well or a nonzero value to flag an error.
───────────────────────────────────────────────────────────────────────────
Function
putstr--display a string in the prevailing attribute
Synopsis
int putstr(s, pg)
register char *s; /* string to display */
int pg; /* "active" screen page */
Description
The putstr() function places the text of the string into display
memory and advances the cursor position.
Return
Number of characters written.
Notes
Results are undefined if the string is not confined to the current
screen row.
───────────────────────────────────────────────────────────────────────────
Function
put_ch--display a character
Synopsis
int put_ch(ch, pg)
register char ch; /* character to write */
int pg; /* "active" screen page */
Description
The put_ch() function writes a single character to the display and
advances the cursor position.
Return
Always 1 to indicate the number of characters written.
───────────────────────────────────────────────────────────────────────────
Function
readca--read the character and attribute in a cell
Synopsis
int readca(ch, attr, pg)
unsigned char *ch; /* character */
unsigned char *attr; /* video attribute */
int pg; /* "active" screen page */
Description
The readca() function gets the values of the character and video
attribute at the current cursor position on the specified active
screen page and fills in the ch and attr variables pointed to by the
parameters.
Return
The value of the carry flag.
───────────────────────────────────────────────────────────────────────────
Function
readcur--get the current cursor row and column
Synopsis
int readcur(row, col, pg)
int *row, *col; /* pointers to cursor values */
int pg; /* "active" screen page */
Description
The readcur() function reports the location of the cursor on the
specified screen page by storing the row and column values in the row
and col variables pointed to by the parameters.
Return
The value of the carry flag.
───────────────────────────────────────────────────────────────────────────
Function
readdot--read the value of a single pixel
Synopsis
int readdot(row, col, dcolor)
int row, col; /* cursor position */
int *dcolor; /* pointer to dot color */
Description
The readdot() function places the color number for the pixel at the
row and column position into the variable pointed to by the dcolor
parameter. The row and col parameters can be used to address up to
224,000 individual screen pixels in the high resolution mode of the
Enhanced Graphics Adapter.
Return
The value of the carry flag.
───────────────────────────────────────────────────────────────────────────
Function
scroll--scroll a specified screen region vertically
Synopsis
int scroll(t, l, b, r, n, a)
int t, l; /* upper left corner */
int b, r; /* lower right corner */
int n; /* number of rows to scroll */
unsigned char a; /* video attribute */
Description
The scroll() routine clears (initializes) the specified region if n is
0. It scrolls the display image up n lines if n is a positive nonzero
value and down if n is negative. Vacated lines are filled with blanks
(spaces) in the specified attribute.
Return
The value of the carry flag.
───────────────────────────────────────────────────────────────────────────
Function
setctype--set the cursor type
Synopsis
int setctype(start, end)
int start; /* starting cursor scan line */
int end; /* ending cursor scan line */
Description
The setctype() function sets the starting and ending scan lines that
form the cursor shape. If the starting scan line is greater than the
ending scan line, the cursor is split (formed of two parts) on
perfectly IBM-compatible machines. Setting the cursor starting scan
line to a large value turns the cursor off on many machines, but this
is not a reliable method on all hardware.
Return
The value of the carry flag.
───────────────────────────────────────────────────────────────────────────
Function
setpage--select the "visual" video page
Synopsis
int setpage(pg)
int pg; /* "visual" screen page */
Description
This function sets the "visual" screen page to pg. It checks the value
of pg against the valid page ranges for the current video mode.
Return
The value of the carry flag or -1 if an illegal page request is made.
───────────────────────────────────────────────────────────────────────────
Function
setvmode--set the video mode
Synopsis
int setvmode(vmode)
int vmode; /* video mode number */
Description
The setvmode() function attempts to set the specified video mode.
Illegal mode requests are not detected and are usually silently
ignored by the BIOS routine.
Return
The value of the carry flag.
Notes
This function cannot be used to switch from one display adapter to
another in systems equipped with more than one adapter. Instruct users
to run the DOS MODE command to set the mode before entering the
program.
───────────────────────────────────────────────────────────────────────────
Function
writea--write an attribute
writec--write a character
writeca--write a character in a specified attribute
Synopsis
int writea(a, count, pg)
int writec(ch, count, pg)
int writeca(ch, attr, count, pg)
unsigned char ch; /* character */
unsigned char attr; /* video attribute */
int count; /* number of repetitions */
int pg; /* "active" screen page */
Description
These functions write a (possibly) repeated attribute, character, or a
combination of the two to the specified screen page. The count
specifies the number of characters to write and must fit entirely
within the current screen row.
Return
The value of the carry flag.
───────────────────────────────────────────────────────────────────────────
Function
writedot--write a single pixel
Synopsis
int writedot(r, c, color)
int r, c; /* coordinates of the pixel */
int color; /* dot color */
Description
This function is for use in graphics modes only. It sets the color of
the specified pixel to color. Setting the color to the same color as
the background effectively erases the pixel.
Return
The value of the carry flag.
───────────────────────────────────────────────────────────────────────────
Function
writemsg--write a two-part message
Synopsis
int writemsg(r, c, w, s1, s2, pg)
int r, c; /* starting location */
int w; /* field width */
char *s1, *s2; /* components of the string */
int pg; /* "active" screen page */
Description
The writemsg() function places a two-part message (either or both
parts may be empty) on the specified screen page. If the combined
string is too long to fit in the field it is truncated. If it does not
fill the field exactly, the field is padded with spaces.
Return
The number of characters written.
───────────────────────────────────────────────────────────────────────────
Function
writetty--write a character using "teletype" style
Synopsis
int writetty(ch, attr, pg)
unsigned char ch; /* character */
unsigned char attr; /* video attribute */
int pg; /* "active" screen page */
Description
The writetty() function places a character into the specified screen
page and advances the cursor position. The screen is scrolled if
necessary to keep the current position within the viewable screen
boundaries. The function responds to the usual format effectors
(backspace, carriage return, and linefeed) correctly. All other codes,
including other control codes, are simply displayed.
Return
The value of the carry flag.
Notes
The attribute specification is honored only in text modes. It is
simply ignored in graphics modes.
═══════════════════════════════════════════════════════════════════════════
DOS LIBRARY
───────────────────────────────────────────────────────────────────────────
Only a very limited set of DOS routines are packaged in the local DOS
library because the standard library provides rather complete DOS access to
disk files, the console keyboard, DOS date and time, and so on.
───────────────────────────────────────────────────────────────────────────
Function
drvpath--convert a drive name to a full pathname
Synopsis
char *drvpath(path)
char path[]; /* path string buffer */
Description
A disk drive designation (d:) is a shorthand notation for the current
directory on disk drive d. The drvpath() function accepts a disk drive
designation as the initial portion of a character buffer and appends
the full pathname of the current directory on that drive to it.
Return
A pointer to a full pathname string starting with the drive letter.
Notes
The user-supplied buffer (path) must be large enough to hold the
longest DOS directory pathname (64 characters) plus a terminating NUL.
───────────────────────────────────────────────────────────────────────────
Function
first_fm--find the first matching filename
next_fm--find the next matching filename
Synopsis
int first_fm(path, fa)
int next_fm()
char *path; /* ambiguous pathname */
int fa; /* file attributes */
Description
Under DOS, we can specify filenames ambiguously using the * and ?
wildcard characters. The first_fm() function finds the first match in
directory order to an ambiguous specification. If the first match is
found, additional matches, if any, may be found by calling the
next_fm() function repeatedly until no match is found.
The file attributes specified by the fa parameter determine which
files that match the ambiguous name will be matched. If fa equals 0,
the functions attempt to match only ordinary files. If the volume-
label attribute is set, the functions search only for a volume label.
Setting fa to system, hidden, subdirectory, or any combination of
these attributes tells the functions to match files having those
attributes in addition to all ordinary files.
Return
Both functions return the value of the carry flag, which is 0 for
success or nonzero for failure.
Notes
These functions use a disk transfer area of type struct DTA, which is
defined in the header file ls.h. The DTA is established by a call to
setdta(). If any intervening functions alter the DTA, it must be reset
by calling setdta() before making further calls to next_fm.
───────────────────────────────────────────────────────────────────────────
Function
getdrive--return the ID of the default drive
Synopsis
int getdrive()
Description
The getdrive() function asks DOS for the ID of the current disk drive.
The ID is the number used internally by DOS where 0 means drive A, 1
means B, and so on.
Return
The internal drive number of the current disk drive.
───────────────────────────────────────────────────────────────────────────
Function
getkey--get a keystroke
Synopsis
#include <local\keydefs.h>
int getkey()
Description
Gets the next available key code from the keyboard buffer. If nothing
is ready, getkey() waits for the user to type something. If you do not
want a "blocking" read, use keyready() (or kbhit()) to determine
whether anything is ready to read before calling getkey().
Return
The code associated with the oldest item in the keyboard buffer. If
the returned value is 256 or more, the input was produced by a special
key and the returned value represents an extended key code (NUL + scan
code).
Notes
Because getkey() uses DOS function 7 (INT 21H), it is immune to Ctrl-
Break and it does not echo the typed input to the screen.
───────────────────────────────────────────────────────────────────────────
Function
keyready--check the keyboard buffer
Synopsis
int keyready()
Description
Checks the console for a waiting keystroke.
Return
A nonzero value if something is waiting in the keyboard buffer.
Notes
This function performs the same job as the kbhit() function in the
standard Microsoft library. It is presented for those whose C
compilers offer no equivalent function.
───────────────────────────────────────────────────────────────────────────
Function
setdta--set the disk transfer area (DTA)
Synopsis
void setdta(bp)
char *bp; /* buffer pointer */
Description
This function establishes a buffer in memory where disk transfers take
place. There may be multiple DTAs of varying sizes but only one may be
active at any time. If no DTA is established by a program, DOS sets up
a 128-byte default DTA in the program segment prefix (PSP) of the
running program.
───────────────────────────────────────────────────────────────────────────
Function
ver--get the DOS major and minor version numbers
Synopsis
int ver()
Description
Gets the DOS version major and minor numbers.
Return
The ver() function returns the DOS major version number in the low
byte (bits 0-7) and the minor version number in the high byte (bits
8-15).
Notes
These values duplicate the values in the global _osmajor and _osminor
variables supported by the Microsoft C Compiler. They are here for use
with compilers that support the bdos() function but not the global
version variables.
═══════════════════════════════════════════════════════════════════════════
SCREEN-BUFFER LIBRARY
───────────────────────────────────────────────────────────────────────────
The screen-buffer library routines are described in Chapter 12. The
routines are used to form a display image in an off-screen buffer and to
copy the image in the buffer to display memory.
───────────────────────────────────────────────────────────────────────────
Function
sb_box--draw a box around a window
Synopsis
#include <local\sbuf.h>
#include <local\box.h>
int sb_box(win, type, attr)
struct REGION *win; /* target window */
short type; /* line drawing type */
unsigned char attr; /* video attribute */
Description
Draws a box shape around a rectangular region (defined by win) in the
screen buffer. Types are summarized in the following table:
═══════════════════════════════════════════════════════════════════════════
Type Description
───────────────────────────────────────────────────────────────────────────
0 Default type: + for corners, | for vertical edges,
and - for horizontal edges
1 Single lines all around
2 Double lines all around
3 Single horizontal and double vertical lines
4 Double horizontal and single vertical lines
5 Block shapes for all lines and corners
───────────────────────────────────────────────────────────────────────────
Return
SB_OK
───────────────────────────────────────────────────────────────────────────
Function
sb_fill--fill the scrolling region of a window
sb_filla--fill with an attribute only
sb_fillc--fill with a character only
Synopsis
#include <local\sbuf.h>
extern struct BUFFER Sbuf;
extern union CELL Scrnbuf[SB_ROWS][SB_COLS];
int sb_fill(win, ch, attr)
int sb_filla()
int sb_fillc()
struct REGION *win; /* target window */
unsigned char ch; /* character */
unsigned char attr; /* video attribute */
Description
These three functions fill the scrolling region of a window (the
entire window if not set explicitly). The sb_fill() function takes
both a character and an attribute as parameters. The sb_filla()
function sets all cells to a common video attribute while leaving the
character in each cell undisturbed. And sb_fillc() sets all cells to a
common character value while leaving the attribute in each cell
undisturbed.
Return
SB_OK
───────────────────────────────────────────────────────────────────────────
Function
sb_init--initialize the buffered-screen interface
Synopsis
int sb_init()
Description
Sets up the Sbuf buffer control structure and initializes the flag
bits and line variables. The SB_DIRECT bit is set to a logical 1 if
the DOS variable UPDATEMODE is set to DIRECT.
Return
SB_OK if the interface can be initialized and SB_ERR if not (due to a
bad update mode specification).
───────────────────────────────────────────────────────────────────────────
Function
sb_move--locate the cursor within a window
Synopsis
#include <local\sbuf.h>
extern struct BUFFER Sbuf;
extern union CELL Scrnbuf[SB_ROWS][SB_COLS];
int sb_move(win, r, c)
struct REGION *win; /* target window */
register short r, c; /* row and column */
Description
The cursor is moved to the specified window-relative row and column
(r, c). The appropriate data structures are updated to reflect the
change.
Return
SB_OK if the cursor location is valid for the window or SB_ERR if
either r or c is an invalid value.
───────────────────────────────────────────────────────────────────────────
Function
sb_new--establish a window (screen-buffer region)
Synopsis
#include <local\sbuf.h>
struct REGION *sb_new(top, left, height, width)
int top; /* top row */
int left; /* left column */
int height; /* total rows */
int width; /* total columns */
Description
This function establishes a rectangular region of the screen buffer as
a window, defined by the upper left corner position and the height and
width values. The scrolling region is initially set to the window
values, but may be altered by a call to sb_set_scrl().
Return
A pointer to a newly allocated window structure or NULL if the window
could not be established.
───────────────────────────────────────────────────────────────────────────
Function
sb_putc--put a character into a window
sb_puts--put a string into a window
Synopsis
#include <local\sbuf.h>
extern struct BUFFER Sbuf;
extern union CELL Scrnbuf[SB_ROWS][SB_COLS];
int sb_putc(win, ch)
int sb_puts(win, s)
struct REGION *win; /* target window */
char ch;
unsigned char *s; /* text string */
Description
The sb_putc() function puts the specified character into the screen-
buffer array at the current window position and advances the window
"cursor." If the character is a newline, the remainder of the current
window row is cleared and the cursor advances to the first position of
the next row. If scrolling is enabled and is required to keep the
cursor inside the window, sb_scrl() is called and the cursor lands in
the first position of the last window row.
A call to sb_puts is treated as a call to sb_putc for each character
in the string. A tab is expanded to spaces using a series of calls to
sb_putc(). Expansion is limited to the current window row.
Return
SB_OK or SB_ERR if an error occurs.
───────────────────────────────────────────────────────────────────────────
Function
sb_ra--read attribute
sb_rc--read character
sb_rca--read character and attribute
Synopsis
#include <local\sbuf.h>
extern struct BUFFER Sbuf;
extern union CELL Scrnbuf[SB_ROWS][SB_COLS];
unsigned char sb_ra(win)
unsigned char sb_rc(win)
unsigned short sb_rca(win)
union REGION *win; /* target window */
Description
Each of these functions is passed a window pointer and window-relative
row and column values of a cell, and returns information about the
specified cell. The sb_ra() function obtains only the video attribute;
sb_rc() obtains only the character value. However, sb_rca() gathers
both the character and the video attribute of the specified cell.
Return
The sb_ra() and sb_rc() functions return a byte that contains the
attribute or character at the current window cursor position. A word
containing both the character and attribute is returned by sb_rca().
───────────────────────────────────────────────────────────────────────────
Function
sb_scrl--scroll a window region
sb_set_scrl--change the defined scrolling region
Synopsis
#include <local\sbuf.h>
extern struct BUFFER Sbuf;
extern union CELL Scrnbuf[SB_ROWS][SB_COLS];
int sb_scrl(win, n)
int sb_set_scrl(win, top, left, bottom, right)
union REGION *win; /* target window */
short top, left; /* upper left corner */
short bottom, right; /* lower right corner */
short n; /* number of rows to scroll */
Description
If n is greater than 0, sb_scrl() scrolls the specified window down by
n rows. A negative n causes upward scrolling. Vacated lines are filled
with spaces in the prevailing attribute. If n equals 0, the entire
scrolling region is set to spaces.
Scrolling occurs within a window's scrolling region. The sb_set_scrl()
function defines the scrolling region, which may be any size up to the
full window size.
Return
SB_OK. If an illegal scrolling region is specified, SB_ERR is returned
by sb_set_scrl().
───────────────────────────────────────────────────────────────────────────
Function
sb_show--copy the screen buffer to display memory
Synopsis
#include <local\sbuf.h>
extern struct BUFFER Sbuf;
extern union CELL Scrnbuf[SB_ROWS][SB_COLS];
int sb_show(pg)
short pg;
Description
The screen-buffer array is copied to display memory by sb_show(),
which uses either BIOS routines or direct display memory accesses,
depending on the value of the variable UPDATEMODE (BIOS or DIRECT)
in the DOS environment.
Return
SB_OK
───────────────────────────────────────────────────────────────────────────
Function
sb_wa--write an attribute
sb_wc--write a character
sb_wca--write a character and attribute
Synopsis
#include <local\sbuf.h>
extern struct BUFFER Sbuf;
extern union CELL Scrnbuf[SB_ROWS][SB_COLS];
int sb_wa(win, attr, n)
int sb_wc(win, ch, n)
int sb_wca(win, ch, attr, n)
union REGION *win; /* target window */
unsigned char ch; /* character */
unsigned char attr; /* video attribute */
short n; /* number of repetitions */
Description
These functions place characters and attributes in the screen-buffer
array. The window cursor position is not changed. The repetition
number specifies the number of buffer cells that are affected.
Return
SB_OK if the write is successful or SB_ERR if an error occurs.
Notes
These functions are valid only within the current row. Behavior is
undefined if writes are attempted outside the boundaries of the
specified window.
═══════════════════════════════════════════════════════════════════════════
UTILITY LIBRARY
───────────────────────────────────────────────────────────────────────────
The development of the utility library starts in Chapter 3 and continues
throughout the book. These routines are usable in a variety of programming
situations and are not easily classified in any of the foregoing
categories.
───────────────────────────────────────────────────────────────────────────
Function
beep--issue a standard terminal beep (Ctrl-G)
Synopsis
void beep()
Description
Call beep() to sound the PC's internal speaker.
───────────────────────────────────────────────────────────────────────────
Function
byte2hex--convert a byte value to hexadecimal
Synopsis
char *byte2hex(data, buf)
unsigned char data; /* 1-byte data item */
char *buf; /* hex string buffer */
Description
The byte2hex() function converts a 1-byte numeric value to a 2-byte
hexadecimal representation.
Return
A pointer to the resulting hexadecimal string.
Notes
The calling function must provide a buffer that is large enough to
receive the 2-byte string and the terminating null byte.
───────────────────────────────────────────────────────────────────────────
Function
clrprnt--clear printer to default settings
Synopsis
#include <local\printer.h>
int clrprnt(fout)
FILE *fout;
Description
The clrprnt() function sends codes needed to set an attached printer
back to its power-on default values for the major print modes. It does
so by individually clearing each mode rather than sending a printer
reset command to avoid "paper creep" and alteration of the top-of-form
setting.
Return
SUCCESS if all goes well or FAILURE if any attempt at writing to the
output stream fails.
───────────────────────────────────────────────────────────────────────────
Function
fatal--print an error message and exit
Synopsis
void fatal(pname, str, errlvl)
char *pname;
char *str;
int errlvl;
Description
If an error occurs in a program, fatal() can be called to display an
error message and then exit with a status (ERRORLEVEL in DOS terms)
other than 0 to indicate an error. The usual error code is 1, but
other values can be used if a program can fail in more ways than one.
───────────────────────────────────────────────────────────────────────────
Function
fcopy--copy input file to output file
Synopsis
int fcopy(fin, fout)
FILE *fin, *fout;
Description
This function copies an input stream (FILE *) to an output stream
(FILE *) using the standard library functions fgets() and fputs()
and a private buffer of BUFSIZ bytes. The ferror() macro is used to
check for input errors and testing is done for the unambiguous EOF
on the output stream.
Return
A successful file copy operation produces a 0 return value. Any input
or output error that is detected causes a nonzero return.
───────────────────────────────────────────────────────────────────────────
Function
fixtabs--set fixed-interval tab stops
Synopsis
void fixtabs(interval)
int interval; /* tabbing interval */
Description
A set of fixed tab stops can be set using fixtabs() to initialize the
private Tabstops array and then set tab stops at the specified
intervals. Internally, the Tabstops array has tab stops at column 0
and repeating at the specified interval. Standard terminal usage
has an interval of 8 columns.
───────────────────────────────────────────────────────────────────────────
Function
getname--extract filename[.ext] from a pathname
Synopsis
char *getname(path)
char *path;
Description
This function extracts a full filespec (filename[.ext]) from a
pathname. It checks to see that the pathname given as a parameter is a
valid pathname. Path separators of \ and / are accepted and may be
intermixed.
Return
A pointer to a valid filespec or NULL if the path parameter is not a
valid pathname.
───────────────────────────────────────────────────────────────────────────
Function
getopt--get option letters and option arguments
Synopsis
extern int optind;
extern int opterr;
extern char *optarg;
int getopt(argc, argv, opts)
int argc; /* argument count */
char *argv[]; /* argument vector array */
char *opts; /* option string */
Description
The string variable opts is a list of recognized option letters.
Option letters that are followed by a colon are expected to take an
argument. On each successive call to getopt(), the function returns
the next option letter in argv that matches a letter in opts. The
optarg variable points to an option argument, if any is expected and
found or NULL otherwise.
When all options have been processed, getopt() returns EOF. The
special option "--" (double dash) may be used to mark the end of
options. When found, this causes getopt() to skip the special option
and return EOF.
Return
If an option letter in argv is not found in the opts string, getopt()
returns a question mark and prints an error message. Setting opterr to
0 silences getopt().
Notes
This getopt() is the public domain version that is provided by the
AT&T UNIX System Toolchest. It is presented in this book with the
permission of AT&T in the interest of promoting a common command-line
interface for programs.
───────────────────────────────────────────────────────────────────────────
Function
getpname--extract a program name from a pathname
Synopsis
void getpname(path, pname)
char *path; /* full or relative pathname */
char *pname; /* filename part of program name */
Description
The getpname() function receives a full or relative pathname. It
strips off any leading path information and any trailing extension.
This function accepts both \ and / as path separators. It does not
check to see that the path parameter is a valid path.
───────────────────────────────────────────────────────────────────────────
Function
getxline--get an expanded line of text
Synopsis
char *getxline(buf, size, fin)
char *buf; /* output buffer */
int size; /* maximum number of bytes */
FILE *fin; /* input stream */
Description
The getxline() function receives a line of text as input and "expands"
the line by converting each tab to the correct number of spaces. The
total character count may thus be altered, but the expanded line
appears to be visually identical to the input line when displayed.
Return
A pointer to the buffer that contains the expanded line or NULL if an
error occurs.
Notes
This function queries the tabstop array, so you must first call either
fixtabs() or vartabs() to set tabs.
───────────────────────────────────────────────────────────────────────────
Function
fconfig--get pointer to a configuration file
Synopsis
FILE *fconfig(varname, fname)
char *varname; /* DOS environment variable name */
char *fname; /* file name */
Description
This function looks for a configuration file by the name fname. It
looks first in the current directory and then in a directory pointed
to by the varname DOS variable.
Return
A pointer to the configuration file if one is found or a NULL pointer
if not.
───────────────────────────────────────────────────────────────────────────
Function
last_ch--get the last character of a string
Synopsis
char last_ch(s)
char *s; /* source string */
Description
The last_ch() function finds the last character of the source string,
s (just before the NUL termination).
Return
The character value or -1 if the string is empty.
───────────────────────────────────────────────────────────────────────────
Function
lines--send blank lines to an output stream
Synopsis
int lines(n, fp)
int n; /* number of repetitions */
FILE *fp; /* output stream */
Description
The lines() function places n newline characters on the output stream.
Return
The number of newlines actually emitted, which may be less than the
number requested if an error occurs on the output stream.
───────────────────────────────────────────────────────────────────────────
Function
memchk--check for physical memory at an address
Synopsis
int memchk(seg, os)
unsigned int seg; /* segment */
unsigned int os; /* offset */
Description
By sequentially writing and reading the memory location specified by
the segmented address, memchk() attempts to determine whether any
physical memory is installed. To prevent clobbering needed memory
values, the original value is preserved and restored upon completion
of the test.
Return
If active memory is found at the specified address, memchk() returns a
1. If no active memory is found, 0 is returned.
───────────────────────────────────────────────────────────────────────────
Function
mkslist--create a selection list (lookup table)
Synopsis
extern long Highest;
int mkslist(list)
char *list; /* pointer to a list in string form */
Description
The mkslist() function extracts tokens from a string representation of
a list of numbers, converts the tokens to numeric values, and stores
the values as a set of numeric ranges in an array (up to 10 ranges
permitted by the current design).
Single numbers are represented as ranges having the same starting and
ending number. Open-ended ranges (for example, 3-) are represented as
the starting number to some unreasonably high ending value (BIGGEST).
The list of ranges is terminated by a -1 starting value in the last
array element following the last valid range.
Return
Always zero.
───────────────────────────────────────────────────────────────────────────
Function
nlerase--erase a newline in a string
Synopsis
char *nlerase(s)
char *s;
Description
The nlerase() function scans a string and replaces the first newline
it finds with a NUL (\0) character, effectively terminating the
string and erasing the newline.
Return
A pointer to the modified string.
───────────────────────────────────────────────────────────────────────────
Function
selected--determine whether a number is in a range
Synopsis
int selected(n)
long n; /* number to test */
Description
The selected() function queries the select-list array, Slist, which is
established by mkslist(), to determine whether the number parameter is
a member of one of the ranges in the list.
Return
Returns 1 if the number is in one of the ranges and 0 otherwise.
───────────────────────────────────────────────────────────────────────────
Function
setfont--select a printer font
Synopsis
#include <local\printer.h>
int setfont(ftype, fout)
int ftype; /* font specifier */
FILE *fout; /* output stream */
Description
This function is used to select a printer font type. The standard font
is a 10 cpi (characters per inch) type and it can be obtained by
list calling clrprnt(). The special types are chosen from the
following list (based on Epson MX/FX-series printer codes):
───────────────────────────────────────────────────────────────────────────
Symbolic Name Description
───────────────────────────────────────────────────────────────────────────
CONDENSED A small but very readable type at about 14 cpi (136
characters per line)
DOUBLE Double strike
EMPHASIZED Emboldened by microspacing and restriking
EXPANDED Wider than normal characters (6 cpi)
ITALICS True italic characters (not supported by all Epson-
compatible printers)
UNDERLINE Continuous underline (without the need to backspace and
restrike the character)
───────────────────────────────────────────────────────────────────────────
Return
The setfont() function returns SUCCESS to indicate apparent success,
or FAILURE if an error occurs or an illegal font combination is
requested.
Notes
The CONDENSED font cannot be used with either DOUBLE or EMPHASIZED on
most Epson-compatible printers, so such combinations are disallowed.
───────────────────────────────────────────────────────────────────────────
Function
setprnt--install printer codes
Synopsis
#include <local\printer.h>
int setprnt()
Description
This function installs printer codes from printer configuration files
or from internal defaults (for Epson MX/FX series and compatible
printers).
Return
The setprnt() function returns SUCCESS to indicate apparent success,
or FAILURE if an error occurs. The only detectable failures are too
many or too few lines in a configuration file.
───────────────────────────────────────────────────────────────────────────
Function
spaces--send spaces to an output stream
Synopsis
int spaces(n, fp)
int n; /* number of repetitions */
FILE *fp; /* output stream */
Description
The spaces() function places n space (blank) characters on the output
stream.
Return
The number of spaces actually emitted, which may be less than the
number requested if an error occurs on the output stream.
───────────────────────────────────────────────────────────────────────────
Function
tabstop--determine whether col is a tab stop
Synopsis
int tabstop(col)
register int col;
Description
The tabstop() function queries the private Tabstops array to determine
whether the specified column, col, is a tab stop.
Return
A nonzero value if col is a tab stop or 0 if it is not.
───────────────────────────────────────────────────────────────────────────
Function
vartabs--set variable tabs from a list
Synopsis
void vartabs(list)
int *list; /* pointer to an array of tab stops */
Description
A set of tab stops can be set from a list using vartabs() to
initialize the private Tabstops array and then set the specified tabs.
───────────────────────────────────────────────────────────────────────────
Function
word2hex--convert a word value to hexadecimal
Synopsis
char *word2hex(data, buf)
unsigned short data; /* 2-byte data item */
char *buf; /* hex string buffer */
Description
The word2hex() function converts a 2-byte numeric value to a 4-byte
hexadecimal representation.
Return
A pointer to the resulting hexadecimal string.
Notes
The calling function must provide a buffer that is large enough to
receive the 4-byte string and the terminating null byte.
───────────────────────────────────────────────────────────────────────────
Augie Hansen
Augie Hansen started programming on IBM mainframes more
than 20 years ago and since then has been involved with
computers and programming at various companies, including
General Dynamics, Raytheon Company, and E.G. & G., Inc. In
addition, Augie spent 7 years with AT&T Bell Laboratories,
specializing in UNIX and C programming. He founded and is
the president of Omniware, a company that provides academic
and commercial training courses on UNIX, C, and MS/PC-DOS
and custom programming consulting. Augie is a contributing
editor of PC Tech Journal and has written one other book,
vi--The UNIX Screen Editor, published in 1986 by Brady
Books/Prentice-Hall Press.
FIGURE 6-2 ▐ Manual page for CAT
───────────────────────────────────────────────────────────────────────────
NAME
CAT--concatenate file
FORMAT
cat [ -s ] [ d: [ path [ filename [ .ext] ] ] ... ]
DESCRIPTION
The CAT command accepts the -s option (silent), which tells the
program not to complain about missing files (this may be useful in
batch files). If no filenames are specified, CAT reads from the
standard input. DOS wildcard characters (? and *) may be used to
specify ambiguous filenames.
EXAMPLES
Display the contents of a single file.
cat hello.c
Concatenate all C source files in the current directory into a single
source file.
cat *.c >program.c
Create a new file with text typed by the user.
cat >myfile.txt
This is a test.
^Z
FIGURE 6-3 ▐ Manual page for TIMER
───────────────────────────────────────────────────────────────────────────
NAME
TIMER--provide timing data for up to four events
SYNOPSIS
timer [-efs#]
DESCRIPTION
TIMER uses command-line options to select program actions. The option
letters may be combined following a single option flag (-) or invoked
separately. If the f option is combined with other options, it must be
last. These options are honored by TIMER:
══════════════════════════════════════════════════════════════════════
Option Meaning
──────────────────────────────────────────────────────────────────────
-s Start (or restart) a timer
-e Display or record elapsed time
-f file Record the output of the TIMER command in file
-# The # symbol is a numeric parameter in the range of 0 to 3
that selects one of the four timers. If no number is
specified, TIMER uses 0 by default.
──────────────────────────────────────────────────────────────────────
EXAMPLES
Start the default timer:
timer -s
Restart timer 0 and record the output in the file project.dat:
timer -s -f project.dat
NOTES
TIMER uses the intra-application communication area to hold starting
times for each of the timers. If another program uses the same memory
locations, timer data could be corrupted. When asked to produce an
elapsed time, TIMER tries to detect corrupt data and outputs an error
message rather than a time value.
FIGURE 7-1 ▐ Manual page for MX
───────────────────────────────────────────────────────────────────────────
NAME
MX--control printer modes
SYNOPSIS
mx -option(s)
DESCRIPTION
The MX program sends mode-setting control strings to a printer under
control of options. Most of the modes may be set individually or in
combinations of two or more. It should be obvious that normal (-n)
should be used by itself. Less obvious is that condensed (-c) and
either bold (-b) or double-strike (-d) cannot be used together. Within
the limitations noted above, options listed below may be used singly
or combined in any order:
═══════════════════════════════════════════════════════════════════════════
Option Meaning
──────────────────────────────────────────────────────────────────────
-b Select bold (emphasized) mode
-c Select compressed mode
-d Select double-strike mode
-e Select expanded mode
-i Select italic font
-n Select normal font. This command option clears all special
font and print-mode selections.
-o file Use the specified output file or stream
-p Preview output on screen (may be redirected)
-r Issue a hardware reset command. This clears all attributes
and establishes the current position as the new top-of-form
reference.
-t Eject a page (top-of-form command) by issuing a formfeed
-u Select underline mode
______________________________________________________________________
The reset (-r) and top-of-form (-t) options have temporal priority
over any other options invoked in the same call to MX. Reset is done
first, then formfeed, then font selection. Printer configuration can
be controlled via optional local and global configuration files (local
overrides global).
EXAMPLES
Request compressed and italicized printing:
mx -ci
Eject a page:
mx -t
FIGURE 7-2 ▐ Manual page for PRTSTR
───────────────────────────────────────────────────────────────────────────
NAME
PRTSTR--print string(s) on standard printer
SYNOPSIS
prtstr [options ] [ string ... ]
DESCRIPTION
Use PRTSTR to send a string or group of strings given as arguments to
the standard printer device (stdprn). PRTSTR appends one space after
each argument and issues a newline to terminate the line. Use quotes
to protect embedded special characters, such as tabs or spaces, in the
text-argument list. The following options may be used separately or
together to alter the default behavior:
══════════════════════════════════════════════════════════════════════
Option Meaning
──────────────────────────────────────────────────────────────────────
-n Suppress newline and trailing space.
-p Preview output on screen (may be redirected).
──────────────────────────────────────────────────────────────────────
EXAMPLES
Send the line C is a fantastic language! to the standard printer
device (notice that no quotes are needed):
prtstr C is a fantastic language!
Send the string BASEBALL to a file:
prtstr -p BASEBALL > baseball.txt
Print some text and stop on the same line:
prtstr -n "This is a test: "
FIGURE 8-1 ▐ Manual page for TOUCH
───────────────────────────────────────────────────────────────────────────
NAME
TOUCH--update file modification time(s)
SYNOPSIS
touch [-cv] file ...
DESCRIPTION
The TOUCH command updates the file modification times associated with
a file or files. Under DOS, the last modification time (and last
access time for DOS version 3.00 and later) maintained in the
directory entry for a file are identical. TOUCH sets the file time to
the current DOS time. TOUCH accepts the following command-line
options, singly or in combination:
══════════════════════════════════════════════════════════════════════
Option Meaning
──────────────────────────────────────────────────────────────────────
-c Control file creation. TOUCH usually creates a named file
if it does exist. This option tells TOUCH not to create
files.
-v Verbose mode. Tells TOUCH to send information to the
standard error stream about file times that have been
successfully updated, about files that were created, and so
forth.
──────────────────────────────────────────────────────────────────────
EXAMPLES
Update the modification times of all C source files in the current
directory and tell the user what's going on:
touch -v *.c
Create a set of empty files in the current directory (assumes these
files do not exist yet):
touch file1.c file2.c file3.c
FIGURE 8-2 ▐ Manual page for TEE
───────────────────────────────────────────────────────────────────────────
NAME
TEE--split a stream into multiple streams
SYNOPSIS
tee [-a] [file ... ]
DESCRIPTION
TEE copies its standard input onto its standard output. If any files
are named, they too become destinations for the output of TEE. The
files are created if necessary. If they already exist, their prior
contents are lost unless the -a option (append) is given on the
command line.
EXAMPLES
Display the contents of a file (FILE1) and simultaneously copy it to
another file (FILE2):
type file1 tee file2
Use CAT to display the file timer.c on the console and append
a copy of the output to two existing files (SRCFILE1 and
SRCFILE2):
cat timer.c tee -a srcfile1 srcfile2
NOTES
TEE does not work with files that contain binary data. As designed, it
handles only ASCII text.
The number of destination files is limited by the operating system and
by the use of the value _NFILE in the stdio.h header file (_NFILE is
typically 20). There are five opened files (stdin, stdout, stderr,
stdaux, and stdprn) when a program starts executing, which reduces the
number of files you can open.
FIGURE 8-3 ▐ Manual page for PWD
───────────────────────────────────────────────────────────────────────────
NAME
PWD--print working directory
SYNOPSIS
pwd
DESCRIPTION
PWD displays the full pathname of the current ("working") directory.
It is designed to ease the transition between UNIX/XENIX and DOS. It
provides the same function as the DOScommand CD if CD is typed without
a pathname argument.
EXAMPLE
Display the current directory pathname:
pwd
NOTES
Under UNIX and XENIX, the CD command typed without a pathname argument
moves the user's context to the "home" directory, which might surprise
an experienced DOS user trying to get the current pathname by the
standard technique.
FIGURE 8-4 ▐ Manual page for RM
───────────────────────────────────────────────────────────────────────────
NAME
RM--remove file(s)
SYNOPSIS
rm [-i] file ...
DESCRIPTION
RM removes a file or a group of files. Each file specification may be
a full or relative pathname and may contain wildcards to name files
ambiguously.
The -i option causes RM to operate in an interactive mode. The program
prompts the user to confirm each removal before it is executed. A
response that starts with y or Y for "yes" confirms the removal. Any
other initial character is interpreted as a "no" response.
EXAMPLES
Remove all backup files in the current directory:
rm *.bak
Remove object files and "map" files in the current directory
interactively:
rm -i *.obj *.map
FIGURE 8-5 ▐ Manual page for LS
───────────────────────────────────────────────────────────────────────────
NAME
LS--list information about a directory
SYNOPSIS
ls [-aCFlrt] [filespec]
DESCRIPTION
The LS command is a general-purpose directory lister. It has enough
features to be useful without being threateningly complex. The most
common use of LS is to find out what files are in the current
directory. This requires no argument on the command line. To get
greater amounts of detail or to modify the output of LS in some way,
use one or more of the following options separately or grouped in any
meaningful combination.
══════════════════════════════════════════════════════════════════════
Option Meaning
──────────────────────────────────────────────────────────────────────
-a All files in the named directory are listed, even those
marked hidden or system. The default is to shield hidden
and system files from view.
-C Produce multi-column output with items sorted down each
column. Without this option, single-column output is
employed. This option causes output to be in the largest
number of columns possible (determined by the length of the
longest item name).
-F Show file type in the output listing. Directories are
marked by a trailing backslash (mydir\).
-l Produce a single-column "long" listing, including a summary
of mode bits, file date and time stamp, and file size in
bytes. This option overrides the -C and -F options.
-r Reverse the sorting order. Works for the default sorting of
file and directory names and for the sort on file
modification time data (see -t option).
-t Sort by file time rather than name. The default is oldest
first, but most-recent-first may be obtained by combining
this option with -r.
──────────────────────────────────────────────────────────────────────
EXAMPLES
Display a long list of the current directory:
ls -l
Display a multi-column listing of the root directory on the current
disk from anywhere in the directory hierarchy:
ls -C \
FIGURE 9-1 ▐ Manual page for PR
───────────────────────────────────────────────────────────────────────────
NAME
PR--print file(s)
SYNOPSIS
pr [ options ] [ file ... ]
DESCRIPTION
The PR program takes a list of filenames and sends the contents of
each in turn to the standard output. Formatting of the output "pages"
is controlled via built-in defaults and may be modified by external
configuration files and command-line options. If no filenames are
specified, PR reads from its standard input (usually the keyboard,
unless input is redirected).
Each page starts with a header composed of the filename, the file-
relative page number, and the file's modification date and time. When
reading from the standard input, the filename is omitted and the
current date and time are used.
Long lines are folded onto the next line rather than being truncated.
The line count is not affected by this. PR continues to track the
number of logical lines in the source file, not the number of physical
lines printed.
The options listed below may be used singly or combined in any
order.
══════════════════════════════════════════════════════════════════════
Option Action
──────────────────────────────────────────────────────────────────────
-e Toggle the Epson-compatible printer mode
-f Use a formfeed to eject a page (the default is spaces)
-h hdr Replace the filename part of the header with the text
given in hdr (must be quoted if it contains white space)
-l len Set the page length to len lines (the default is 66)
-n Enable the line-numbering feature (the initial setting
is off)
-o cols Set offset to cols columns from left edge of the paper
-p Preview output on screen (may be redirected)
-s list Output only those lines selected by the comma-separated
list (must be quoted if it contains any white space)
-w cols Set the output line width to cols columns
──────────────────────────────────────────────────────────────────────
Program configuration may be controlled via optional local and global
configuration files (local overrides global). Most settings
established by configuration files may be altered by the user via the
command-line arguments listed above. The following listing (comments
are optional--only the numbers and strings at the beginning of each
line are required) is the default global configuration file, which, if
it exists, must reside in a subdirectory pointed to by the CONFIG
environment variable:
2 top1
2 top2
3 bottom
132 width
5 left margin
3 right margin
66 lines per page
6 lines per inch
1 printer mode (Epson)
1 line number enabled
1 use formfeeds
8 standard tabs
PRN output destination
EXAMPLES
Print all of the C source files in the current directory, with
linenumbering turned on, to an Epson printer attached to the standard
printer port:
pr -en *.c
Display the contents of the \include\std.h header file
with an offset of 10 columns and a text file:
pr -o10 \include\std.h
Print the file myprog.pas on the default printer starting
at page 4 and continuing up to and including page 10:
pr -s4,10 myprog.pas
FIGURE 10-1 ▐ Manual page for DUMP
───────────────────────────────────────────────────────────────────────────
NAME
DUMP--dump file in hexadecimal and text formats
SYNOPSIS
dump [ -sv] [file ... ]
DESCRIPTION
The DUMP program is a stand-alone program that can be used as a
filter. It receives a stream of data and transforms it into a combined
hexadecimal and text output stream. Each line of output contains three
fields: an offset from the start of the incoming stream; a hexadecimal
display of a 16-byte block of data; and a text representation of the
same block of bytes.
The output of DUMP contains non-ASCII characters that may confuse
printers and other output devices. The -s option causes these to be
stripped out of the output stream and replaced by dots (.). When this
option is used, the output of DUMP may be safely sent to any other
program that can read from its standard input, and to any device that
can accept an ASCII text stream.
The -v option tells DUMP to be verbose. For each file given as a
command-line argument, DUMP outputs a blank line, then the name of the
file being dumped, and then moves to a new line before outputting the
formatted file dump. When the standard input is the data source, the
verbose option is ignored.
EXAMPLES
Display the converted contents of hex.obj on the
screen:
dump hex.obj
Print the converted contents of PROG.COM:
dump prog.com > prn
FIGURE 10-3 ▐ Manual page for SHOW
───────────────────────────────────────────────────────────────────────────
NAME
SHOW--make all characters in a stream visible
SYNOPSIS
show [-svw] [file ... ]
DESCRIPTION
The SHOW program is a stand-alone program that can be used as a
filter. It receives a stream of data and transforms it into a straight
ASCII output stream. Normal displayable characters pass through
unchanged, but most nonprintable characters are converted to a two-
digit hexadecimal form.
The -s option strips all control codes and IBM extended ASCII
characters from the text stream, except for the primary format
effectors (tab and newline). The -v option tells SHOW to be verbose.
When the -w (word-processing) option is used, a carriage return that
is not paired with a newline is assumed to be a "soft" return and is
replaced by a newline. The -w option also causes SHOW to convert all
extended codes (eighth bit on) to 7-bit ASCII and outputs those whose
converted values fall into the displayable ASCII range.
EXAMPLES
Display the contents of a spreadsheet data file:
show data.wks
Convert a word processor's formatted document file to straight ASCII
text and save output in a file:
show -w letter.doc >letter.txt
FIGURE 13-4 ▐ Manual page for SetColor
───────────────────────────────────────────────────────────────────────────
NAME
SC--control video attributes
SYNOPSIS
sc [ attribute ]
sc [ foreground [ background [ border ] ] ]
DESCRIPTION
The SetColor program may be used in either batch mode or interactive
mode to set the foreground, background, and border attributes of a
color display system. The interactive mode, invoked by typing SC with
no arguments, uses on-screen instructions to guide the user in
selecting attributes via function keys. The display is updated with
each keypress to reflect the current selections.
The attribute selections in batch mode can be formed from the
following list of argument values. Only the first three letters are
significant and case is not relevant.
══════════════════════════════════════════════════════════════════════
Values For Values For Foreground,
Attribute Background and Border
──────────────────────────────────────────────────────────────────────
Normal Black Red
Reverse Blue Magenta
Green Brown
Cyan White
──────────────────────────────────────────────────────────────────────
All color specifications can be modified by prefixing bold, bright, or
light to obtain high-intensity values of the base color. Yellow may be
used as a synonym for bold brown. If "blink" is enabled (the default),
selecting a high-intensity background will cause the background to be
a low-intensity color and the foreground will blink.
EXAMPLES
Run SetColor in the interactive mode:
sc
Request bright white on a blue background with a cyan border:
sc bold white blue cyan
Set up a reverse video screen:
sc reverse
NOTES
The ANSI.SYS device driver must be loaded into memory. This requires
DOS version 2.00 or later and the line
device=ansi.sys
in the CONFIG.SYS file.
The border cannot be set on some machines, notably the AT&T PC6300.
Also, the ANSI.SYS device driver supplied with MS-DOS version 2.11
erases to Normal attribute (white on black) regardless of the
specified attribute. However, displayed characters written after the
erasure will have the specified attributes.
FIGURE 14-1 ▐ Manual page for ViewFile
───────────────────────────────────────────────────────────────────────────
NAME
VF_a full-screen file-viewing program
SYNOPSIS
vf [-n] file ...
DESCRIPTION
ViewFile is a file-viewing program that features fast vertical and
horizontal scrolling through any text file. VF uses the PC arrow and
special-purpose keys to move around quickly and easily in a file. Most
commands have single-letter synonyms.
Absolute go-to-line and search-for-string commands may also be used to
seek a position in the file being viewed. Line-numbering is optional;
it can be turned on from the command line by using the -n option.
Numbering can also be toggled on and off while VF is running by using
the N key.
The following commands (and synonyms) are understood by VF:
══════════════════════════════════════════════════════════════════════
COMMAND MEANING
──────────────────────────────────────────────────────────────────────
PgUp (U) Scroll up the file one screen page
PgDn (D) Scroll down the file one screen page
Up arrow (-) Scroll up in the file one line
Down arrow (+) Scroll down in the file one line
Right arrow (>) Scroll right by 20 columns
Left arrow (<) Scroll left by 20 columns
Home (B) Go to beginning of file buffer
End (E) Go to end of file buffer
Alt-g (G) Go to a specified line in the buffer
Alt-h (H or ?) Display the help frame
Alt-n (N) Toggle line-numbering feature
\ (R) Reverse search for a literal string
/ (S) Search forward for a literal string
Esc Next file from list (quits if none)
Alt-q (Q) Quit
──────────────────────────────────────────────────────────────────────
This information is also available on-line in the built-in ViewFile
help frame.
EXAMPLES
"View" sequentially all of the C source files in the current
directory:
vf -n *.c
std.h
───────────────────────────────────────────────────────────────────────────
/*
* std.h
*/
/* data type aliases */
#define META short
#define UCHAR unsigned char
#define UINT unsigned int
#define ULONG unsigned long
#define USHORT unsigned short
/* Boolean data type */
typedef enum {
FALSE, TRUE
} BOOLEAN;
/* function return values and program exit codes */
#define OK 0
#define BAD 1
#define SUCCESS 0
#define FAILURE 1
/* infinite loop */
#define FOREVER while (1)
/* masks */
#define HIBYTE 0xFF00
#define LOBYTE 0x00FF
#define ASCII 0x7F
#define HIBIT 0x80
/* lengths */
#define MAXNAME 8
#define MAXEXT 3
#define MAXLINE 256
#define MAXPATH 64
/* special number */
#define BIGGEST 65535
replay
───────────────────────────────────────────────────────────────────────────
/*
* replay -- echo the command-line arguments
* to standard output
*/
#include <stdio.h>
#include <stdlib.h>
#include <local\std.h>
main(argc, argv)
int argc;
char **argv;
{
int i;
char **p;
static char pgm[MAXNAME + 1] = { "replay" };
void getpname(char *, char *);
/* get program name from DOS (version 3.00 and later) */
if (_osmajor >= 3)
getpname(*argv, pgm);
/* check for arguments */
if (argc == 1)
exit(1); /* none given */
/* echo the argument list, one per line */
p = argv;
printf("%s arguments:\n\n", pgm);
for (--argc, ++argv; argc > 0; --argc, ++argv)
printf("argv[%d] -> %s\n", argv - p, *argv);
exit(0);
} /* end main() */
getpname
───────────────────────────────────────────────────────────────────────────
/*
* getpname -- extract the base name of a program from the
* pathname string (deletes a drive specifier, any leading
* path node information, and the extension)
*/
#include <stdio.h>
#include <ctype.h>
void getpname(path, pname)
char *path; /* full or relative pathname */
char *pname; /* program name pointer */
{
char *cp;
/* find the end of the pathname string */
cp = path; /* start of pathname */
while (*cp != '\0')
++cp;
--cp; /* went one too far */
/* find the start of the filename part */
while (cp > path && *cp != '\\' && *cp != ':' && *cp != '/')
--cp;
if (cp > path)
++cp; /* move to right of pathname separator */
/* copy the filename part only */
while ((*pname = tolower(*cp)) != '.' && *pname != '\0') {
++cp;
++pname;
}
*pname = '\0';
}
showenv
───────────────────────────────────────────────────────────────────────────
/*
* showenv -- display the values of any DOS variables
* named on the invocation command line
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <local\std.h>
main(argc, argv, envp)
int argc;
char **argv;
char **envp;
{
register char *ep;
static char pgm[MAXNAME + 1] = { "showenv" };
extern void getpname(char *, char *);
/* use an alias if one is given to this program */
if (_osmajor >= 3)
getpname(*argv, pgm);
/* if no arguments, show full environment list */
if (argc == 1)
for (; *envp; ++envp)
printf("%s\n", *envp);
else {
/*
* treat all args as DOS variable names and
* display values of only specified variables
*/
++argv; /* skip past command name */
--argc;
while (argc > 0) {
if ((ep = getenv(strupr(*argv))) == NULL)
fprintf(stderr, "%s not defined\n", *argv);
else
printf("%s=%s\n", *argv, ep);
++argv;
--argc;
}
}
exit(0);
}
setmydir
───────────────────────────────────────────────────────────────────────────
/*
* setmydir -- try to change the DOS environment
*/
#include <stdio.h>
#include <stdlib.h>
#include <local\std.h>
main(argc, argv)
int argc;
char **argv;
{
register char **p;
static char var[] = { "MYDIR" };
static char pgm[MAXNAME + 1] = { "setmydir" };
extern void fatal(char *, int);
extern void getpname(char *, char *);
/* use an alias if one is given to this program */
if (_osmajor >= 3)
getpname(*argv, pgm);
/* try to add the MYDIR variable to the environment */
if (putenv("MYDIR=c:\\mydir") == -1)
fatal(pgm, "Error changing environment", 1);
/* display the environment for this process */
for (p = environ; *p; p++) {
printf("%s\n", *p);
}
exit(0);
}
timedata
───────────────────────────────────────────────────────────────────────────
/*
* timedata -- time zone and time value tests
*/
#include <stdio.h>
#include <time.h>
main()
{
long now;
struct tm *tbuf;
/* get TZ data into global variables */
tzset();
/* display the global time values */
printf("daylight savings time flag = %d\n", daylight);
printf("difference (in seconds) from GMT = %ld\n", timezone);
printf("standard time zone string is %s\n", tzname[0]);
printf("daylight time zone string is %s\n", tzname[1]);
/*
* display the current date and time values for
* local and universal time
*/
now = time(NULL);
printf("\nctime():\t%s\n", ctime(&now));
tbuf = localtime(&now);
printf("local time:\t%s\n", asctime(tbuf));
tbuf = gmtime(&now);
printf("universal time:\t%s\n", asctime(tbuf));
exit(0);
}
notes1
───────────────────────────────────────────────────────────────────────────
/*
* notes1 -- add an entry to a "notes" text file
*
* version 1: appends new data to NOTES.TXT in the
* current directory -- uses local date/time stamp
* as a header for each new entry
*/
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <local\std.h>
main()
{
FILE *fp;
static char notesfile[MAXPATH + 1] = { "notes.txt" };
char ch;
long ltime;
static char pgm[MAXNAME + 1] = { "notes1" };
extern void fatal(char *, char *, int);
/* try to open notes file in current directory */
if ((fp = fopen(notesfile, "a")) == NULL)
fatal(pgm, notesfile, 1);
/* append a header and date/time tag */
ltime = time(NULL);
fprintf(stderr, "Appending to %s: %s\n",
notesfile, ctime(<ime));
fprintf(fp, "%s\n", ctime(<ime));
/* append new text */
while ((ch = getchar()) != EOF)
putc(ch, fp);
/* clean up */
if (fclose(fp))
fatal(pgm, notesfile, 2);
exit(0);
}
notes2
───────────────────────────────────────────────────────────────────────────
/*
* notes2 -- add a date/time stamped entry to a
* "notes" data file. Allow user to optionally
* edit the data file upon completion of the entry.
*/
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <signal.h>
#include <process.h>
#include <local\std.h>
/* length of date/time string */
#define DT_STR 26
main(argc, argv)
int argc;
char **argv;
{
int n; /* number of lines added */
int exitcode = 0;
FILE *fp;
static char notesfile[MAXPATH + 1] = { "notes.txt" };
static char editname[MAXPATH + 1] = { "edlin" };
char ch;
char dt_stamp[DT_STR];
char *s;
long ltime;
static char pgm[MAXNAME + 1] = { "notes2" };
extern void fatal(char *, char *, int);
extern void getpname(char *, char *);
static int addtxt(FILE *, FILE *);
if (_osmajor >= 3)
getpname(*argv, pgm);
/* locate the "notes" database file and open it */
if (argc > 1)
strcpy(notesfile, *++argv);
else if (s = getenv("NOTESFILE"))
strcpy(notesfile, s);
if ((fp = fopen(notesfile, "a")) == NULL)
fatal(pgm, notesfile, 1);
/* disable Ctrl-Break interrupt */
if (signal(SIGINT, SIG_IGN) == (int(*)())-1)
perror("Cannot set signal");
/* append a header and date/time tag */
ltime = time(NULL);
strcpy(dt_stamp, ctime(<ime));
fprintf(stderr, "Appending to %s: %s", notesfile, dt_stamp);
fprintf(fp, "\n%s", dt_stamp);
/* add text to notes file */
if ((n = addtxt(stdin, fp)) == 0) {
fputs("No new text", stderr);
if (fclose(fp))
fatal(pgm, notesfile, 2);
exit(0);
}
else
fprintf(stderr, "%d line(s) added to %s\n", n, notesfile);
if (fclose(fp))
fatal(pgm, notesfile, 2);
/* optionally edit text in the notes file */
fprintf(stderr, "E + ENTER to edit; ENTER alone to quit: ");
while ((ch = tolower(getchar())) != '\n')
if (ch == 'e') {
if (s = getenv("EDITOR"))
strcpy(editname, s);
if ((exitcode = spawnlp(P_WAIT, editname, editname,
notesfile, NULL)) == -1)
fatal(pgm, editname, 3);
}
exit(exitcode);
}
/*
* addtxt -- append new text to notes file
*/
static int addtxt(fin, fout)
FILE *fin, *fout;
{
int ch;
int col = 0; /* column */
int n = 0; /* number of lines added */
while ((ch = fgetc(fin)) != EOF) {
if (ch == '.' && col == 0) {
ch = fgetc(fin); /* trash the newline */
break;
}
fputc(ch, fout);
if (ch == '\n') {
col = 0;
++n;
}
else
++col;
}
return (n);
}
run_once
───────────────────────────────────────────────────────────────────────────
/*
* run_once -- run a program one time and then
* "hang" the system to prevent unwanted use
*/
#include <stdio.h>
#include <stdlib.h>
#include <process.h>
#include <local\std.h>
main(argc, argv)
int argc;
char *argv[];
{
extern void fatal(char *, *, int);
++argv; /* skip over the program name */
/* run the specified command line [pgmname arg(s)] */
if (spawnvp(P_WAIT, *argv, argv) == -1)
fatal("run_once" "Error running specified program", 1);
fprintf(stderr, "Please turn off the power to the computer.\n");
FOREVER /* do nothing */
;
}
dos.h
───────────────────────────────────────────────────────────────────────────
/*
* dos.h
*
* Defines the structs and unions used to handle the input and output
* registers for the DOS interface routines defined in the V2.0 to V3.0
* compatibility package. It also includes macros to access the segment
* and offset values of MS C "far" pointers, so that they may be used by
* these routines.
*
* Copyright (C) Microsoft Corporation, 1984, 1985, 1986
*/
/* word registers */
struct WORDREGS {
unsigned int ax;
unsigned int bx;
unsigned int cx;
unsigned int dx;
unsigned int si;
unsigned int di;
unsigned int cflag;
};
/* byte registers */
struct BYTEREGS {
unsigned char al, ah;
unsigned char bl, bh;
unsigned char cl, ch;
unsigned char dl, dh;
};
/* general purpose registers union - overlays the corresponding word and
* byte registers.
*/
union REGS {
struct WORDREGS x;
struct BYTEREGS h;
};
/* segment registers */
struct SREGS {
unsigned int es;
unsigned int cs;
unsigned int ss;
unsigned int ds;
};
/* dosexterror struct */
struct DOSERROR {
int exterror;
char class;
char action;
char locus;
};
/* macros to break MS C "far" pointers into their segment and offset
* components
*/
#define FP_SEG(fp) (*((unsigned *)&(fp) + 1))
#define FP_OFF(fp) (*((unsigned *)&(fp)))
/* function declarations for those who want strong type checking
* on arguments to library function calls
*/
#ifdef LINT_ARGS /* argument checking enabled */
#ifndef NO_EXT_KEYS /* extended keywords are enabled */
int cdecl bdos(int, unsigned int, unsigned int);
int cdecl dosexterr(struct DOSERROR *);
int cdecl intdos(union REGS *, union REGS *);
int cdecl intdosx(union REGS *, union REGS *, struct SREGS *);
int cdecl int86(int, union REGS *, union REGS *);
int cdecl int86x(int, union REGS *, union REGS *, struct SREGS *);
void cdecl segread(struct SREGS *);
#else /* extended keywords not enabled */
int bdos(int, unsigned int, unsigned int);
int dosexterr(struct DOSERROR *);
int intdos(union REGS *, union REGS *);
int intdosx(union REGS *, union REGS *, struct SREGS *);
int int86(int, union REGS *, union REGS *);
int int86x(int, union REGS *, union REGS *, struct SREGS *);
void segread(struct SREGS *);
#endif /* NO_EXT_KEYS */
#else
#ifndef NO_EXT_KEYS /* extended keywords are enabled */
int cdecl bdos();
int cdecl dosexterr();
int cdecl intdos();
int cdecl intdosx();
int cdecl int86();
int cdecl int86x();
void cdecl segread();
#else /* extended keywords not enabled */
void segread();
#endif /* NO_EXT_KEYS */
#endif /* LINT_ARGS */
doslib.h
───────────────────────────────────────────────────────────────────────────
/*
* doslib.h
*/
/* DOS interrupts */
#define PGM_TERMINATE 0x20
#define BDOS_REQ 0x21
#define TERMINATE_ADDR 0x22
#define CB_EXIT_ADDR 0x23
#define CRITICAL_ERR 0x24
#define ABS_DISK_READ 0x25
#define ABS_DISK_WRITE 0x26
#define PGM_TERM_RES 0x27
/* DOS functions -- usually placed in ah register for C86 calls */
#define PGM 0x0
#define KEYIN_ECHO_CB 0x1
#define DSPY_CHAR 0x2
#define AUX_IN 0x3
#define AUX_OUT 0x4
#define PRNT_OUT 0x5
#define KEYIN_ECHO 0x6
#define KEYIN 0x7
#define KEYIN_CB 0x8
#define DSPY_STR 0x9
#define KEYIN_BUF 0xA
#define CH_READY 0xB
#define GET_CH 0xC
#define DISK_RESET 0xD
#define SELECT_DISK 0xE
#define OPEN_FILE 0xF
#define CLOSE_FILE 0x10
#define FIRST_FM 0x11
#define NEXT_FM 0x12
#define DELETE_FILE 0x13
#define READ_SEQ 0x14
#define WRITE_SEQ 0x15
#define CREATE_FILE 0x16
#define RENAME_FILE 0x17
/* 0x18 reserved */
#define CURRENT_DISK 0x19
#define SET_DTA 0x1A
#define DFLT_FAT_INFO 0x1B
#define FAT_INFO 0x1C
/* 0x1D - 0x20 reserved */
#define READ_RANDOM 0x21
#define WRITE_RANDOM 0x22
#define FILE_SIZE 0x23
#define SET_INT_VEC 0x25
#define NEW_PGM_SEG 0x26
#define RAND_BLK_READ 0x27
#define RAND_BLK_WRITE 0x28
#define PARSE_FILENAME 0x29
#define GET_DATE 0x2A
#define SET_DATE 0x2B
#define GET_TIME 0x2C
#define SET_TIME 0x2D
#define TOGGLE_VERIFY 0x2E
#define GET_DTA 0x2F
#define DOS_VERSION 0x30
#define PGM_TERM_KEEP 0x31
/* 0x32 reserved */
#define CB_CHECK 0x33
/* 0x34 reserved */
#define GET_INTR 0x35
#define GET_FREE_SPACE 0x36
/* 0x37 reserved */
#define INTL_INFO 0x38
#define MKDIR 0x39
#define RMDIR 0x3A
#define CHDIR 0x3B
#define CREAT 0x3C
#define OPEN_FD 0x3D
#define CLOSE_FD 0x3E
#define WRITE_FD 0x40
#define UNLINK 0x41
#define LSEEK 0x42
#define CHMOD 0x43
#define IOCTL 0x44
#define DUP 0x45
#define FORCE_DUP 0x46
#define GET_CUR_DIR 0x47
#define ALLOC 0x48
#define FREE 0x49
#define SET_BLOCK 0x4A
#define EXEC 0x4B
#define EXIT 0x4C
#define WAIT 0x4D
#define FIND_FIRST 0x4E
#define FIND_NEXT 0x4F
/* 0x50 - 53 reserved */
#define VERIFY_STATE 0x54
/* 0x55 reserved */
#define RENAME 0x56
#define FILE_MTIME 0x57
keydefs
───────────────────────────────────────────────────────────────────────────
/* keydefs -- values for special keys on IBM PC and clones */
#define XF 0x100 /* extended key flag */
#define K_F1 59 | XF /* function keys */
#define K_F2 60 | XF
#define K_F3 61 | XF
#define K_F4 62 | XF
#define K_F5 63 | XF
#define K_F6 64 | XF
#define K_F7 65 | XF
#define K_F8 66 | XF
#define K_F9 67 | XF
#define K_F10 68 | XF
#define K_SF1 84 | XF /* shifted function keys */
#define K_SF2 85 | XF
#define K_SF3 86 | XF
#define K_SF4 87 | XF
#define K_SF5 88 | XF
#define K_SF6 89 | XF
#define K_SF7 90 | XF
#define K_SF8 91 | XF
#define K_SF9 92 | XF
#define K_SF10 93 | XF
#define K_CF1 94 | XF /* control function keys */
#define K_CF2 95 | XF
#define K_CF3 96 | XF
#define K_CF4 97 | XF
#define K_CF5 98 | XF
#define K_CF6 99 | XF
#define K_CF7 100 | XF
#define K_CF8 101 | XF
#define K_CF9 102 | XF
#define K_CF10 103 | XF
#define K_AF1 104 | XF /* alternate function keys */
#define K_AF2 105 | XF
#define K_AF3 106 | XF
#define K_AF4 107 | XF
#define K_AF5 108 | XF
#define K_AF6 109 | XF
#define K_AF7 110 | XF
#define K_AF8 111 | XF
#define K_AF9 112 | XF
#define K_AF10 113 | XF
#define K_HOME 71 | XF /* cursor keypad (NumLock off; */
#define K_END 79 | XF /* not shifted) */
#define K_PGUP 73 | XF
#define K_PGDN 81 | XF
#define K_LEFT 75 | XF
#define K_RIGHT 77 | XF
#define K_UP 72 | XF
#define K_DOWN 80 | XF
#define K_CHOME 119 | XF /* control cursor keypad */
#define K_CEND 117 | XF
#define K_CPGUP 132 | XF
#define K_CPGDN 118 | XF
#define K_CLEFT 115 | XF
#define K_CRGHT 116 | XF
#define K_CTRLA 1 /* standard control keys */
#define K_CTRLB 2
#define K_CTRLC 3
#define K_CTRLD 4
#define K_CTRLE 5
#define K_CTRLF 6
#define K_CTRLG 7
#define K_CTRLH 8
#define K_CTRLI 9
#define K_CTRLJ 10
#define K_CTRLK 11
#define K_CTRLL 12
#define K_CTRLM 13
#define K_CTRLN 14
#define K_CTRLO 15
#define K_CTRLP 16
#define K_CTRLQ 17
#define K_CTRLR 18
#define K_CTRLS 19
#define K_CTRLT 20
#define K_CTRLU 21
#define K_CTRLV 22
#define K_CTRLW 23
#define K_CTRLX 24
#define K_CTRLY 25
#define K_CTRLZ 26
#define K_ALTA 30 | XF /* alternate keys */
#define K_ALTB 48 | XF
#define K_ALTC 46 | XF
#define K_ALTD 32 | XF
#define K_ALTE 18 | XF
#define K_ALTF 33 | XF
#define K_ALTG 34 | XF
#define K_ALTH 35 | XF
#define K_ALTI 23 | XF
#define K_ALTJ 36 | XF
#define K_ALTK 37 | XF
#define K_ALTL 38 | XF
#define K_ALTM 50 | XF
#define K_ALTN 49 | XF
#define K_ALTO 24 | XF
#define K_ALTP 25 | XF
#define K_ALTQ 16 | XF
#define K_ALTR 19 | XF
#define K_ALTS 31 | XF
#define K_ALTT 20 | XF
#define K_ALTU 22 | XF
#define K_ALTV 47 | XF
#define K_ALTW 17 | XF
#define K_ALTX 45 | XF
#define K_ALTY 21 | XF
#define K_ALTZ 44 | XF
#define K_ALT1 120 | XF /* additional alternate key */
#define K_ALT2 121 | XF /* combinations */
#define K_ALT3 122 | XF
#define K_ALT4 123 | XF
#define K_ALT5 124 | XF
#define K_ALT6 125 | XF
#define K_ALT7 126 | XF
#define K_ALT8 127 | XF
#define K_ALT9 128 | XF
#define K_ALT0 129 | XF
#define K_ALTDASH 130 | XF
#define K_ALTEQU 131 | XF
#define K_ESC 27 /* miscellaneous special keys */
#define K_SPACE 32
#define K_INS 82 | XF
#define K_DEL 83 | XF
#define K_TAB K_CTRLI
#define K_BACKTAB K_CTRLO
#define K_CTRL_PRTSC 114 | XF /* printer echoing toggle */
#define K_RETURN 13 /* return key variations */
#define K_SRETURN 13
#define K_CRETURN 10
bioslib.h
───────────────────────────────────────────────────────────────────────────
/* bioslib.h */
#define PRINT_SCRN 0x05 /* BIOS interrupts */
#define TOD_INIT 0x08
#define KEYBD_INIT 0x09
#define DISK_INIT 0x0E
#define VIDEO_IO 0x10
#define EQUIP_CK 0x11
#define MEM_SIZE 0x12
#define DISK_IO 0x13
#define RS232_IO 0x14
#define CASS_IO 0x15
#define KEYBD_IO 0x16
#define PRINT_IO 0x17
#define TOD 0x1A
#define VIDEO_INIT 0x1D
#define GRAPHICS 0x1F
#define SET_MODE 0 /* video routine numbers */
#define CUR_TYPE 1 /* (placed in register AH */
#define CUR_POS 2 /* before a BIOS interrupt 10H) */
#define GET_CUR 3
#define LPEN_POS 4
#define SET_PAGE 5
#define SCROLL_UP 6
#define SCROLL_DN 7
#define READ_CHAR_ATTR 8
#define WRITE_CHAR_ATTR 9
#define WRITE_CHAR 10
#define PALETTE 11
#define WRITE_DOT 12
#define READ_DOT 13
#define WRITE_TTY 14
#define GET_STATE 15
#define ALT_FUNCTION 18 /* EGA only */
#define WRITE_STR 19 /* AT only */
#define RESET_DISK 0 /* disk routine numbers */
#define DISK_STATUS 1
#define READ_SECTOR 2
#define WRITE_SECTOR 3
#define VERIFY_SECTOR 4
#define FORMAT_TRACK 5
#define KBD_READ 0 /* keyboard routine numbers */
#define KBD_READY 1
#define KBD_STATUS 2
equipchk
───────────────────────────────────────────────────────────────────────────
/* equipchk -- get equipment list */
#include <dos.h>
#include <local\bioslib.h>
#include <local\equip.h>
struct EQUIP Eq;
int equipchk()
{
union REGS inregs, outregs;
/* call BIOS equipment check routine */
int86(EQUIP_CK, &inregs, &outregs);
/* extract data from returned data word */
Eq.nprint = (outregs.x.ax & 0xC000) / 0x8000;
Eq.game_io = ((outregs.x.ax & 0x1000) / 0x1000) ? 1 : 0;
Eq.nrs232 = (outregs.x.ax & 0x0E00) / 0x0200;
Eq.ndrive = ((outregs.x.ax & 0x00C0) / 0x0040) + 1;
Eq.vmode = (outregs.x.ax & 0x0030) / 0x0010;
Eq.basemem = ((outregs.x.ax & 0x000C) / 0x0004) + 1;
Eq.disksys = outregs.x.ax & 0x0001 == 1;
return (outregs.x.cflag);
}
video.h
───────────────────────────────────────────────────────────────────────────
/* video.h */
/* current video state/mode information */
extern short Vmode;
extern short Vwidth;
extern short Vpage;
#define MAXVMODE 17
/* video limit tables */
extern short Maxrow[MAXVMODE];
extern short Maxcol[MAXVMODE];
extern short Maxpage[MAXVMODE];
/* active display */
#define MONO 1
#define COLOR 2
/* cursor modes */
#define CURSOR_OFF 0
#define CURSOR_ON 1
/* installed display adapters */
#define MDA 1
#define CGA 2
#define EGA 4
/* --- video modes --- */
/* CGA modes */
#define CGA_M40 0
#define CGA_C40 1
#define CGA_M80 2
#define CGA_C80 3
#define CGA_CMRES 4
#define CGA_MMRES 5
#define CGA_MHRES 6
/* MDA mode */
#define MDA_M80 7
/* PCjr modes */
#define PCJR_CLRES 8
#define PCJR_CMRES 9
#define PCJR_CHRES 10
/* modes 11 and 12 are not currently used */
/* EGA modes */
#define EGA_CMRES 13
#define EGA_CHRES 14
#define EGA_MHRES 15
#define EGA_EHRES 16
/* miscellaneous video masks */
#define CMASK 0x00FF /* character mask */
#define AMASK 0xFF00 /* attribute mask */
/* attribute modifiers */
#define BRIGHT 8
#define BLINK 128
/* primary video attributes */
#define BLU 1
#define GRN 2
#define RED 4
/* composite video attributes */
#define BLK 0
#define CYAN (BLU | GRN) /* 3 */
#define MAGENTA (BLU | RED) /* 5 */
#define BRN (GRN | RED) /* 6 */
#define WHT (BLU | GRN | RED) /* 7 */
#define GRAY (BLK | BRIGHT)
#define LBLU (BLU | BRIGHT)
#define LGRN (GRN | BRIGHT)
#define LCYAN (CYAN | BRIGHT)
#define LRED (RED | BRIGHT)
#define LMAG (MAG | BRIGHT)
#define YEL (BRN | BRIGHT)
#define BWHT (WHT | BRIGHT)
#define NORMAL WHT
#define REVERSE 112
/* drawing characters -- items having two numbers use
* the first number as the horizontal specifier */
/* single-line boxes */
#define VBAR1 179
#define VLINE 179 /* alias */
#define HBAR1 196
#define HLINE 196 /* alias */
#define ULC11 218
#define URC11 191
#define LLC11 192
#define LRC11 217
#define TL11 195
#define TR11 180
#define TT11 194
#define TB11 193
#define X11 197
/* double-line boxes */
#define VBAR2 186
#define HBAR2 205
#define ULC22 201
#define URC22 187
#define LLC22 200
#define LRC22 188
#define TL22 204
#define TR22 185
#define TT22 203
#define TB22 202
#define X22 206
/* single-line horizontal & double-line vertical boxes */
#define ULC12 214
#define URC12 183
#define LLC12 211
#define LRC12 189
#define TL12 199
#define TR12 182
#define TT12 210
#define TB12 208
#define X12 215
/* double-line horizontal & single-line vertical boxes */
#define ULC21 213
#define URC21 184
#define LLC21 212
#define LRC21 190
#define TL21 198
#define TR21 181
#define TT21 209
#define TB21 207
#define X21 216
/* full and partial blocks */
#define BLOCK 219
#define VBAR 219 /* alias */
#define VBARL 221
#define VBARR 222
#define HBART 223
#define HBARB 220
/* special character-graphic symbols */
#define BLANK 32
#define DIAMOND 4
#define UPARROW 24
#define DOWNARROW 25
#define RIGHTARROW 26
#define LEFTARROW 27
#define SLASH 47
getstate
───────────────────────────────────────────────────────────────────────────
/* getstate -- update video state structure */
#include <dos.h>
#include <local\std.h>
#include <local\bioslib.h>
/* current video state/mode information */
short Vmode;
short Vwidth;
short Vpage;
/* video tables -- these tables of video parameters use
* a value of -1 to indicate that an item is not supported
* and 0 to indicate that an item has a variable value. */
/* video limit tables */
short Maxrow[] = {
25, 25, 25, 25, 25, 25, 25, /* CGA modes */
25, /* MDA mode */
25, 25, 25, /* PCjr modes */
-1, -1, /* not used */
25, 25, 25, 43 /* EGA modes */
};
short Maxcol[] = {
40, 40, 80, 80, 40, 40, 80, /* CGA modes */
80, /* MDA mode */
-1, 40, 80, /* PCjr modes */
-1, -1, /* not used */
80, 80, 80, 80 /* EGA modes */
};
short Maxpage[] = {
8, 8, 4, 4, 1, 1, 1, /* CGA modes */
1, /* MDA mode */
0, 0, 0, /* PCjr modes */
-1, -1, /* not used */
8, 4, 1, 1 /* EGA modes */
};
int getstate()
{
union REGS inregs, outregs;
inregs.h.ah = GET_STATE;
int86(VIDEO_IO, &inregs, &outregs);
Vmode = outregs.h.al;
Vwidth = outregs.h.ah;
Vpage = outregs.h.bh;
return (outregs.x.cflag);
}
setvmode
───────────────────────────────────────────────────────────────────────────
/* setvmode -- set the video mode (color/graphics systems only) */
#include <dos.h>
#include <local\std.h>
#include <local\bioslib.h>
/***********************************************************
* mode # description
* ------------ ----------------------------------
* PC MODES:
* 0 40x25 Mono text (c/g default)
* 1 40x25 Color text
* 2 80x25 Mono text
* 3 80x25 Color text
* 4 320x200 4-color graphics (med res)
* 5 320x200 Mono graphics (med res)
* 6 640x200 2-color graphics (hi res)
* 7 80x25 on monochrome adapter
*
* PCjr MODES:
* 8 160x200 16-color graphics
* 9 320x200 16-color graphics
* 10 640x200 4-color fraphics
*
* EGA MODES:
* 13 320x200 16-color graphics
* 14 620x200 16-color graphics
* 15 640x350 mono graphics
* 16 640x350 color graphics (4- or 16-color)
***********************************************************/
int setvmode(vmode)
unsigned int vmode; /* user-specified mode number */
{
union REGS inregs, outregs;
inregs.h.ah = SET_MODE;
inregs.h.al = vmode; /* value not checked */
int86(VIDEO_IO, &inregs, &outregs);
getstate(); /* update video structure */
return (outregs.x.cflag);
}
setpage
───────────────────────────────────────────────────────────────────────────
/* setpage -- select "visual" screen page for viewing
* (the "active" page is the one being written to) */
#include <dos.h>
#include <local\std.h>
#include <local\bioslib.h>
#include <local\video.h>
int setpage(pg)
int pg; /* visual screen page number */
{
union REGS inregs, outregs;
/* check page number against table */
if (Maxpage[Vmode] > 0 && (pg < 0 || pg >= Maxpage[Vmode]))
return (-1);
/* change the visual page */
inregs.h.ah = SET_PAGE;
inregs.h.al = pg;
int86(VIDEO_IO, &inregs, &outregs);
return (outregs.x.cflag);
}
readcur
───────────────────────────────────────────────────────────────────────────
/* readcur -- pass back the cursor position (row, col) */
#include <dos.h>
#include <local\std.h>
#include <local\bioslib.h>
unsigned int readcur(row, col, pg)
unsigned int *row; /* current row */
unsigned int *col; /* current column */
unsigned int pg; /* screen page */
{
union REGS inregs, outregs;
inregs.h.ah = GET_CUR;
inregs.h.bh = pg;
int86(VIDEO_IO, &inregs, &outregs);
*col = outregs.h.dl; /* col */
*row = outregs.h.dh; /* row */
return (outregs.x.cflag);
}
putcur
───────────────────────────────────────────────────────────────────────────
/* putcur -- put cursor at specified position (row, col) */
#include <dos.h>
#include <local\std.h>
#include <local\bioslib.h>
unsigned int putcur(r, c, pg)
unsigned int
r, /* row */
c, /* column */
pg; /* screen page for writes */
{
union REGS inregs, outregs;
inregs.h.ah = CUR_POS;
inregs.h.bh = pg & 0x07;
inregs.h.dh = r & 0xFF;
inregs.h.dl = c & 0xFF;
int86(VIDEO_IO, &inregs, &outregs);
return (outregs.x.cflag);
}
getctype
───────────────────────────────────────────────────────────────────────────
/* getctype -- pass back cursor type info (scan lines) */
#include <dos.h>
#include <local\std.h>
#include <local\bioslib.h>
#define LO_NIBBLE 0x0F
int getctype(start_scan, end_scan, pg)
int *start_scan; /* starting scan line */
int *end_scan; /* ending scan line */
int pg; /* "visual" page */
{
union REGS inregs, outregs;
inregs.h.bh = pg;
inregs.h.ah = GET_CUR;
int86(VIDEO_IO, &inregs, &outregs);
/* end_scan = low 4 bits of cl */
*end_scan = outregs.h.cl & LO_NIBBLE;
/* starting_scan = low 4 bits of ah */
*start_scan = outregs.h.ch & LO_NIBBLE;
return (outregs.x.cflag);
}
setctype
───────────────────────────────────────────────────────────────────────────
/* setctype -- set the cursor start and end raster scan lines */
#include <dos.h>
#include <local\bioslib.h>
#define LO_NIBBLE 0x0F
#define CURSOR_OFF 0x2
#define MAXSCANLN 15
int setctype(start, end)
int start; /* starting raster scan line */
int end; /* ending raster scan line */
{
union REGS inregs, outregs;
inregs.h.ah = CUR_TYPE;
inregs.h.ch = start & LO_NIBBLE;
inregs.h.cl = end & LO_NIBBLE;
if (start >= MAXSCANLN) {
inregs.h.ah |= CURSOR_OFF;
inregs.h.al = MAXSCANLN;
}
int86(VIDEO_IO, &inregs, &outregs);
return (outregs.x.cflag);
}
readca
───────────────────────────────────────────────────────────────────────────
/* readca -- read character and attribute at current position */
#include <dos.h>
#include <local\std.h>
#include <local\bioslib.h>
int readca(ch, attr, pg)
unsigned char *ch;
unsigned char *attr;
unsigned int pg; /* screen page for reads */
{
union REGS inregs, outregs;
inregs.h.ah = READ_CHAR_ATTR;
inregs.h.bh = pg; /* display page */
int86(VIDEO_IO, &inregs, &outregs);
*ch = outregs.h.al; /* character */
*attr = outregs.h.ah; /* attribute */
/* return the value in AX register */
return (outregs.x.cflag);
}
writeca
───────────────────────────────────────────────────────────────────────────
/* writeca -- write character and attribute to the screen */
#include <dos.h>
#include <local\std.h>
#include <local\bioslib.h>
int writeca(ch, attr, count, pg)
unsigned char ch; /* character */
unsigned char attr; /* attribute */
int count; /* number of repetitions */
int pg; /* screen page for writes */
{
union REGS inregs, outregs;
inregs.h.ah = WRITE_CHAR_ATTR;
inregs.h.al = ch;
inregs.h.bh = pg;
inregs.h.bl = attr;
inregs.x.cx = count;
int86(VIDEO_IO, &inregs, &outregs);
return (outregs.x.cflag);
}
clrscrn
───────────────────────────────────────────────────────────────────────────
/* clrscrn -- clear the "visual" screen page */
#include <dos.h>
#include <local\std.h>
#include <local\bioslib.h>
#include <local\video.h>
int clrscrn(a)
unsigned int a; /* video attribute for new lines */
{
union REGS inregs, outregs;
inregs.h.ah = SCROLL_UP;
inregs.h.al = 0; /* blank entire window */
inregs.h.bh = a; /* use specified attribute */
inregs.h.bl = 0;
inregs.x.cx = 0; /* upper left corner */
inregs.h.dh = Maxrow[Vmode] - 1; /* bottom screen row */
inregs.h.dl = Maxcol[Vmode] - 1; /* rightmost column */
int86(VIDEO_IO, &inregs, &outregs);
return (outregs.x.cflag);
}
clrw
───────────────────────────────────────────────────────────────────────────
/* clrw -- clear specified region of "visual" screen page */
#include <dos.h>
#include <local\std.h>
#include <local\bioslib.h>
int clrw(t, l, b, r, a)
int t; /* top row of region to clear */
int l; /* left column */
int b; /* bottom row */
int r; /* right column */
unsigned char a; /* attribute for cleared region */
{
union REGS inregs, outregs;
inregs.h.ah = SCROLL_UP; /* scroll visual page up */
inregs.h.al = 0; /* blank entire window */
inregs.h.bh = a; /* attribute of blank lines */
inregs.h.bl = 0;
inregs.h.ch = t; /* upper left of scroll region */
inregs.h.cl = l;
inregs.h.dh = b; /* lower right of scroll region */
inregs.h.dl = r;
int86(VIDEO_IO, &inregs, &outregs);
return (outregs.x.cflag);
}
scroll
───────────────────────────────────────────────────────────────────────────
/* scroll -- scroll a region of the "visual" screen
* page up or down by n rows (0 = initialize region) */
#include <dos.h>
#include <local\std.h>
#include <local\bioslib.h>
int scroll(t, l, b, r, n, a)
int t; /* top row of scroll region */
int l; /* left column */
int b; /* bottom row */
int r; /* right column */
int n; /* number of lines to scroll */
/* sign indicates direction to scroll */
/* 0 means scroll all lines in the region (initialize) */
unsigned char a;/* attribute for new lines */
{
union REGS inregs, outregs;
if (n < 0) {
/* scroll visual page down n lines */
inregs.h.ah = SCROLL_DN;
inregs.h.al = -n;
}
else {
/* scroll visual page up n lines */
inregs.h.ah = SCROLL_UP;
inregs.h.al = n;
}
inregs.h.bh = a; /* attribute of blank lines */
inregs.h.bl = 0;
inregs.h.ch = t; /* upper-left of scroll region */
inregs.h.cl = l;
inregs.h.dh = b; /* lower-right of scroll region */
inregs.h.dl = r;
int86(VIDEO_IO, &inregs, &outregs);
return (outregs.x.cflag);
}
writea
───────────────────────────────────────────────────────────────────────────
/* writea -- write attribute only to screen memory (faked by
* reading char and attr and writing back the original
* character and the new attribute at each position) */
#include <local\std.h>
int writea(a, n, pg)
unsigned char a;/* video attribute */
int n; /* number of positions to write */
int pg; /* screen page */
{
int i;
int status;
unsigned short chx, attrx;
unsigned short r, c;
/* get starting (current) position */
status = 0;
status = readcur(&r, &c, pg);
for (i = 0; i < n; ++i) {
status += putcur(r, c + i, pg);
status += readca(&chx, &attrx, pg);
status += writeca(chx, a, 1, pg);
}
/* restore cursor position */
status += putcur(r, c, pg);
return (status);
}
drawbox
───────────────────────────────────────────────────────────────────────────
/* drawbox -- create a box with IBM line-drawing characters */
#include <local\video.h>
int drawbox(top, lft, btm, rgt, pg)
int top, lft, btm, rgt, pg;
{
int i;
int x; /* interior line length for top and bottom segments */
x = rgt - lft - 1;
/* draw the top row */
putcur(top, lft, pg);
put_ch(ULC11, pg);
writec(HBAR1, x, pg);
putcur(top, rgt, pg);
put_ch(URC11, pg);
/* draw the sides */
for (i = 1; i < btm - top; ++i)
{
putcur(top + i, lft, pg);
put_ch(VBAR1, pg);
putcur(top + i, rgt, pg);
put_ch(VBAR1, pg);
}
/* draw the bottom row */
putcur(btm, lft, pg);
put_ch(LLC11, pg);
writec(HBAR1, x, pg);
putcur(btm, rgt, pg);
put_ch(LRC11, pg);
}
cursor
───────────────────────────────────────────────────────────────────────────
/* cursor -- interactively set cursor shape */
#include <dos.h>
#include <stdlib.h>
#include <local\video.h>
#include <local\keydefs.h>
/* additional drawing characters (others are defined in video.h) */
#define DOT 254
#define NO_DOT 196
#define D_POINT 31
#define R_POINT 16
#define L_POINT 17
/* dimensions of the help frame */
#define BOX_Y 6
#define BOX_X 30
/* upper-left row and column of big cursor */
int Ulr, Ulc;
int Mid;
/* cursor scan-line-selection modes */
typedef enum { STARTSCAN, ENDSCAN } CMODE;
int main()
{
int i, j, ch;
int start, end;
int height, width;
static char spoint[] = { "Start\020" }; /* contains right pointer */
static char epoint[] = { "\021Stop" }; /* contains left pointer */
static char title[] = { "CURSOR: Control cursor shape (V1.0)" };
unsigned char
oldattr, /* video attribute upon entry */
headattr, /* video attribute of header */
attr, /* primary video attribute */
standout; /* highlighting video attribute */
CMODE mode;
static void drawdspy(int, int, int, int, int);
static void drawstart(int, char *);
static void drawend(int, int, char *);
static void drawactive(int, int, CMODE);
static void showhelp(int, int);
static void writestr(char *, int);
/* get video information and initialize */
getstate();
Mid = Vwidth / 2;
readca(&ch, &oldattr, Vpage); /* preserve user's video attribute */
getctype(&start, &end, Vpage); /* and cursor shape */
headattr = (WHT << 4) | BLK;
/* set parameters based on video mode (default = CGA) */
height = width = 8; /* use an 8 by 8 block character cell */
attr = (BLU << 4) | CYAN | BRIGHT;
standout = YEL;
if (Vmode == MDA_M80) {
/* uses a 14 by 9 dot block character cell */
height = 14;
width = 9;
attr = NORMAL;
standout = BWHT;
}
setctype(height + 1, height + 1); /* cursor off */
/* basic text and layout */
Ulr = 2;
Ulc = Mid - width / 2;
clrscrn(attr);
putcur(0, 0, Vpage);
writeca(' ', headattr, Vwidth, Vpage);
putcur(0, Mid - strlen(title) / 2, Vpage);
writestr(title, Vpage);
showhelp(Ulr + height + 1, Mid - BOX_X / 2);
/* interactively select cursor shape */
mode = STARTSCAN;
drawdspy(start, end, standout, width, height);
drawstart(start, spoint);
drawend(end, width, epoint);
drawactive(height, width, mode);
while (1) {
switch (ch = getkey()) {
case K_UP:
/* move up one scan line */
if (mode == STARTSCAN)
drawstart(start--, " ");
else
drawend(end--, width, " ");
break;
case K_DOWN:
/* move down one scan line */
if (mode == STARTSCAN)
drawstart(start++, " ");
else
drawend(end++, width, " ");
break;
case K_LEFT:
/* starting scan-line-selection mode */
mode = STARTSCAN;
drawactive(height, width, mode);
continue;
case K_RIGHT:
/* ending scan-line-selection mode */
mode = ENDSCAN;
drawactive(height, width, mode);
continue;
case K_RETURN:
/* set the new cursor shape */
setctype(start, end);
clrscrn(oldattr);
putcur(0, 0, Vpage);
exit(0);
}
/* make corrections at cursor image boundaries */
if (start < 0)
start = 0;
else if (start > height)
start = height;
if (end < 0)
end = 0;
else if (end >= height)
end = height - 1;
/* show updated cursor shape and pointers */
drawdspy(start, end, standout, width, height);
drawstart(start, spoint);
drawend(end, width, epoint);
}
exit(0);
} /* end main() */
/* drawdspy -- draw a magnified image of a cursor with the
* currently active scan lines depicted as a sequence of dots
* and inactive lines depicted as straight lines */
static void drawdspy(s, e, a, w, h)
int s; /* starting scan line */
int e; /* ending scan line */
int a; /* video attribute */
int w; /* width */
int h; /* height */
{
int i;
/* display an exploded image of each scan line */
for (i = 0; i < h; ++i) {
putcur(Ulr + i, Ulc, Vpage);
if (s >= h)
/* cursor is effectively off */
writeca(NO_DOT, a, w, Vpage);
else if ((s <= e && i >= s && i <= e) || /* a full block */
(s > e && (i <= e || i >= s))) /* a split block */
writeca(DOT, a, w, Vpage);
else
/* outside start/end range */
writeca(NO_DOT, a, w, Vpage);
}
} /* end drawdspy() */
/* drawstart -- display a pointer to the displayed starting
* scan line in the magnified cursor image */
static void drawstart(s, sp)
int s; /* starting scan line number */
char *sp; /* visual pointer to the displayed starting scan line */
{
putcur(Ulr + s, Ulc - strlen(sp), Vpage);
putstr(sp, Vpage);
} /* end drawstart() */
/* drawend -- display a pointer to the displayed ending
* scan line in the magnified cursor image */
static void drawend(e, w, ep)
int e; /* ending scan line number */
int w; /* width of the cursor image */
char *ep; /* visual pointer to the displayed ending scan line */
{
putcur(Ulr + e, Ulc + w, Vpage);
putstr(ep, Vpage);
} /* end drawend() */
static void drawactive(h, w, m)
int h, w;
CMODE m;
{
int col;
/* clear active selector row */
putcur(Ulr - 1, Ulc, Vpage);
writec(' ', w, Vpage);
/* point to active selector */
col = (m == STARTSCAN) ? 0 : w - 1;
putcur(Ulr - 1, Ulc + col, Vpage);
writec(D_POINT, 1, Vpage);
} /* end drawactive() */
/ showhelp -- display a set of instructions about the
* use of the cursor program in a fine-ruled box */
static void showhelp(r, c)
int r, c; /* upper-left corner of help frame */
{
static char title[] = { " Instructions " };
extern int drawbox(int, int, int, int, int);
/* fine-ruled box */
clrw(r, c, r + BOX_Y, c + BOX_X, (WHT << 4) | GRN | BRIGHT);
drawbox(r, c, r + BOX_Y, c + BOX_X, Vpage);
/* centered title */
putcur(r, c + (BOX_X - strlen(title)) / 2, Vpage);
putstr(title, Vpage);
/* display symbols and text using brute-force positioning */
putcur(r + 2, c + 2, Vpage);
put_ch(LEFTARROW, Vpage);
put_ch(RIGHTARROW, Vpage);
putstr(" Change selection mode", Vpage);
putcur(r + 3, c + 2, Vpage);
put_ch(UPARROW, Vpage);
put_ch(DOWNARROW, Vpage);
putstr(" Select scan lines", Vpage);
putcur(r + 4, c + 2, Vpage);
put_ch(L_POINT, Vpage);
put_ch(LRC11, Vpage);
putstr(" Set shape and exit", Vpage);
} /* end showhelp() */
/* writestr -- write a string in the prevailing video attribute */
static void writestr(s, pg)
char *s /* string to write*/
unsigned int pg; /* screen page for writes */
{
unsigned int r, c, cO;
readcur(&r, &c, pg);
for (cO = c; *s != '\0'; ++s, ++c) {
putcur(r, c, pg);
writec(*s, 1, pg);
}
/* restore cursor position */
putcur(r, cO, pg);
} /* end writestr() */
makefile for the BIOS library
───────────────────────────────────────────────────────────────────────────
# makefile for the BIOS library
LINC = c:\include\local
LLIB = c:\lib\local
# --- inference rules ---
.c.obj:
msc $*;
# --- objects ---
O1 = clrscrn.obj clrw.obj drawbox.obj equipchk.obj getctype.obj
getstate.obj
O2 = kbd_stat.obj memsize.obj palette.obj putcur.obj putstr.obj put_ch.obj
O3 = readca.obj readcur.obj readdot.obj scroll.obj setctype.obj setpage.obj
O4 = setvmode.obj writea.obj writec.obj writeca.obj writedot.obj
writetty.obj
# --- compile sources ---
clrscrn.obj: clrscrn.c $(LINC)\bioslib.h $(LINC)\std.h
clrw.obj: clrw.c $(LINC)\bioslib.h $(LINC)\std.h
drawbox.obj: drawbox.c $(LINC)\video.h
equipchk.obj: equipchk.c $(LINC)\bioslib.h $(LINC)\equip.h
getctype.obj: getctype.c $(LINC)\bioslib.h $(LINC)\std.h
getstate.obj: getstate.c $(LINC)\bioslib.h $(LINC)\std.h
kbd_stat.obj: kbd_stat.c $(LINC)\bioslib.h $(LINC)\keybdlib.h
palette.obj: palette.c $(LINC)\bioslib.h
putcur.obj: putcur.c $(LINC)\bioslib.h $(LINC)\std.h
putstr.obj: putstr.c $(LINC)\bioslib.h
put_ch.obj: put_ch.c $(LINC)\bioslib.h
readca.obj: readca.c $(LINC)\bioslib.h $(LINC)\std.h
readcur.obj: readcur.c $(LINC)\bioslib.h $(LINC)\std.h
readdot.obj: readdot.c $(LINC)\bioslib.h $(LINC)\std.h
scroll.obj: scroll.c $(LINC)\bioslib.h $(LINC)\std.h
setctype.obj: setctype.c $(LINC)\bioslib.h
setpage.obj: setpage.c $(LINC)\bioslib.h $(LINC)\std.h $(LINC)\video.h
setvmode.obj: setvmode.c $(LINC)\bioslib.h $(LINC)\std.h
writea.obj: writea.c $(LINC)\std.h
writec.obj: writec.c $(LINC)\bioslib.h $(LINC)\std.h
writeca.obj: writeca.c $(LINC)\bioslib.h $(LINC)\std.h
writedot.obj: writedot.c $(LINC)\bioslib.h $(LINC)\std.h
writetty.obj: writetty.c $(LINC)\bioslib.h $(LINC)\std.h
# --- create and install the library ---
$(LLIB)\bios.lib: $(O1) $(O2) $(O3) $(O4)
del $(LLIB)\bios.lib
lib $(LLIB)\bios +$(O1);
lib $(LLIB)\bios +$(O2);
lib $(LLIB)\bios +$(O3);
lib $(LLIB)\bios +$(O4);
del $(LLIB)\bios.bak
writemsg
───────────────────────────────────────────────────────────────────────────
/* writemsg -- displays a message in a field of the prevailing
* video attribute and returns the number of displayable message
* characters written; truncates the message if its too long
* to fit in the field */
#include <stdio.h>
#include <local\std.h>
int writemsg(r, c, w, s1, s2, pg)
int r, c, w ;
char *s1, *s2;
int pg;
{
int n = 0;
char *cp;
/* display first part of the message */
if (s1 != NULL)
for (cp = s1; *cp != '\0' && n < w; ++n, ++cp) {
putcur(r, c + n, pg);
writec(*cp, 1, pg);
}
/* display second part of the message */
if (s2 != NULL)
for (cp = s2; *cp != '\0' && n < w; ++n, ++cp) {
putcur(r, c + n, pg);
writec(*cp, 1, pg);
}
/* pad the remainder of the field, if any, with spaces */
if (n < w) {
putcur(r, c + n, pg);
writec(' ', w - n, pg);
}
return (n);
}
getopt()
───────────────────────────────────────────────────────────────────────────
/*
* Copyright (c) 1984, 1985 AT&T
* All Rights Reserved
*
* ----- Author's Note -----
* getopt() is reproduced with permission of the AT&T UNIX(R) System
* Toolchest. This is a public domain version of getopt(3) that is
* distributed to registered Toolchest participants.
* Defining DOS_MODS alters the code slightly to obtain compatibility
* with DOS and support libraries provided with most DOS C compilers.
*/
#define DOS_MODS
#if defined (DOS_MODS)
/* getopt() for DOS */
#else
#ident "@(#)getopt.c 1.9"
#endif
/* 3.0 SID # 1.2 */
/*LINTLIBRARY*/
#define NULL 0
#define EOF (-1)
/*
* For this to work under versions of DOS prior to 3.00, argv[0]
* must be set in main() to point to a valid program name or a
* reasonable substitute string. (ARH, 10-8-86)
*/
#define ERR(s, c) if(opterr){\
char errbuf[2];\
errbuf[0] = c; errbuf[1] = '\n';\
(void) write(2, argv[0], (unsigned)strlen(argv[0]));\
(void) write(2, s, (unsigned)strlen(s));\
(void) write(2, errbuf, 2);}
#if defined (DOS_MODS)
/* permit function prototyping under DOS */
#include <stdlib.h>
#include <string.h>
#else
/* standard UNIX declarations */
extern int strcmp();
extern char *strchr();
/*
* The following line was moved here from the ERR definition
* to prevent a "duplicate definition" error message when the
* code is compiled under DOS. (ARH, 10-8-86)
*/
extern int strlen(), write();
#endif
int opterr = 1;
int optind = 1;
int optopt;
char *optarg;
int
getopt(argc, argv, opts)
int argc;
char **argv, *opts;
{
static int sp = 1;
register int c;
register char *cp;
if(sp == 1)
if(optind >= argc ||
argv[optind][0] != '-' || argv[optind][1] == '\0')
return(EOF);
else if(strcmp(argv[optind], "--") == NULL) {
optind++;
return(EOF);
}
optopt = c = argv[optind][sp];
if(c == ':' || (cp=strchr(opts, c)) == NULL) {
ERR(": illegal option -- ", c);
if(argv[optind][++sp] == '\0') {
optind++;
sp = 1;
}
return('?');
}
if(*++cp == ':') {
if(argv[optind][sp+1] != '\0')
optarg = &argv[optind++][sp+1];
else if(++optind >= argc) {
ERR(": option requires an argument -- ", c);
sp = 1;
return('?');
} else
optarg = argv[optind++];
sp = 1;
} else {
if(argv[optind][++sp] == '\0') {
sp = 1;
optind++;
}
optarg = NULL;
}
return(c);
}
cat
───────────────────────────────────────────────────────────────────────────
/*
* cat -- concatenate files
*/
#include <stdio.h>
#include <stdlib.h>
#include <local\std.h>
main(argc, argv)
int argc;
char **argv;
{
int ch;
char *cp;
FILE *fp;
BOOLEAN errflag, silent;
static char pgm[MAXNAME + 1] = { "cat" };
extern void getpname(char *, char *);
extern int fcopy(FILE *, FILE *);
extern int getopt(int, char **, char *);
extern int optind;
extern char *optarg;
/* use an alias if one is given to this program */
if (_osmajor >= 3)
getpname(*argv, pgm);
/* process optional arguments, if any */
errflag = FALSE;
silent = FALSE;
while ((ch = getopt(argc, argv, "s")) != EOF)
switch (ch) {
case 's':
/* don't complain about nonexistent files */
silent = TRUE;
break;
case '?':
/* say what? */
errflag = TRUE;
break;
}
if (errflag == TRUE) {
fprintf(stderr, "Usage: %s [-s] file...\n", pgm);
exit(1);
}
/* process any remaining arguments */
argc -= optind;
argv += optind;
if (argc == 0)
/* no file names -- use standard input */
if (fcopy(stdin, stdout) != 0) {
fprintf(stderr, "error copying stdin");
exit(2);
}
else
exit(0);
/* copy the contents of each named file to standard output */
for (; argc-- > 0; ++argv) {
if ((fp = fopen(*argv, "r")) == NULL) {
if (silent == FALSE)
fprintf(stderr, "%s: can't open %s\n",
pgm, *argv);
continue;
}
if (fcopy(fp, stdout) != 0) {
fprintf(stderr, "%s: Error while copying %s",
pgm, *argv);
exit(3);
}
if (fclose(fp) != 0) {
fprintf(stderr, "%s: Error closing %s",
pgm, *argv);
exit(4);
}
}
exit(0);
}
timer
───────────────────────────────────────────────────────────────────────────
/*
* timer -- general purpose timer program; uses the
* PC's intra-application communication area (ICA) as
* a time and date buffer
*/
#include <dos.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <memory.h>
#include <local\std.h>
#define NBYTES 16
#define ICA_SEG 0x4F
#define MAXTNUM 3
#define TIMEMASK 0x3FFFFFFF
#define FLAGBIT 0x80000000
#define IDBIT 0x40000000
main(argc, argv)
int argc;
char *argv[];
{
int ch;
char *cp;
int tn; /* timer number */
int errflag; /* error flag */
int eflag; /* elapsed time flag */
int sflag; /* start timer flag */
char dest[MAXPATH + 1]; /* destination file name */
char timestr[MAXLINE]; /* buffer for elapsed time string */
long now; /* current time */
long then; /* previously recorded time */
FILE *fout;
unsigned long tdata[MAXTNUM];
struct SREGS segregs;
static char pgm[MAXNAME + 1] = { "timer" };
static void usage(char *, char *);
extern char interval(long, char *);
extern void getpname(char *, char *);
extern int getopt(int, char **, char *);
extern int optind, opterr;
extern char *optarg;
if (_osmajor >= 3)
getpname(*argv, pgm);
/* process optional arguments */
fout = stdout;
tn = 0;
errflag = eflag = sflag = 0;
while ((ch = getopt(argc, argv, "0123ef:s")) != EOF) {
switch (ch) {
case 'e':
/* report elapsed timing */
++eflag;
break;
case 'f':
/* use specified log file or stream */
strcpy(dest, optarg);
if ((fout = fopen(dest, "a")) == NULL) {
fprintf(stderr, "%s: Cannot open %s\n",
pgm, dest);
exit(1);
}
break;
case 's':
/* start (or restart) timing an interval */
++sflag;
break;
case '0':
case '1':
case '2':
case '3':
/* use specified timer */
tn = ch - 0x30;
break;
case '?':
/* bad option flag */
++errflag;
break;
}
}
argc -= optind;
argv += optind;
/* check for errors */
if (errflag > 0 || argc > 0)
usage(pgm, "Bad command line option(s)");
segread(&segregs);
/* report current date and time */
now = time(NULL);
fprintf(fout, "%s", ctime(&now));
/* control and report timer data */
if (eflag) {
/* report elapsed time for specified timer */
movedata(ICA_SEG, 0, segregs.ds, tdata, NBYTES);
then = tdata[tn];
if ((then & FLAGBIT) != FLAGBIT || (then & IDBIT) !=
IDBIT) {
fprintf(stderr, "Timer database corrupted or not
set\n");
exit(1);
}
interval(now - (then & TIMEMASK), timestr);
fprintf(stdout, "Elapsed time = %s\n", timestr);
}
if (sflag) {
/* start (or restart) specified timer */
movedata(ICA_SEG, 0, segregs.ds, tdata, NBYTES);
tdata[tn] = (now & TIMEMASK) | FLAGBIT | IDBIT;
movedata(segregs.ds, tdata, ICA_SEG, 0, NBYTES);
}
fputc('\n', fout);
exit(0);
}
/*
* usage -- display a usage message and exit
* with an error indication
*/
static void
usage(pname, mesg)
char *pname;
char *mesg;
{
fprintf(stderr, "%s\n", mesg);
fprintf(stderr, "Usage: %s [-efs#]\n", pname);
fprintf(stderr, "\t-e \tshow an elapsed time (must use start
first)\n");
fprintf(stderr, "\t-f file\tappend output to specified file\n");
fprintf(stderr, "\t-s \tstart (or restart) an interval timer\n");
fprintf(stderr, "\t-# \tselect a timer (0 to 3; default is 0)\n");
exit(2);
}
interval
───────────────────────────────────────────────────────────────────────────
/*
* interval -- report the interval given in seconds as
* a human-readable null-terminated string
*/
#include <stdio.h>
char *
interval(seconds, buf)
long seconds;
char *buf;
{
int hh, mm, ss;
long remainder;
/* calculate the values */
hh = seconds / 3600;
remainder = seconds % 3600;
mm = remainder / 60;
ss = remainder - (mm * 60);
sprintf(buf, "%02d:%02d:%02d\0", hh, mm, ss);
return (buf);
}
delay
───────────────────────────────────────────────────────────────────────────
/*
* delay -- provide a delay of ** approximately ** the
* specified duration (resolution is about 0.055 second)
*/
#include <local\timer.h>
void
delay(d)
float d;/* duration in seconds and fractional seconds */
{
long ticks, then;
extern long getticks();
/* convert duration to number of PC clock ticks */
ticks = d * TICKRATE;
/* delay for the specified interval */
then = getticks() + ticks;
while (1)
if (getticks() >= then)
break;
}
getticks
───────────────────────────────────────────────────────────────────────────
/*
* getticks -- get the current bios clock ticks value
*/
#include <dos.h>
#define BIOS_DATA_SEG 0x40
#define TIMER_DATA 0x6C
#define TICKS_PER_DAY 0x01800B0L
long getticks()
{
static long total = 0; /* accumulated count of timer ticks */
long count; /* current BIOS TOD count */
long far *lp /* far pointer */
/* set up the far pointera to the BIOS TOD counter */
FP_SEG(lp) = BIOS_DATA_SEG;
FP_OFF(lp) = TIMER_DATA;
while (1) { /* read the TOD count */
count = *lp
if (*lp == count)
break; /* two matching TOD readings */
}
/* correct for clock roll-over, if necessary */
total = (count < total) ? count + TICKS_PER_DAY : count;
return (total);
}
sweep
───────────────────────────────────────────────────────────────────────────
/*
* sweep -- produce a sound that sweeps from
* a low to a high frequency repeatedly until a
* key is pressed
*/
#include <conio.h>
#include <local\sound.h>
main()
{
unsigned int f;
int d, n;
extern void setfreq(unsigned int);
SPKR_ON;
while (1) {
/* give the user a way out */
if (kbhit())
break;
n = 10;
for (f = 100; f <= 5000; f += n) {
setfreq(f);
d = 1000;
/* fake a short delay (machine dependent) */
while (d-- > 0)
;
n += 10;
}
}
SPKR_OFF;
exit(0);
}
sound
───────────────────────────────────────────────────────────────────────────
/*
* sound -- produce a constant tone for a specified duration
*/
#include <conio.h>
#include <local\sound.h>
void
sound(f, dur)
unsigned int f; /* frequency of pitch in hertz */
float dur; /* in seconds and tenths of seconds */
{
extern void setfreq(unsigned int);
extern void delay(float);
/* set the frequency in hertz */
setfreq(f);
/* turn the speaker on for specified duration */
SPKR_ON;
delay(dur);
SPKR_OFF;
}
sounds
───────────────────────────────────────────────────────────────────────────
/*
* sounds -- make various sounds on demand
*/
#include <stdio.h>
#include <conio.h>
#include <math.h>
#define ESC 27
extern void sound(unsigned int, float);
main()
{
int ch;
fprintf(stderr, "1=warble 2=error 3=confirm 4=warn\n");
fprintf(stderr, "Esc=quit\n");
while ((ch = getch()) != ESC)
switch (ch) {
case '1':
warble();
break;
case '2':
error();
break;
case '3':
confirm();
break;
case '4':
warn();
break;
}
exit(0);
}
#define CYCLES 3
#define LOTONE 600
#define HITONE 1200
#define PERIOD 0.1
warble()
{
int i;
for (i = 0; i < 2 * CYCLES; ++i)
if (i % 2)
sound(LOTONE, PERIOD);
else
sound(HITONE, PERIOD);
}
error()
{
float d = 0.1;
sound(440, d);
sound(220, d);
}
confirm()
{
float d = 0.1;
sound(440, d);
sound(880, d);
}
warn()
{
float d = 0.2;
sound(100, d);
}
getreply
───────────────────────────────────────────────────────────────────────────
/*
* getreply -- display a message and wait for a reply
*/
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <ctype.h>
#include <local\std.h>
#include <local\keydefs.h>
#include "linebuf.h"
char *
getreply(row, col, width, mesg, lp, size, attr, pg)
short row, col, width; /* window location and width */
char *mesg; /* message text */
LINEBUF *lp; /* line pointer */
short size; /* size of line buffer */
short attr; /* video attribute for response field */
short pg; /* active display page */
{
int n, k, len;
short mfw; /* message field width */
short rfw; /* response field width */
short ccol; /* visible cursor column */
int msgflag; /* nonzero after a message is displayed */
char *cp; /* character pointer */
char *wp; /* pointer to window start */
char *tmp; /* temporary char pointer */
extern int writemsg(short, short, short, char *, char *, short);
/* display the prompt string and calculate response field width */
putcur(row, col, pg);
mfw = writemsg(row, col, width, mesg, NULL, pg);
rfw = width - mfw;
writea(attr, rfw, pg);
/* collect the user's response */
memset(lp->l_buf, '\0', size);
wp = cp = lp->l_buf;
putcur(row, col + mfw, pg);
msgflag = 0;
while ((k = getkey()) != K_RETURN) {
if (msgflag) {
/* clear old messages */
errmsg("");
putcur(row, ccol, pg);
msgflag = 0;
}
if (isascii(k) && isprint(k)) {
len = strlen(cp);
if (cp + len - lp->l_buf < size - 1) {
memcpy(cp + 1, cp, len);
*cp = k;
++cp;
}
else {
errmsg("input buffer full");
++msgflag;
}
}
else
switch (k) {
case K_LEFT:
/* move left one character */
if (cp > lp->l_buf)
--cp;
break;
case K_RIGHT:
/* move right one character */
if (*cp != '\0')
++cp;
break;
case K_UP:
/* pop a line off the stack */
if (lp->l_prev != NULL) {
lp = lp->l_prev;
wp = cp = lp->l_buf;
}
break;
case K_DOWN:
/* push a line onto the stack */
if (lp->l_next != NULL) {
lp = lp->l_next;
wp = cp = lp->l_buf;
}
break;
case K_HOME:
/* beginning of buffer */
cp = lp->l_buf;
break;
case K_END:
/* end of buffer */
while (*cp != '\0')
++cp;
break;
case K_CTRLH:
if (cp > lp->l_buf) {
tmp = cp - 1;
memcpy(tmp, cp, strlen(tmp));
--cp;
}
break;
case K_DEL:
/* delete character at cursor */
memcpy(cp, cp + 1, strlen(cp));
break;
case K_ESC:
/* cancel current input */
lp->l_buf[0] = '\0';
putcur(row, col, pg);
writec(' ', width, pg);
return (NULL);
default:
errmsg("unknown command");
++msgflag;
break;
}
/* adjust the window pointer if necessary */
if (cp < wp)
wp = cp;
else if (cp >= wp + rfw)
wp = cp + 1 - rfw;
/* display the reply window */
ccol = col + mfw;
writemsg(row, ccol, rfw, wp, NULL, pg);
/* reposition the cursor */
ccol = col + mfw + (cp - wp);
putcur(row, ccol, pg);
}
putcur(row, col, pg);
writec(' ', width, pg); /* blank message area */
return (lp->l_buf);
}
tstreply
───────────────────────────────────────────────────────────────────────────
/*
* tstreply -- test the getreply function
*/
#include <stdio.h>
#include <string.h>
#include <local\std.h>
#include <local\video.h>
#include "linebuf.h"
#define INPUT_ROW 0
#define INPUT_COL 40
#define WIDTH 40
int Apage = 0;
BOOLEAN Silent = FALSE;
main(argc, argv)
int argc;
char *argv[];
{
unsigned int r, c, ch, attr, revattr;
char reply[MAXPATH + 1];
LINEBUF buf;
extern char *getreply(short, short, short,
char *, LINEBUF *, short, short, short);
/* process command line */
if (argc == 2 && strcmp(argv[1], "-s") == 0)
Silent = TRUE;
else if (argc > 2) {
fprintf(stderr, "Usage: tstreply [-s]\n");
exit(1);
}
/* initial setup */
getstate();
readca(&ch, &attr, Apage);
revattr = ((attr << 4) | (attr >> 4)) & 0x77;
clrscrn(attr);
putcur(0, 0, Apage);
writestr("TSTREPLY", Apage);
putcur(1, 0, Apage);
writec(HLINE, Maxcol[Vmode} - 1, Apage);
buf.l_buf = reply;
buf.l_next = buf.l_prev = (LINEBUF *)NULL;
/* demo getreply() */
if (getreply(INPUT_ROW, INPUT_COL, WIDTH, "File: ", &buf,
MAXPATH, revattr, 0) == NULL) {
putcur(INPUT_ROW, INPUT_COL, Apage);
writeca(' ', attr, WIDTH, Apage);
putcur(2, 0, Apage);
fprintf(stderr, "input aborted\n");
exit(1);
}
putcur(INPUT_ROW, INPUT_COL, Apage);
writeca(' ', attr, WIDTH, Apage);
putcur(2, 0, Apage);
fprintf(stderr, "reply = %s\n", reply);
exit(0);
}
#define MSG_ROW 24
#define MSG_COL 0
int errmsg(mesg)
char *mesg;
{
int n;
extern void sound(unsigned int, float);
putcur(MSG_ROW, MSG_COL, Apage);
if ((n = strlen(mesg)) > 0) {
writestr(mesg, Apage);
if (Silent == FALSE)
sound(100, 0.2);
}
else
writec(' ', Maxcol[Vmode] - 1 - MSG_COL, Apage);
return (n);
}
fconfig
───────────────────────────────────────────────────────────────────────────
/*
* fconfig -- return a FILE pointer to a local or
* global configuration file, or NULL if none found
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <local\std.h>
FILE *
fconfig(varname, fname)
char *varname;
char *fname;
{
FILE *fp;
char pname[MAXPATH + 1];
char *p;
/* look for a local configuration file */
if ((fp = fopen(fname, "r")) != NULL)
return (fp);
/* look for a directory variable */
if ((p = getenv(strupr(varname))) != NULL) {
strcpy(pname, p);
strcat(pname, "\\");
strcat(pname, fname);
if ((fp = fopen(pname, "r")) != NULL)
return (fp);
}
/* didn't find anything to read */
return (NULL);
}
printer.h
───────────────────────────────────────────────────────────────────────────
/*
* printer.h -- header for printer-control functions
*/
/* ASCII codes used for Epson MX/FX-series printer-control */
#define DC2 18 /* cancel condensed type mode */
#define DC4 20 /* cancel expanded type mode */
#define ESC 27 /* signal start of printer-control sequences */
#define FF 12 /* top of page next page */
#define SO 14 /* start expanded type mode */
#define SI 15 /* start condensed type mode */
/* font types */
#define NORMAL 0x00
#define CONDENSED 0x01
#define DOUBLE 0x02
#define EMPHASIZED 0x04
#define EXPANDED 0x08
#define ITALICS 0x10
#define UNDERLINE 0x20
/* miscellaneous constants */
#define MAXPSTR 32 /* maximum printer-control string length */
/* primary printer-control data structure */
typedef struct printer_st {
/* hardware initialize/reset */
char p_init[MAXPSTR];
/* set option strings and codes */
char p_bold[MAXPSTR]; /* bold (emphasized) on */
char p_cmp[MAXPSTR]; /* compressed on */
char p_ds[MAXPSTR]; /* double strike on */
char p_exp[MAXPSTR]; /* expanded (double width) on */
char p_ul[MAXPSTR]; /* underline on */
char p_ital[MAXPSTR]; /* italic on */
/* reset option strings and codes */
char p_norm[MAXPSTR]; /* restore normal font */
char p_xbold[MAXPSTR]; /* bold (emphasized) off */
char p_xcmp[MAXPSTR]; /* compressed off */
char p_xds[MAXPSTR]; /* double strike off */
char p_xexp[MAXPSTR]; /* expanded (double width) off */
char p_xul[MAXPSTR]; /* underline off */
char p_xital[MAXPSTR]; /* italic off */
} PRINTER;
printer
───────────────────────────────────────────────────────────────────────────
/*
* printer -- interface functions for printer
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <local\std.h>
#include <local\printer.h>
PRINTER prt; /* printer data */
/*
* setprnt -- install printer codes from configuration
* file for printer (defaults to Epson MX/FX series)
*/
#define NSELEM 13
int
setprnt()
{
int n;
char *s, line[MAXLINE];
FILE *fp, *fconfig(char *, char *);
/* use local or global config file, if any */
if ((fp = fconfig("CONFIG", "printer.cnf")) != NULL) {
n = 0;
while (fgets(line, MAXLINE, fp) != NULL) {
if ((s = strtok(line, " \t\n")) == NULL)
return (-1);
switch (n) {
case 0:
strcpy(prt.p_init, s);
break;
case 1:
strcpy(prt.p_bold, s);
break;
case 2:
strcpy(prt.p_ds, s);
break;
case 3:
strcpy(prt.p_ital, s);
break;
case 4:
strcpy(prt.p_cmp, s);
break;
case 5:
strcpy(prt.p_exp, s);
break;
case 6:
strcpy(prt.p_ul, s);
break;
case 7:
strcpy(prt.p_xbold, s);
break;
case 8:
strcpy(prt.p_xds, s);
break;
case 9:
strcpy(prt.p_xital, s);
break;
case 10:
strcpy(prt.p_xcmp, s);
break;
case 11:
strcpy(prt.p_xexp, s);
break;
case 12:
strcpy(prt.p_xul, s);
break;
default:
/* too many lines */
return (-1);
}
++n;
}
if (n != NSELEM)
/* probably not enough lines */
return (-1);
}
/* or use Epson defaults */
strcpy(prt.p_init, "\033@"); /* hardware reset */
strcpy(prt.p_bold, "\033E"); /* emphasized mode */
strcpy(prt.p_ds, "\033G"); /* double-strike mode */
strcpy(prt.p_ital, "\0334"); /* italic mode */
strcpy(prt.p_cmp, "\017"); /* condensed mode */
strcpy(prt.p_exp, "\016"); /* expanded mode */
strcpy(prt.p_ul, "\033-1"); /* underline mode */
strcpy(prt.p_xbold, "\033F"); /* cancel emphasized mode */
strcpy(prt.p_xds, "\033H"); /* cancel double-strike mode */
strcpy(prt.p_xital, "\0335"); /* cancel italic mode */
strcpy(prt.p_xcmp, "\022"); /* cancel condensed mode */
strcpy(prt.p_xexp, "\024"); /* cancel expanded mode */
strcpy(prt.p_xul, "\033-0"); /* cancel underline mode */
return (0);
}
/*
* clrprnt -- clear printer options to default values
* (clears individual options to avoid the "paper creep"
* that occurs with repeated printer resets and to avoid
* changing the printer's notion of top-of-form position)
*/
int
clrprnt(fout)
FILE *fout;
{
fputs(prt.p_xbold, fout); /* cancel emphasized mode */
fputs(prt.p_xds, fout); /* cancel double-strike mode */
fputs(prt.p_xital, fout); /* cancel italic mode */
fputs(prt.p_xcmp, fout); /* cancel condensed mode */
fputs(prt.p_xexp, fout); /* cancel expanded mode */
fputs(prt.p_xul, fout); /* cancel underline mode */
} /* end clrprnt() */
/*
* setfont -- set the printing font to the type specified
* by the argument (may be a compound font specification)
*/
int
setfont(ftype, fout)
int ftype; /* font type specifier */
FILE *fout; /* output stream */
{
clrprnt(fout);
if ((ftype & CONDENSED) == CONDENSED)
if ((ftype & DOUBLE) == DOUBLE ||
(ftype & EMPHASIZED) == EMPHASIZED)
return FAILURE;
else if (*prt.p_cmp)
fputs(prt.p_cmp, fout);
if (*prt.p_ds && (ftype & DOUBLE) == DOUBLE)
fputs(prt.p_ds, fout);
if (*prt.p_bold && (ftype & EMPHASIZED) == EMPHASIZED)
fputs(prt.p_bold, fout);
if (*prt.p_exp && (ftype & EXPANDED) == EXPANDED)
fputs(prt.p_exp, fout);
if (*prt.p_ital && (ftype & ITALICS) == ITALICS)
fputs(prt.p_ital, fout);
if (*prt.p_ul && (ftype & UNDERLINE) == UNDERLINE)
fputs(prt.p_ul, fout);
return SUCCESS;
} /* end setfont() */
mx
───────────────────────────────────────────────────────────────────────────
/*
* mx -- control Epson MX-series printer
*/
#include <stdio.h>
#include <stdlib.h>
#include <local\std.h>
#include <local\printer.h>
extern PRINTER prt; /* printer data */
main(argc, argv)
int argc;
char **argv;
{
int ch, font;
BOOLEAN errflag; /* option error */
BOOLEAN clrflag; /* clear special fonts */
BOOLEAN rflag; /* hardware reset */
BOOLEAN tflag; /* top-of-form */
FILE *fout;
static char pgm[MAXNAME + 1] = { "mx" };
extern void getpname(char *, char *);
extern int getopt(int, char **, char *);
extern char *optarg;
extern int optind, opterr;
extern int setprnt();
extern int clrprnt(FILE *);
extern int setfont(int, FILE *);
if (_osmajor >= 3)
getpname(*argv, pgm);
if (setprnt() == -1) {
fprintf(stderr, "%s: Bad printer configuration\n", pgm);
exit(1);
}
/* interpret command line */
errflag = clrflag = rflag = tflag = FALSE;
font = 0;
fout = stdprn;
while ((ch = getopt(argc, argv, "bcdefino:prtu")) != EOF) {
switch (ch) {
case 'b':
/* set bold */
font |= EMPHASIZED;
break;
case 'c':
/* set compressed */
font |= CONDENSED;
break;
case 'd':
/* set double strike */
font |= DOUBLE;
break;
case 'e':
/* set double strike */
font |= EXPANDED;
break;
case 'i':
/* set italic */
font |= ITALICS;
break;
case 'n':
/* set normal (clear all special fonts) */
clrflag = TRUE;
break;
case 'o':
/* use specified output stream */
if ((fout = fopen(optarg, "w")) == NULL)
fatal(pgm, "cannot open output stream");
break;
case 'p':
/* preview control strings on stdout */
fout = stdout;
break;
case 'r':
/* hardware reset */
rflag = TRUE;
break;
case 't':
/* top of form */
tflag = TRUE;
break;
case 'u':
/* set underline */
font |= UNDERLINE;
break;
case '?':
/* unknown option */
errflag = TRUE;
break;
}
}
/* report errors, if any */
if (errflag == TRUE || argc == 1) {
fprintf(stderr, "Usage: %s -option\n", pgm);
fprintf(stderr,
"b=bold, c=compressed, d=double strike,
e=expanded\n");
fprintf(stderr,
"i=italic, n=normal, o file=output to file\n");
fprintf(stderr,
"p=preview, r=reset, t=top-of-form,
u=underline\n");
exit(2);
}
/* do hardware reset and formfeed first */
if (rflag == TRUE)
fputs(prt.p_init, fout);
else if (tflag == TRUE)
fputc('\f', fout);
/* clear or set the aggregate font */
if (clrflag == TRUE)
clrprnt(fout);
else if (setfont(font, fout) == FAILURE) {
fprintf(stderr, "%s: Bad font spec\n", pgm);
exit(3);
}
exit(0);
}
prtstr
───────────────────────────────────────────────────────────────────────────
/*
* prtstr -- send text string(s) to standard printer
*/
#include <stdio.h>
#include <stdlib.h>
#include <local\std.h>
main(argc, argv)
int argc;
char **argv;
{
int ch;
BOOLEAN errflag, lineflag;
static char pgm[MAXNAME + 1] = { "prtstr" };
FILE *fout;
extern char *getpname(char *, char *);
extern int getopt(int, char **, char *);
extern int optind, opterr;
extern char *optarg;
if (_osmajor >= 3)
getpname(*argv, pgm);
errflag = FALSE; /* process options, if any */
lineflag = TRUE;
fout = stdprn;
while ((ch = getopt(argc, argv, "np")) != EOF)
switch (ch) {
case 'n': /* don't emit the trailing newline */
lineflag = FALSE;
break;
case 'p': /* preview on stdout */
fout = stdout;
break;
case '?': /* bad option */
errflag = TRUE;
break;
}
if (errflag == TRUE) {
fprintf(stderr, "Usage: %s [-np] [string...]\n", pgm);
exit(1);
}
/* print the string(s) */
argc -= optind;
argv += optind;
while (argc-- > 1 ) {
fputs(*argv++, fout);
fputc(' ', fout);
}
fputs(*argv++, fout);
if (lineflag == TRUE)
fputc(' ', fout);
if (lineflag == TRUE)
fputc('\n', fout);
exit(0);
}
sample.bat
───────────────────────────────────────────────────────────────────────────
echo off
mx -n
prtstr This is normal text.
mx -b
prtstr This is bold (emphasized) text.
mx -n
prtstr We can mix fonts, too.
mx -be
prtstr This is bold and expanded together.
mx -n
prtstr -n We can mix fonts in a line also:
mx -b
prtstr -n " bold"
mx -i
prtstr -n " and "
mx -e
prtstr expanded.
mx -u
prtstr This is underlined text...
mx -i
prtstr ... this is italicized.
mx -n
prtstr And finally back to normal type.
select
───────────────────────────────────────────────────────────────────────────
/*
* select -- functions to create a selection table and
* to determine whether an item is an entry in the table
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <local\std.h>
#define NRANGES 10
#define NDIGITS 5
struct slist_st {
long int s_min;
long int s_max;
} Slist[NRANGES + 1];
long Highest = 0;
/*
* mkslist -- create the selection lookup table
*/
int
mkslist(list)
char *list;
{
int i;
char *listp, *s;
long tmp;
static long save_range();
if (*list == '\0') { /* fill in table of selected items */
Slist[0].s_min = 0; /* if no list, select all */
Slist[0].s_max = Highest = BIGGEST;
Slist[1].s_min = -1;
}
else {
listp = list;
for (i = 0; i < NRANGES; ++i) {
if ((s = strtok(listp, ", \t")) == NULL)
break;
if ((tmp = save_range(i, s)) > Highest)
Highest = tmp;
listp = NULL;
}
Slist[i].s_min = -1;
}
return (0);
} /* end mkslist() */
/*
* selected -- return non-zero value if the number
* argument is a member of the selection list
*/
int
selected(n)
long n;
{
int i;
/* look for converted number in selection list */
for (i = 0; Slist[i].s_min != -1; ++i)
if (n >= Slist[i].s_min && n <= Slist[i].s_max)
return (1);
return (0);
} /* end selected() */
/*
* save_range -- convert a string number spec to a
* numeric range in the selection table and return
* the highest number in the range
*/
static long
save_range(n, s)
int n;
char *s;
{
int radix = 10;
char *cp, num[NDIGITS + 1];
/* get the first (and possibly only) number */
cp = num;
while (*s != '\0' && *s != '-')
*cp++ = *s++;
*cp = '\0';
Slist[n].s_min = atol(num);
if (*s == '\0')
/* pretty narrow range, huh? */
return (Slist[n].s_max = Slist[n].s_min);
/* get the second number */
if (*++s == '\0')
/* unspecified top end of range */
Slist[n].s_max = BIGGEST;
else {
cp = num;
while (*s != '\0')
*cp++ = *s++;
*cp = '\0';
Slist[n].s_max = atol(num);
}
return (Slist[n].s_max);
} /* end save_range() */
tstsel
───────────────────────────────────────────────────────────────────────────
/*
* tstsel -- test driver for the "select" functions
*/
#include <stdio.h>
#include <local\std.h>
#define MAXTEST 20
#define NRANGES 10
#define NDIGITS 5
extern struct slist_st {
long int s_min;
long int s_max;
} Slist[NRANGES + 1];
main (argc, argv)
int argc;
char **argv;
{
int i;
extern int mkslist(char *);
extern int selected(unsigned int);
static void showlist();
if (argc != 2) {
fprintf(stderr, "Usage: tstsel list\n");
exit(1);
}
printf("argv[1] = %s\n", argv[1]);
mkslist(argv[1]);
showlist();
for (i = 0; i < MAXTEST; ++i)
printf("%2d -> %s\n", i, selected(i) ? "YES" : "NO");
exit(0);
}
/*
* showlist -- display the contents of the select list
*/
static void
showlist()
{
int i;
/* scan the selection list and display values */
for (i = 0; i <= NRANGES; ++i)
printf("%2d %5ld %5ld\n", i, Slist[i].s_min,
Slist[i].s_max);
}
Sample output from tstsel
───────────────────────────────────────────────────────────────────────────
argv[1] = 1,2,3,5-10
0 1 1
1 2 2
2 3 3
3 5 10
4 -1 0
5 0 0
6 0 0
7 0 0
8 0 0
9 0 0
10 0 0
0 -> NO
1 -> YES
2 -> YES
3 -> YES
4 -> NO
5 -> YES
6 -> YES
7 -> YES
8 -> YES
9 -> YES
10 -> YES
11 -> NO
12 -> NO
13 -> NO
14 -> NO
15 -> NO
16 -> NO
17 -> NO
18 -> NO
19 -> NO
tabs
───────────────────────────────────────────────────────────────────────────
/*
* tabs -- a group of cooperating functions that set
* and report the settings of "tabstops"
*/
#include <local\std.h>
static char Tabstops[MAXLINE];
/*
* fixtabs -- set up fixed-interval tabstops
*/
void
fixtabs(interval)
register int interval;
{
register int i;
for (i = 0; i < MAXLINE; i++)
Tabstops[i] = (i % interval == 0) ? 1 : 0;
} /* end fixtabs() */
/*
* vartabs -- set up variable tabstops from an array
* integers terminated by a -1 entry
*/
void
vartabs(list)
int *list;
{
register int i;
/* initialize the tabstop array */
for (i = 0; i < MAXLINE; ++i)
Tabstops[i] = 0;
/* set user-specified tabstops */
while (*list != -1)
Tabstops[*++list] = 1;
} /* end vartabs() */
/*
* tabstop -- return non-zero if col is a tabstop
*/
int
tabstop(col)
register int col;
{
return (col >= MAXLINE ? 1 : Tabstops[col]);
} /* end tabstop() */
showtabs
───────────────────────────────────────────────────────────────────────────
/*
* showtabs -- graphically display tabstop settings
*/
#include <stdio.h>
#include <stdlib.h>
#include <local\std.h>
#define MAXCOL 80
#define TABWIDTH 8
extern long Highest;
main(argc, argv)
int argc;
char *argv[];
{
int ch, i;
int interval, tablist[MAXLINE + 1], *p;
char *tabstr;
BOOLEAN errflag, fflag, vflag;
static char pgm[MAXNAME + 1] = { "showtabs" };
extern char *getpname(char *, char *);
extern int getopt(int, char **, char *);
extern char *optarg;
extern int optind, opterr;
extern int mkslist(char *);
extern int selected(long);
extern void fixtabs(int);
extern void vartabs(int *);
extern int tabstop(int);
if (_osmajor >= 3)
getpname(*argv, pgm);
/* process command-line options */
errflag = fflag = vflag = FALSE;
interval = 0;
while ((ch = getopt(argc, argv, "f:v:")) != EOF) {
switch (ch) {
case 'f':
/* used fixed tabbing interval */
if (vflag == FALSE) {
fflag = TRUE;
interval = atoi(optarg);
}
break;
case 'v':
/* use list of tabs */
if (fflag == FALSE) {
vflag = TRUE;
strcpy(tabstr, optarg);
}
break;
case '?':
errflag = TRUE;
break;
}
}
if (errflag == TRUE) {
fprintf(stderr, "Usage: %s [-f interval |
-v tablist]\n", pgm);
exit(2);
}
/* set the tabstops */
if (vflag == TRUE) {
/* user-supplied variable tab list */
mkslist(tabstr);
p = tablist;
for (i = 0; i < MAXLINE && i < Highest; ++i)
*p++ = selected((long)i + 1) ? i : 0;
*p = -1; /* terminate the list */
vartabs(tablist);
}
else if (fflag == TRUE)
/* user-supplied fixed tabbing interval */
fixtabs(interval);
else
/* hardware default tabbing interval */
fixtabs(TABWIDTH);
/* display current tabs settings */
for (i = 0; i < MAXCOL; ++i)
if (tabstop(i))
fputc('T', stdout);
else if ((i + 1) % 10 == 0)
fputc('+', stdout);
else
fputc('-', stdout);
fputc('\n', stdout);
exit(0);
}
touch
───────────────────────────────────────────────────────────────────────────
/*
* touch -- update modification time of file(s)
*/
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <sys\types.h>
#include <sys\stat.h>
#include <sys\utime.h>
#include <io.h>
#include <errno.h>
#include <local\std.h>
/* error return -- big enough not to be mistaken for a bad file count */
#define ERR 0x7FFF
main(argc, argv)
int argc;
char *argv[];
{
int ch;
int i;
int badcount; /* # of files that can't be updated */
struct stat statbuf; /* buffer for stat results */
BOOLEAN errflag, /* error flag */
cflag, /* creation flag */
vflag; /* verbose flag */
FILE *fp;
static char pgm[MAXNAME + 1] = { "touch" };
extern int getopt(int, char **, char *);
extern int optind, opterr;
extern char *optarg;
extern void getpname(char *, char *);
static void usage(char *);
/* get program name from DOS (version 3.00 and later) */
if (_osmajor >= 3)
getpname(argv[0], pgm);
/* process optional arguments first */
errflag = cflag = vflag = FALSE;
badcount = 0;
while ((ch = getopt(argc, argv, "cv")) != EOF)
switch (ch) {
case 'c':
/* don't create files */
cflag = TRUE;
break;
case 'v':
/* verbose -- report activity */
vflag = TRUE;
break;
case '?':
errflag = TRUE;
break;
}
argc -= optind;
argv += optind;
/* check for errors including no file names */
if (errflag == TRUE || argc <= 0) {
usage(pgm);
exit(ERR);
}
/* update modification times of files */
for (; argc-- > 0; ++argv) {
if (stat(*argv, &statbuf) == -1) {
/* file doesn't exist */
if (cflag == TRUE) {
/* don't create it */
++badcount;
continue;
}
else if ((fp = fopen(*argv, "w")) == NULL) {
fprintf(stderr, "%s: Cannot create %s\n",
pgm, *argv);
++badcount;
continue;
}
else {
if (fclose(fp) == EOF) {
perror("Error closing file");
exit(ERR);
}
if (stat(*argv, &statbuf) == -1) {
fprintf(stderr,
"%s: Cannot stat %s\n",
pgm, *argv);
++badcount;
continue;
}
}
}
if (utime(*argv, NULL) == -1) {
++badcount;
perror("Error updating date/time stamp");
continue;
}
if (vflag == TRUE)
fprintf(stderr, "Touched file %s\n", *argv);
}
exit(badcount);
} /* end main() */
/*
* usage -- display an informative usage message
*/
static void
usage(pname)
char *pname;
{
fprintf(stderr, "Usage: %s [-cv] file ...\n", pname);
fprintf(stderr, "\t-c Do not create any files\n");
fprintf(stderr, "\t-v Verbose mode -- report activities\n");
} /* end usage() */
/*
* dummy functions to show how to save a little space
*/
_setenvp()
{
}
#ifndef DEBUG
_nullcheck()
{
}
#endif
tee
───────────────────────────────────────────────────────────────────────────
/*
* tee -- a "pipe fitter" for DOS
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <local\std.h>
main(argc, argv)
int argc;
char *argv[];
{
register int ch, n;
static char openmode[] = { "w" };
static char pgm[MAXPATH + 1] = { "tee" };
FILE *fp[_NFILE]; /* array of file pointers */
extern int getopt(int, char **, char *);
extern int optind, opterr;
extern char *optarg;
extern void getpname(char *, char *);
/* check for an alias */
if (_osmajor >= 3)
getpname(argv[0], pgm);
/* process command-line options, if any */
while ((ch = getopt(argc, argv, "a")) != EOF)
switch (ch) {
case 'a':
strcpy(openmode, "a");
break;
case '?':
break;
}
n = argc -= optind;
argv += optind;
/* check for errors */
if (argc > _NFILE) {
fprintf(stderr, "Too many files (max = %d)\n", _NFILE);
exit(1);
}
/* open the output file(s) */
for (n = 0; n < argc; ++n) {
if ((fp[n] = fopen(argv[n], openmode)) == NULL) {
fprintf(stderr, "Cannot open %s\n", argv[n]);
continue;
}
}
/* copy input to stdout plus opened file(s) */
while ((ch = getchar()) != EOF) {
putchar(ch);
for (n = 0; n < argc; ++n)
if (fp[n] != NULL)
fputc(ch, fp[n]);
}
/* close file(s) */
if (fcloseall() == -1) {
fprintf(stderr, "Error closing a file\n");
exit(2);
}
exit(0);
}
pwd
───────────────────────────────────────────────────────────────────────────
/*
* pwd -- print (display actually) the current directory pathname
*/
#include <stdio.h>
#include <direct.h>
#include <local\std.h>
main()
{
char *path;
if ((path = getcwd(NULL, MAXPATH)) == NULL) {
perror("Error getting current directory");
exit(1);
}
printf("%s\n", path);
exit(0);
}
_setargv()
{
}
_setenvp()
{
}
_nullcheck()
{
}
rm
───────────────────────────────────────────────────────────────────────────
/*
* rm -- remove file(s)
*/
#include <stdio.h>
#include <stdlib.h>
#include <sys\types.h>
#include <sys\stat.h>
#include <ctype.h>
#include <io.h>
#include <local\std.h>
main(argc, argv)
int argc;
char *argv[];
{
int ch;
BOOLEAN errflag,
iflag;
static char pgm[MAXNAME + 1] = { "rm" };
extern void getpname(char *, char *);
static void do_rm(char *, char *, BOOLEAN);
extern int getopt(int, char **, char *);
extern int optind, opterr;
extern char *optarg;
/* get program name from DOS (version 3.00 and later) */
if (_osmajor >= 3)
getpname(*argv, pgm);
/* process optional arguments first */
errflag = iflag = FALSE;
while ((ch = getopt(argc, argv, "i")) != EOF)
switch (ch) {
case 'i':
/* interactive -- requires confirmation */
iflag = TRUE;
break;
case '?':
/* say what? */
errflag = TRUE;
break;
}
argc -= optind;
argv += optind;
if (argc <= 0 || errflag == TRUE) {
fprintf(stderr, "%s [-i] file(s)\n", pgm);
exit(1);
}
/* process remaining arguments */
for (; argc-- > 0; ++argv)
do_rm(pgm, *argv, iflag);
exit(0);
} /* end main() */
/*
* do_rm -- remove a file
*/
static void
do_rm(pname, fname, iflag)
char *pname, *fname;
BOOLEAN iflag;
{
int result = 0;
struct stat statbuf;
static BOOLEAN affirm();
if (iflag == TRUE) {
fprintf(stderr, "%s (y/n): ", fname);
if (affirm() == FALSE)
return;
}
if ((result = unlink(fname)) == -1) {
fprintf(stderr, "%s: ", pname);
perror(fname);
}
return;
}
/*
* affirm -- return TRUE if the first character of the
* user's response is 'y' or FALSE otherwise
*/
#define MAXSTR 64
static BOOLEAN
affirm()
{
char line[MAXSTR + 1];
char *response;
response = fgets(line, MAXSTR, stdin);
return (tolower(*response) == 'y' ? TRUE : FALSE);
}
ls.h
───────────────────────────────────────────────────────────────────────────
/*
* ls.h -- header file for ls program
*/
/* structure definition for output buffer elements */
struct OUTBUF {
unsigned short o_mode; /* file mode (attributes) */
long o_size; /* file size in bytes */
unsigned int o_date; /* file modification date */
unsigned int o_time; /* file modification time */
char *o_name; /* DOS filespec */
};
/* constants for DOS file-matching routines */
#define FILESPEC 13 /* maximum filespec + NUL */
#define RNBYTES 21 /* bytes reserved for next_fm() calls */
/* file modes (attributes) */
#define READONLY 0x0001
#define HIDDEN 0x0002
#define SYSTEM 0x0004
#define VOLUME 0x0008
#define SUBDIR 0x0010
#define ARCHIVE 0x0020
/* structure definition for DOS file-matching routines */
struct DTA {
unsigned char d_reserved[RNBYTES]; /* buffer for next_fm */
unsigned char d_attr; /* file attribute (type) byte */
unsigned d_mtime; /* time of last modification */
unsigned d_mdate; /* date of last modification */
long d_fsize; /* file size in bytes */
char d_fname[FILESPEC]; /* file spec (filename.ext + NUL) */
};
ls
───────────────────────────────────────────────────────────────────────────
/*
* ls -- display a directory listing
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <memory.h>
#include <dos.h>
#include <direct.h>
#include <signal.h>
#include <search.h>
#include <local\std.h>
#include <local\doslib.h>
#include "ls.h"
/* allocation quantities */
#define N_FILES 256
#define N_DIRS 16
/* global data */
int Multicol = 0;
int Filetype = 0;
int Hidden = 0;
int Longlist = 0;
int Reverse = 0;
int Modtime = 0;
main(argc, argv)
int argc;
char *argv[];
{
int ch, i;
int errflag; /* error flag */
char *ep; /* environment pointer */
int status = 0; /* return status value */
int fileattr; /* file attribute number */
struct DTA buf; /* private disk buffer */
char path[MAXPATH + 1]; /* working pathname */
struct OUTBUF *fp, *fq; /* pointers to file array */
char **dp, **dq; /* pointer to directory pointer array */
int fbc = 1; /* file memory block allocation count */
int dbc = 1; /* directory memory block allocation */
/* count */
int nfiles; /* number of file elements */
int ndirs; /* number of directory elements */
static char pgm[MAXNAME + 1] = { "ls" };
/* function prototypes */
void getpname(char *, char *);
extern int getopt(int, char **, char *);
extern int optind, opterr;
extern char *optarg;
extern char *drvpath(char *);
extern void fatal(char *, char *, int);
extern void setdta(char *);
extern int first_fm(char *, int);
extern int next_fm();
extern int ls_fcomp(struct OUTBUF *, struct OUTBUF *);
extern int ls_dcomp(char *, char *);
extern int ls_single(struct OUTBUF *, int);
extern int ls_multi(struct OUTBUF *, int);
extern int ls_dirx(char *, char *);
int bailout();
/* guarantee that needed DOS services are available */
if (_osmajor < 2)
fatal(pgm, "ls requires DOS 2.00 or later", 1);
/* get program name from DOS (version 3.00 and later) */
if (_osmajor >= 3)
getpname(*argv, pgm);
/* useful aliases (DOS version 3.00 and later) */
if (strcmp(pgm, "lc") == 0)
++Multicol;
if (strcmp(pgm, "lf") == 0) {
++Multicol;
++Filetype;
}
/* prepare for emergencies */
if (signal(SIGINT, bailout) == (int(*)())-1) {
perror("Can't set SIGINT");
exit(2);
}
/* process optional arguments first */
errflag = 0;
while ((ch = getopt(argc, argv, "aCFlrt")) != EOF)
switch (ch) {
case 'a':
/* all files (hidden, system, etc.) */
++Hidden;
break;
case 'C':
++Multicol;
break;
case 'F':
/* show file types (/=directory, *=executable) */
++Filetype;
break;
case 'l':
/* long list (overrides multicolumn) */
++Longlist;
break;
case 'r':
/* reverse sort */
++Reverse;
break;
case 't':
/* sort by file modification time */
++Modtime;
break;
case '?':
errflag = TRUE;
break;
}
argc -= optind;
argv += optind;
/* check for command-line errors */
if (argc < 0 || errflag) {
fprintf(stderr, "Usage: %s [-aCFlrt] [pathname ...]", pgm);
exit(3);
}
/* allocate initial file and directory storage areas */
dp = dq = (char **)malloc(N_DIRS * sizeof (char *));
if (dp == NULL)
fatal(pgm, "Out of memory", 4);
fp = fq = (struct OUTBUF *)malloc(N_FILES *
sizeof (struct OUTBUF));
if (fp == NULL)
fatal(pgm, "Out of memory", 4);
nfiles = ndirs = 0;
/* use current directory if no args */
if (argc == 0) {
if (getcwd(path, MAXPATH) == NULL)
fatal(pgm, "Cannot get current directory", 5);
*dq = path;
ndirs = 1;
}
else {
/* use arguments as file and directory names */
for ( ; argc-- > 0; ++argv) {
strcpy(path, *argv);
if (path[0] == '\\') {
/* prepend default drive name */
memcpy(path + 2, path, strlen(path) + 1);
path[0] = 'a' + getdrive();
path[1] = ':';
}
if (path[1] == ':' && path[2] ==
'\0' && drvpath(path) == NULL) {
fprintf(stderr,
"%s: Cannot get drive path", pgm);
continue;
}
/* establish private disk transfer area */
setdta((char *)&buf);
/* set file attribute for search */
if (Hidden)
fileattr = SUBDIR | HIDDEN | SYSTEM |
READONLY;
else
fileattr = SUBDIR;
if (first_fm(path, fileattr) != 0 && path[3] !=
'\0') {
fprintf(stderr,
"%s -- No such file or
directory\n", path);
continue;
}
if ((buf.d_attr & SUBDIR) == SUBDIR || path[3] ==
'\0') {
/* path is a (sub)directory */
*dq = strdup(path);
if (++ndirs == dbc * N_DIRS) {
++dbc; /* increase space */
/* requirement */
dp = (char **)realloc(dp, dbc *
N_DIRS
* sizeof (char *));
if (dp == NULL)
fatal(pgm,
"Out of memory", 4);
dq = dp + dbc * N_DIRS;
}
else
++dq;
}
else {
fq->o_name = strdup(path);
fq->o_mode = buf.d_attr;
fq->o_date = buf.d_mdate;
fq->o_time = buf.d_mtime;
fq->o_size = buf.d_fsize;
if (++nfiles == fbc * N_FILES) {
++fbc;
fp = (struct OUTBUF *)realloc(fp,
fbc * N_FILES *
sizeof (struct OUTBUF));
if (fp == NULL)
fatal(pgm, "Out of memory",
4);
fq = fp + fbc * N_FILES;
}
else
++fq;
}
}
}
/* output file list, if any */
if (nfiles > 0) {
qsort(fp, nfiles, sizeof(struct OUTBUF), ls_fcomp);
if (Longlist)
ls_long(fp, nfiles);
else if (Multicol)
ls_multi(fp, nfiles);
else
ls_single(fp, nfiles);
putchar('\n');
}
free(fp);
/* output directory lists, if any */
if (ndirs == 1 && nfiles == 0) {
/* expand directory and output without header */
if (ls_dirx(pgm, *dp))
fprintf(stderr, "%s -- empty directory\n",
strlwr(*dp));
}
else if (ndirs > 0) {
/* expand each directory and output with headers */
dq = dp;
qsort(dp, ndirs, sizeof(char *), ls_dcomp);
while (ndirs-- > 0) {
fprintf(stdout, "%s:\n", strlwr(*dq));
if (ls_dirx(pgm, *dq++))
fprintf(stderr, "%s -- empty directory\n",
strlwr(*dq));
putchar('\n');
}
}
exit(0);
}
/*
* bailout -- optionally terminate upon interrupt
*/
int
bailout()
{
char ch;
signal(SIGINT, bailout);
printf("\nTerminate directory listing? ");
scanf("%1s", &ch);
if (ch == 'y' || ch == 'Y')
exit(1);
}
drvpath
───────────────────────────────────────────────────────────────────────────
/*
* drvpath -- convert a drive name to a full pathname
*/
#include <stdio.h>
#include <dos.h>
#include <string.h>
#include <ctype.h>
#include <local\doslib.h>
char *
drvpath(path)
char path[]; /* path string */
/* must be large enough to hold a full DOS path + NUL */
{
union REGS inregs, outregs;
static int drive(char);
/* patch root directory onto drive name */
strcat(path, "\\");
/* set current directory path for drive from DOS */
inregs.h.ah = GET_CUR_DIR;
inregs.h.dl = drive(path[0]); /* convert to drive number */
inregs.x.si = (unsigned)&path[3]; /* start of return string */
intdos(&inregs, &outregs);
return (outregs.x.cflag ? (char *)NULL : path);
}
static int
drive(dltr)
char dltr; /* drive letter */
{
/* 'A' (or 'a') => 1, 'B' (or 'b') => 2, etc. */
return (tolower(dltr) - 'a' + 1);
}
ls_fcomp
───────────────────────────────────────────────────────────────────────────
/*
* ls_fcomp -- file and directory comparison functions
*/
#include <string.h>
#include "ls.h"
extern int Modtime;
extern int Reverse;
/*
* ls_fcomp -- compare two "file" items
*/
int
ls_fcomp(s1, s2)
struct OUTBUF *s1, *s2;
{
int result;
if (Modtime) {
if ((result = s1->o_date - s2->o_date) == 0)
result = s1->o_time - s2->o_time;
}
else
result = strcmp(s1->o_name, s2->o_name);
return (Reverse ? -result : result);
} /* end_fcomp() */
/*
* dcomp -- compare two "directory" items
*/
int
ls_dcomp(s1, s2)
char *s1, *s2;
{
int result;
result = strcmp(s1, s2);
return (Reverse ? -result : result);
} /* end ls_dcomp() */
ls_list
───────────────────────────────────────────────────────────────────────────
/*
* ls_list -- list functions (long, single, multi) for ls
*/
#include <stdio.h>
#include <string.h>
#include "ls.h"
#define MAXCOL 80
#define MONTH_SHIFT 5
#define MONTH_MASK 0x0F
#define DAY_MASK 0x1F
#define YEAR_SHIFT 9
#define DOS_EPOCH 80
#define HOUR_SHIFT 11
#define HOUR_MASK 0x1F
#define MINUTE_SHIFT 5
#define MINUTE_MASK 0x3F
extern int Filetype;
/*
* ls_long -- list items in "long" format (mode time size name)
*/
int
ls_long(buf, nelem)
struct OUTBUF *buf;
int nelem;
{
int n = 0;
char modebuf[5];
static void modestr(unsigned short, char *);
while (nelem-- > 0) {
/* convert mode number to a string */
modestr(buf->o_mode, modebuf);
printf("%s ", modebuf);
/* display file size in bytes */
printf("%7ld ", buf->o_size);
/* convert date and time values to formatted presentation */
printf("%02d-%02d-%02d ", (buf->o_date >> MONTH_SHIFT) &
MONTH_MASK, buf->o_date & DAY_MASK, (buf->o_date >>
YEAR_SHIFT) + DOS_EPOCH);
printf("%02d:%02d ", (buf->o_time >> HOUR_SHIFT) &
HOUR_MASK, (buf->o_time >> MINUTE_SHIFT) &
MINUTE_MASK);
/* display filenames as lowercase strings */
printf("%s\n", strlwr(buf->o_name));
++buf;
++n;
}
/* tell caller how many entries were printed */
return (n);
} /* end ls_long() */
/*
* ls_single -- list items in a single column
*/
int
ls_single(buf, nelem)
struct OUTBUF *buf;
int nelem;
{
int n = 0;
while (nelem-- > 0) {
printf("%s", strlwr(buf->o_name));
if (Filetype && (buf->o_mode & SUBDIR) == SUBDIR)
putchar('\\');
putchar('\n');
++buf;
++n;
}
/* tell caller how many entries were printed */
return (n);
} /* end ls_single() */
/*
* ls_multi -- list items in multiple columns that
* vary in width and number based on longest item size
*/
int
ls_multi(buf, nelem)
struct OUTBUF *buf;
int nelem;
{
int i, j;
int errcount = 0;
struct OUTBUF *tmp; /* temporary buffer pointer */
struct OUTBUF *base; /* buffer pointer for multi-col output */
int n; /* number of items in list */
int len, maxlen; /* pathname lengths */
int ncols; /* number of columns to output */
int nlines; /* number of lines to output */
/*
* get length of longest pathname and calculate number
* of columns and lines (col width = maxlen + 1)
*/
tmp = buf;
n = 0;
maxlen = 0;
for (tmp = buf, n = 0; n < nelem; ++tmp, ++n)
if ((len = strlen(tmp->o_name)) > maxlen)
maxlen = len;
/*
* use width of screen - 1 to allow for newline at end of
* line and leave two spaces between entries (one for optional
* file type flag)
*/
ncols = (MAXCOL - 1) / (maxlen + 2);
nlines = n / ncols;
if (n % ncols)
++nlines;
/* output multi-column list */
base = buf;
for (i = 0; i < nlines; ++i) {
tmp = base;
for (j = 0; j < ncols; ++j) {
len = maxlen + 2;
len -= printf("%s", strlwr(tmp->o_name));
if (Filetype && (tmp->o_mode & SUBDIR) == SUBDIR) {
putchar('\\');
--len;
}
while (len-- > 0)
putchar(' ');
tmp += nlines;
if (tmp - buf >= nelem)
break;
}
putchar('\n');
++base;
}
return (errcount);
} /* end ls_multi() */
static void
modestr(mode, s)
unsigned short mode; /* file mode number */
char s[]; /* mode string buffer */
{
/* fill in the mode string to show what's set */
s[0] = (mode & SUBDIR) == SUBDIR ? 'd' : '-';
s[1] = (mode & HIDDEN) == HIDDEN ? 'h' : '-';
s[2] = (mode & SYSTEM) == SYSTEM ? 's' : '-';
s[3] = (mode & READONLY) == READONLY ? 'r' : '-';
s[4] = '\0';
} /* end modestr() */
ls_dirx
───────────────────────────────────────────────────────────────────────────
/*
* ls_dirx -- expand the contents of a directory using
* the DOS first/next matching file functions
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>
#include <dos.h>
#include <direct.h>
#include <signal.h>
#include <search.h>
#include <local\std.h>
#include <local\doslib.h>
#include "ls.h"
#define NFILES 1024
extern int Recursive;
extern int Longlist;
extern int Multicol;
extern int Hidden;
int
ls_dirx(pname, namep)
char *pname;
char *namep;
{
int status = 0; /* function return value */
int n; /* number of items found */
int fileattr; /* attributes of file-matching */
struct DTA buf; /* disk transfer area */
struct OUTBUF *bp, *bq; /* output buffer pointers */
char path[MAXPATH + 1]; /* working path string */
extern void setdta(char *);
extern int first_fm(char *, int);
extern int next_fm();
extern int ls_fcomp(struct OUTBUF *, struct OUTBUF *);
extern char last_ch(char *);
/* allocate a buffer */
bp = bq = (struct OUTBUF *)malloc(NFILES * sizeof(struct OUTBUF));
if (bp == NULL)
fatal(pname, "Out of memory");
/* form name for directory search */
strcpy(path, namep);
if (last_ch(path) != '\\')
strcat(path, "\\");
strcat(path, "*.*");
/* list the files found */
n = 0;
/* establish a private DTA */
setdta((char *)&buf);
/* select file attributes */
if (Hidden)
fileattr = SUBDIR | HIDDEN | SYSTEM | READONLY;
else
fileattr = SUBDIR;
if (first_fm(path, fileattr) == 0) {
/* add file or directory to the buffer */
do {
if (!Hidden && buf.d_fname[0] == '.')
continue;
bq->o_name = strdup(buf.d_fname);
bq->o_mode = buf.d_attr;
bq->o_size = buf.d_fsize;
bq->o_date = buf.d_mdate;
bq->o_time = buf.d_mtime;
++bq;
++n;
setdta((char *)&buf); /* reset to our DTA */
} while (next_fm() == 0);
if (n > 0) {
/* got some -- sort and list them */
qsort(bp, n, sizeof(struct OUTBUF), ls_fcomp);
if (Longlist)
ls_long(bp, n);
else if (Multicol)
ls_multi(bp, n);
else
ls_single(bp, n);
}
}
else
++status;
free(bp);
return (status);
}
first_fm
───────────────────────────────────────────────────────────────────────────
/*
* first_fm - find first file match in work directory
*/
#include <dos.h>
#include <local\doslib.h>
int
first_fm(path, fa)
char *path; /* pathname of directory */
int fa; /* attribute(s) of file to match */
{
union REGS inregs, outregs;
/* find first matching file */
inregs.h.ah = FIND_FIRST;
inregs.x.cx = fa;
inregs.x.dx = (unsigned int)path;
(void)intdos(&inregs, &outregs);
return (outregs.x.cflag);
}
/*
* next_fm - find next file match in work directory
*/
#include <dos.h>
#include <local\doslib.h>
int
next_fm()
{
union REGS inregs, outregs;
/* find next matching file */
inregs.h.ah = FIND_NEXT;
(void)intdos(&inregs, &outregs);
return (outregs.x.cflag);
}
makefile for the LS program
───────────────────────────────────────────────────────────────────────────
# makefile for the LS program
LIB=c:\lib
LLIB=c:\lib\local
first_fm.obj: first_fm.c
msc $*;
lib $(LLIB)\dos -+ $*;
next_fm.obj: next_fm.c
msc $*;
lib $(LLIB)\dos -+ $*;
setdta.obj: setdta.c
msc $*;
lib $(LLIB)\dos -+ $*;
getdrive.obj: getdrive.c
msc $*;
lib $(LLIB)\dos -+ $*;
drvpath.obj: drvpath.c
msc $*;
lib $(LLIB)\dos -+ $*;
ls_fcomp.obj: ls_fcomp.c ls.h
msc $*;
ls_dirx.obj: ls_dirx.c ls.h
msc $*;
ls_list.obj: ls_list.c ls.h
msc $*;
ls.obj: ls.c ls.h
msc $*;
ls.exe: ls.obj ls_dirx.obj ls_fcomp.obj ls_list.obj \
$(LLIB)\util.lib $(LLIB)\dos.lib
link $* ls_dirx ls_fcomp ls_list $(LIB)\ssetargv, $*,, $(LLIB)\util
$(LLIB)\dos;
pr_help
───────────────────────────────────────────────────────────────────────────
/*
* pr_help -- display an abbreviated manual page
*/
#include <stdio.h>
#include <local\std.h>
void
pr_help(pname)
char *pname;
{
static char *m_str[] = {
"The following options may be used singly or in "combination:",
"-e\t set Epson-compatible mode",
"-f\t use formfeed to eject a page (default is newlines)",
"-g\t use generic printer mode",
"-h hdr\t use specified header instead of filename",
"-l len\t set page length in lines (default = 66)",
"-n\t enable line-numbering (default = off)",
"-o cols\t offset from left edge in columns (default = 5)",
"-p\t preview output on screen (may be redirected)",
"-s list\t print only selected pages",
"-w cols\t line width in columns (default = 80)"
};
int i, n = sizeof (m_str)/ sizeof (char *);
fprintf(stderr, "Usage: %s [options] file...\n\n", pname);
for (i = 0; i < n; ++i)
fprintf(stderr, "%s\n", m_str[i]);
return;
}
print.h
───────────────────────────────────────────────────────────────────────────
/*
* print.h -- header information for print programs
*/
/* default printing format information */
#define BOTTOM 3 /* blank lines at bottom of page */
#define MARGIN 5 /* default margin width in columns */
#define MAXPCOL 80 /* maximum number of printed columns per line */
#define MAXPSTR 64 /* maximum characters in a string variable */
#define PAGELEN 66 /* default page length (at 6 lines per inch) */
#define LPI 6 /* default lines per inch */
#define TABSPEC 8 /* default tab separation */
#define TOP1 2 /* blank lines above header line */
#define TOP2 2 /* blank lines below header line */
/* primary data structure for printer programs */
typedef struct pr_st {
/* numeric variables */
int p_top1; /* lines above header */
int p_top2; /* lines below header */
int p_btm; /* lines in footer */
int p_wid; /* width in columns */
int p_lmarg; /* left margin */
int p_rmarg; /* right margin */
int p_len; /* lines per page */
int p_lpi; /* lines per inch */
int p_lnum; /* nonzero turns line-numbering on */
int p_mode; /* zero for generic printer */
int p_font; /* font number when in nongeneric mode */
int p_ff; /* nonzero uses formfeed to eject page */
int p_tabint; /* tab interval */
/* string variables */
char p_hdr[MAXPSTR];
char p_dest[MAXPSTR];
} PRINT;
pr
───────────────────────────────────────────────────────────────────────────
/*
* pr -- file printer
*/
#include <stdio.h>
#include <stdlib.h>
#include <dos.h>
#include <local\std.h>
#include "print.h"
char Pagelist[MAXLINE];
main(argc, argv)
int argc;
char **argv;
{
int ch;
BOOLEAN errflag;
extern PRINT pcnf;
static char pgm[MAXNAME + 1] = { "pr" };
extern char getpname(char *, char *);
extern int getopt(int, char **, char *);
extern char *optarg;
extern int optind, opterr;
extern int pr_gcnf(char *);
extern pr_file(char *, int, char **);
extern void pr_help(char *);
extern void fixtabs(int);
extern int setprnt();
if (_osmajor >= 3)
getpname(*argv, pgm);
/* do configuration */
if (pr_gcnf(pgm) != 0) {
fprintf(stderr, "%s: Configuration error", pgm);
exit(2);
}
if (setprnt() == -1) {
fprintf(stderr, "%s: Bad printer configuration\n", pgm);
exit(1);
}
fixtabs(pcnf.p_tabint);
/* process command-line arguments */
while ((ch = getopt(argc, argv, "efgh:l:no:ps:w:")) != EOF) {
switch (ch) {
case 'e':
/* force "Epson-compatible " printer mode */
pcnf.p_mode = 1;
break;
case 'f':
/* use formfeed to eject a page */
pcnf.p_ff = 1;
break;
case 'g':
/* force "generic" printer mode */
pcnf.p_mode = 0;
break;
case 'h':
/* use specified header */
strcpy(pcnf.p_hdr, optarg);
break;
case 'l':
/* set lines per page */
pcnf.p_len = atoi(optarg);
break;
case 'n':
/* enable line-numbering */
pcnf.p_lnum = 1;
break;
case 'o':
/* set left margin */
pcnf.p_lmarg = atoi(optarg);
break;
case 'p':
/* preview output on screen */
strcpy(pcnf.p_dest, "");
break;
case 's':
/* output selected pages */
strcpy(Pagelist, optarg);
break;
case 'w':
/* set page width in columns */
pcnf.p_wid = atoi(optarg);
break;
case '?':
/* unknown option */
errflag = TRUE;
break;
}
}
if (errflag == TRUE) {
pr_help(pgm);
exit(3);
}
/* print the files */
pr_file(pgm, argc - optind, argv += optind);
exit(0);
}
pr_gcnf
───────────────────────────────────────────────────────────────────────────
/*
* pr_gcnf -- get configuration for pr program
*/
#include <stdio.h>
#include <string.h>
#include <local\std.h>
#include <local\printer.h>
#include "print.h"
/* expected number of configuration items */
#define N_NBR 12
PRINT pcnf;
int
pr_gcnf(pname)
char *pname;
{
char line[MAXLINE];
char *s;
int cnf[N_NBR];
int n, errcount, good;
FILE *fp, *fconfig(char *, char *);
/* get configuration file values, if any */
n = good = errcount = 0;
if ((fp = fconfig("CONFIG", "pr.cnf")) != NULL) {
while (n < N_NBR && (s = fgets(line, MAXLINE, fp)) != NULL) {
cnf[n] = atoi(s);
++n;
}
if ((s = fgets(line, MAXLINE, fp)) == NULL)
++errcount;
else
strcpy(pcnf.p_dest, strtok(line, " \t\n"));
if (n != N_NBR)
++errcount;
if (errcount == 0)
good = 1;
if (fclose(fp) == -1)
fatal(pname, "cannot close config file", 1);
}
/* use config data if good; use defaults otherwise */
pcnf.p_top1 = good ? cnf[0]: TOP1;
pcnf.p_top2 = good ? cnf[1] : TOP2;
pcnf.p_btm = good ? cnf[2] : BOTTOM;
pcnf.p_wid = good ? cnf[3] : MAXPCOL;
pcnf.p_lmarg = good ? cnf[4] : MARGIN;
pcnf.p_rmarg = good ? cnf[5] : MARGIN;
pcnf.p_len = good ? cnf[6] : PAGELEN;
pcnf.p_lpi = good ? cnf[7] : LPI;
pcnf.p_mode = good ? cnf[8] : 0;
pcnf.p_lnum = good ? cnf[9] : 0;
pcnf.p_ff = good ? cnf[10] : 0;
pcnf.p_tabint = good ? cnf[11] : TABSPEC;
if (!good)
strcpy(pcnf.p_dest, "PRN");
if (pcnf.p_mode == 1)
pcnf.p_font = CONDENSED;
strcpy(pcnf.p_hdr, "");
return (errcount);
}
pr_file
───────────────────────────────────────────────────────────────────────────
/*
* pr_file -- process each filename or standard input
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <local\std.h>
#include "print.h"
int
pr_file(pname, ac, av)
char *pname;
int ac;
char **av;
{
int ch, errcount = 0;
FILE *fin, *fout;
extern PRINT pcnf;
extern void fatal(char*, char*, int);
extern int pr_cpy(FILE *, FILE *, char *);
/* open output stream only if not already open */
if (*pcnf.p_dest == '\0' || strcmp(pcnf.p_dest, "CON") == 0)
fout = stdout;
else if (strcmp(pcnf.p_dest, "PRN") == 0)
fout = stdprn;
else if (strcmp(pcnf.p_dest, "AUX") == 0)
fout = stdaux;
else
if ((fout = fopen(pcnf.p_dest, "w")) == NULL)
fatal(pname, "Error open destination stream", 1);
/* prepare input stream */
if (ac == 0)
pr_cpy(stdin, fout, "");
else {
for (; ac > 0; --ac, ++av) {
if ((fin = fopen(*av, "r")) == NULL) {
fprintf(stderr, "%s: Error opening %s\n",
pname, *av);
continue;
}
if (pr_cpy(fin, fout, *av) == -1) {
fprintf(stderr, "%s: Cannot stat %s",
pname, *av);
continue;
}
if (fclose(fin) == EOF)
fatal(pname, "Error closing input file", 1);
}
}
return (errcount);
}
pr_cpy
───────────────────────────────────────────────────────────────────────────
/*
* pr_cpy -- copy input stream to output stream
*/
#include <stdio.h>
#include <string.h>
#include <local\std.h>
#include <local\printer.h>
#include <sys\types.h>
#include <sys\stat.h>
#include <time.h>
#include "print.h"
extern PRINT pcnf;
extern char Pagelist[MAXLINE];
extern long Highest;
int
pr_cpy(fin, fout, fname)
FILE *fin;
FILE *fout;
char *fname;
{
int errcount = 0;
unsigned int p_line; /* page-relative line number */
long f_line; /* file-relative line number */
long f_page; /* file-relative page number */
int lnlen; /* line length */
char line[MAXLINE]; /* input line buffer */
struct stat tbuf; /* file information */
long ltime; /* date and time */
FILE *fnull, *fx; /* additional output file pointers */
extern void mkslist(char *); /* make a selection list */
extern int selected(long); /* is item in the list? */
extern int spaces(int, FILE *); /* emit string of spaces */
extern int setfont(int, FILE *);/* set printer font type */
extern int clrprnt(FILE *); /* clear special fonts */
extern int lines(int, FILE *); /* emit string of blank lines */
static int fit(int, int); /* will line fit on page? */
extern int pr_line(char *, FILE *, unsigned int);
/* install page selection list, if any */
if (Pagelist[0] != '\0') {
/* open the NUL device for dumping output */
if ((fnull = fopen("NUL", "w")) == NULL) {
perror("Error opening NUL device");
exit(1);
}
mkslist(Pagelist);
}
else
Highest = BIGGEST;
/* get date and time stamp */
if (*fname == '\0')
/* using stdin -- use today's date and time */
ltime = time(NULL);
else {
if (stat(fname, &tbuf) == -1)
return (-1);
/* use file's modification time */
ltime = tbuf.st_mtime;
}
p_line = 0;
f_line = 1;
f_page = 1;
while ((lnlen = pr_getln(line, MAXLINE, fin)) > 0 ) {
/* if formfeed or no room for line, eject page */
if (line[0] == '\f' || !fit(lnlen, p_line)) {
/* to top of next page */
if (pcnf.p_ff == 0)
lines(pcnf.p_len - p_line, fx);
else
fputc('\f', fx);
p_line = 0;
}
/* if at top of page, print the header */
if (p_line == 0) {
if (f_page > Highest)
break;
fx = selected(f_page) ? fout : fnull;
p_line += lines(pcnf.p_top1, fx);
if (pcnf.p_mode != 0)
setfont(EMPHASIZED, fx);
spaces(pcnf.p_lmarg, fx);
if (*pcnf.p_hdr != '\0')
fprintf(fx, "%s ", pcnf.p_hdr);
else if (*fname != '\0')
fprintf(fx, "%s ", strupr(fname));
fprintf(fx, "Page %u ", f_page++);
fputs(ctime(<ime), fx);
++p_line;
if (pcnf.p_mode != 0)
setfont(pcnf.p_font, fx);
p_line += lines(pcnf.p_top2, fx);
}
/* OK to output the line */
if (line[0] != '\f')
p_line += pr_line(line, fx, f_line++);
}
if (ferror(fin) != 0)
++errcount;
if (p_line > 0 && p_line < pcnf.p_len)
if (pcnf.p_ff == 0)
lines(pcnf.p_len - p_line, fx);
else
fputc('\f', fx);
if (pcnf.p_mode != 0)
clrprnt(fx);
return (errcount);
}
/*
* fit -- return nonzero value if enough physical
* lines are available on the current page to take
* the current logical line of text
*/
#define NFLDWIDTH 8 /* width of number field */
static int
fit(len, ln)
int len, ln;
{
int need, left; /* physical lines */
int cols; /* columns of actual output */
int lw; /* displayable line width */
/* total need (columns -> physical lines) */
cols = len + (pcnf.p_lnum > 0 ? NFLDWIDTH : 0);
lw = pcnf.p_wid - pcnf.p_lmarg - pcnf.p_rmarg;
need = 1 + cols / lw;
/* lines remaining on page */
left = pcnf.p_len - ln - pcnf.p_btm;
return (need <= left ? 1 : 0);
}
pr_getln
───────────────────────────────────────────────────────────────────────────
/*
* pr_getln -- get a line of text while expanding tabs;
* put text into an array and return the length of the line
* including termination to the calling function.
*/
#include <stdio.h>
#include <stdlib.h>
#include <local\std.h>
int
pr_getln(s, lim, fin)
char *s;
int lim;
FILE *fin;
{
int ch;
register char *cp;
extern int tabstop(); /* query tabstop array */
cp = s;
while (--lim > 0 && (ch = fgetc(fin)) != EOF && ch != '\n' && ch !=
'\f') {
if (ch == '\t')
/* loop and store spaces until next tabstop */
do
*cp++ = ' ';
while (--lim > 0 && tabstop(cp - s) == 0);
else
*cp++ = ch;
}
if (ch == EOF && cp - s == 0)
;
else if (ch == EOF || ch == '\n')
*cp++ = '\n'; /* assure correct line termination */
else if (ch == '\f' && cp - s == 0) {
*cp++ = '\f';
fgetc(fin); /* toss the trailing newline */
}
*cp = '\0';
return (cp - s);
}
pr_line
───────────────────────────────────────────────────────────────────────────
/*
* pr_line -- ouput a buffered logical line and
* return a count of physical lines produced
*/
#include <stdio.h>
#include <stdlib.h>
#include <local\std.h>
#include "print.h"
int
pr_line(s, fout, rline)
char *s; /* buffered line of text */
FILE *fout; /* output stream */
unsigned int rline; /* file-relative line number */
{
int c_cnt; /* character position in output line */
int nlines; /* number of lines output */
extern PRINT pcnf;
extern int spaces(int, FILE *); /* emit string of spaces */
nlines = 1;
c_cnt = 0;
/* output the left indentation, if any */
c_cnt += spaces(pcnf.p_lmarg, fout);
/* output the line number if numbering enabled */
if (pcnf.p_lnum != 0)
c_cnt += fprintf(fout, "%6u ", rline);
/* output the text of the line */
while (*s != '\0') {
if (c_cnt > (pcnf.p_wid - pcnf.p_rmarg)) {
fputc('\n', fout);
++nlines;
c_cnt = 0;
c_cnt = spaces(pcnf.p_lmarg, fout);
}
fputc(*s, fout);
++s;
++c_cnt;
}
return (nlines);
}
makefile for the pr program
───────────────────────────────────────────────────────────────────────────
# makefile for the pr program
LINC=c:\include\local
LIB=c:\lib
LLIB=c:\lib\local
pr_cpy.obj: pr_cpy.c print.h $(LINC)\printer.h
msc $*;
lib prlib -+$*;
pr_file.obj: pr_file.c print.h
msc $*;
lib prlib -+$*;
pr_getln.obj: pr_getln.c
msc $*;
lib prlib -+$*;
pr_help.obj: pr_help.c
msc $*;
lib prlib -+$*;
pr_gcnf.obj: pr_gcnf.c print.h $(LINC)\printer.h
msc $*;
lib prlib -+$*;
pr_line.obj: pr_line.c print.h
msc $*;
lib prlib -+$*;
pr.obj: pr.c print.h
msc $*;
pr.exe: pr.obj prlib.lib $(LLIB)\util.lib
link $* $(LIB)\ssetargv, $*,, prlib $(LLIB)\util;
hex.c
───────────────────────────────────────────────────────────────────────────
/*
* hex.c -- hex conversions routines
*/
#define NIBBLE 0x000F
#define BYTE 0x00FF
#define WORD 0xFFFF
char hextab[] = {
'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
};
/*
* byte2hex -- convert a byte to a string
* representation of its hexadecimal value
*/
char *
byte2hex(data, buf)
unsigned char data;
char *buf;
{
char *cp;
unsigned int d;
d = data & BYTE;
cp = buf;
*cp++ = hextab[(d >> 4) & NIBBLE];
*cp++ = hextab[d & NIBBLE];
*cp = '\0';
return (buf);
}
/*
* word2hex -- convert a word to a string
* representation of its hexadecimal value
*/
char *
word2hex(data, buf)
unsigned int data;
char *buf;
{
char *cp;
unsigned int d;
d = data & WORD;
cp = buf;
*cp++ = hextab[(d >> 12) & NIBBLE];
*cp++ = hextab[(d >> 8) & NIBBLE];
*cp++ = hextab[(d >> 4) & NIBBLE];
*cp++ = hextab[d & NIBBLE];
*cp = '\0';
return (buf);
}
dump
───────────────────────────────────────────────────────────────────────────
/*
* dump -- display contents of non-ASCII files in hex byte and
* ASCII character forms (like the DOS debug dump option)
*/
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys\types.h>
#include <sys\stat.h>
#include <io.h>
#include <local\std.h>
#define STDINPUT 0
#define LINEWIDTH 80
main(argc,argv)
int argc;
char *argv[];
{
int ch;
BOOLEAN sflag = FALSE,
vflag = FALSE,
errflag = FALSE;
int fd;
static char pgm[MAXNAME + 1] = { "dump" };
extern int getopt(int, char **, char *);
extern char *optarg;
extern int optind, opterr;
extern void getpname(char *, char *);
extern int hexdump(int, BOOLEAN);
extern void fatal(char *, char *, int);
if (_osmajor >= 3)
getpname(*argv, pgm);
while ((ch = getopt(argc, argv, "sv")) != EOF)
switch (ch) {
case 's': /* strip -- convert all non-ASCII to '.' */
sflag = TRUE;
break;
case 'v': /* verbose -- tell user what's happening */
vflag = TRUE;
break;
case '?': /* bad option */
errflag = TRUE;
break;
}
if (errflag == TRUE) {
fprintf(stderr, "Usage: %s [-sv] [file...]\n", pgm);
exit(1);
}
if (optind == argc) {
if (setmode(STDINPUT, O_BINARY) == -1)
fatal(pgm, "Cannot set binary mode", 2);
hexdump(STDINPUT, sflag);
exit(0);
}
for ( ; optind < argc; ++optind) {
if ((fd = open(argv[optind], O_BINARY | O_RDONLY)) == -1) {
fprintf(stderr,
"%s: Error opening %s -- ", pgm,
argv[optind]);
perror("");
continue;
}
if (vflag == TRUE)
fprintf(stdout, "\n%s:\n", argv[optind]);
if (hexdump(fd, sflag) == FAILURE) {
fprintf(stderr,
"%s: Error reading %s -- ", pgm,
argv[optind]);
perror("");
}
if (close(fd) == -1)
fatal(pgm, "Error closing input file", 3);
}
exit(0);
}
hexdump
───────────────────────────────────────────────────────────────────────────
/*
* hexdump -- read data from an open file and "dump"
* it in side-by-side hex and ASCII to standard output
*/
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <local\std.h>
#define LINEWIDTH 80
#define NBYTES 16
#define WORD 0xFFFF
#define RGHTMARK 179
#define LEFTMARK 179
#define DEL 0x7F
int hexdump(fd, strip)
int fd;
BOOLEAN strip;
{
unsigned char i;
int n; /* bytes per read operation */
unsigned long offset; /* bytes from start of file */
char inbuf[BUFSIZ + 1], outbuf[LINEWIDTH + 1];
char hexbuf[5];
register char *inp, *outp;
extern char *byte2hex(unsigned char, char *);
extern char *word2hex(unsigned int, char *);
offset = 0;
while ((n = read(fd, inbuf, BUFSIZ)) != 0) {
if (n == -1)
return FAILURE;
inp = inbuf;
while (inp < inbuf + n) {
outp = outbuf;
/* offset in hex */
outp += sprintf(outp, "%08lX",
offset + (unsigned long)(inp - inbuf));
*outp++ = ' ';
/* block of bytes in hex */
for (i = 0; i < NBYTES; ++i) {
*outp++ = ' ';
strcpy(outp, byte2hex(*inp++, hexbuf));
outp += 2;
}
*outp++ = ' ';
*outp++ = ' ';
*outp++ = (strip == TRUE) ? '|' : LEFTMARK;
/* same block of bytes in ASCII */
inp -= NBYTES;
for (i = 0; i < NBYTES; ++i) {
if (strip == TRUE && (*inp < ' ' || *inp >=
DEL))
*outp = '.';
else if (iscntrl(*inp))
*outp = '.';
else
*outp = *inp;
++inp;
++outp;
}
*outp++ = (strip == TRUE) ? '|' : RGHTMARK;
*outp++ = '\n';
*outp = '\0';
fputs(outbuf, stdout);
}
offset += n;
}
return SUCCESS;
}
show
───────────────────────────────────────────────────────────────────────────
/*
* show -- a filter that displays the contents of a file
* in a way that is guaranteed to be displayable
*/
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <io.h>
#include <local\std.h>
main(argc, argv)
int argc;
char **argv;
{
int ch;
FILE *fp;
BOOLEAN sflag = FALSE; /* strip non-ASCII characters */
BOOLEAN vflag = FALSE; /* verbose mode */
BOOLEAN wflag = FALSE; /* filter typical word-processing codes */
BOOLEAN errflag = FALSE;
static char pgm[] = { "show" };
extern int getopt(int, char **, char *);
extern char *optarg;
extern int optind, opterr;
extern int showit(FILE *, BOOLEAN, BOOLEAN);
extern void fatal(char *, char *, int);
if (_osmajor >= 3)
getpname(*argv, pgm);
while ((ch = getopt(argc, argv, "svw")) != EOF) {
switch (ch) {
case 's': /* strip non-ASCII characters */
sflag = TRUE;
break;
case 'v': /* verbose */
vflag = TRUE;
break;
case 'w': /* use word-processing conventions */
wflag = sflag = TRUE;
break;
case '?':
errflag = TRUE;
break;
}
}
/* check for errors */
if (errflag == TRUE) {
fprintf(stderr, "Usage: %s [-sv] [file...]\n", pgm);
exit(1);
}
/* if no file names, use standard input */
if (optind == argc) {
if (setmode(fileno(stdin), O_BINARY) == -1)
fatal(pgm, "Cannot set binary mode on stdin", 2);
showit(stdin, sflag, wflag);
exit(0);
}
/* otherwise, process remainder of command line */
for ( ; optind < argc; ++optind) {
if ((fp = fopen(argv[optind], "rb")) == NULL) {
fprintf(stderr,
"%s: Error opening %s\n", pgm,
argv[optind]);
perror("");
continue;
}
if (vflag == TRUE)
fprintf(stdout, "\n%s:\n", argv[optind]);
if (showit(fp, sflag, wflag) != 0) {
fprintf(stderr,
"%s: Error reading %s\n", pgm,
argv[optind]);
perror("");
}
if (fclose(fp) == EOF)
fatal(pgm, "Error closing input file", 2);
}
exit(0);
}
showit
───────────────────────────────────────────────────────────────────────────
/*
* showit -- make non-printable characters in
* the stream fp visible (or optionally strip them)
*/
#include <stdio.h>
#include <ctype.h>
#include <local\std.h>
int
showit(fp, strip, wp)
FILE *fp;
BOOLEAN strip; /* strip non-ASCII codes */
BOOLEAN wp; /* filter typical word-processing codes */
{
int ch;
clearerr(fp);
while ((ch = getc(fp)) != EOF)
if (isascii(ch) && (isprint(ch) || isspace(ch)))
switch (ch) {
case '\r':
if (wp == TRUE) {
if ((ch = getc(fp)) != '\n')
ungetc(ch, fp);
putchar('\r');
putchar('\n');
}
else
putchar(ch);
break;
default:
putchar(ch);
break;
}
else if (strip == FALSE)
printf("\\%02X", ch);
else if (wp == TRUE && isprint(ch & ASCII))
putchar(ch & ASCII);
return (ferror(fp));
}
dspytype
───────────────────────────────────────────────────────────────────────────
/*
* dspytype -- determine display adapter type
*/
#include <stdio.h>
#include <dos.h>
#include <local\bioslib.h>
#include <local\video.h>
#define MDA_SEG 0xB000
#define CGA_SEG 0xB800
main()
{
extern int memchk(unsigned int, unsigned int);
int mdaflag, egaflag, cgaflag;
int ega_mem, ega_mode;
unsigned int features, switches;
static int memtab[] = {
64, 128, 192, 256
};
mdaflag = egaflag = cgaflag = 0;
/* look for display adapters */
if (ega_info(&ega_mem, &ega_mode, &features, &switches))
++egaflag;
fputs("Enhanced graphics adapter ", stdout);
if (egaflag) {
fputs("installed\n", stdout);
fprintf(stdout, "EGA memory size = %d-KB\n",
memtab[ega_mem]);
fprintf(stdout, "EGA is in %s mode\n",
ega_mode ? "monochrome" : "color");
}
else
fputs("not installed\n", stdout);
/* look for IBM monochrome memory */
if (!egaflag || (egaflag && ega_mode == 0))
if (memchk(MDA_SEG, 0))
++mdaflag;
/* look for CGA memory */
if (!egaflag || (ega && ega_mode == 1))
if (memchk(CGA_SEG, 0))
++cgaflag;
}
fputs("Monochrome adapter ", stdout);
if (mdaflag)
fputs("installed\n", stdout);
else
fputs("not installed\n", stdout);
fputs("Color/graphics adapter ", stdout);
if (cgaflag)
fputs("installed\n", stdout);
else
fputs("not installed\n", stdout);
/* report video settings */
getstate();
fprintf(stdout, "mode=%d width=%d page=%d\n", Vmode, Vwidth,
Vpage);
exit(0);
}
memchk
───────────────────────────────────────────────────────────────────────────
/*
* memchk -- look for random-access memory at
* a specified location; return nonzero if found
*/
#include <dos.h>
#include <memory.h>
int
memchk(seg, os)
unsigned int seg;
unsigned int os;
{
unsigned char tstval, oldval, newval;
unsigned int ds;
struct SREGS segregs;
/* get value of current data segment */
segread(&segregs);
ds = segregs.ds;
/* save current contents of test location */
movedata(seg, os, ds, &oldval, 1);
/* copy a known value into test location */
tstval = 0xFC;
movedata(ds, &tstval, seg, os, 1);
/* read test value back and compare to value written */
movedata(seg, os, ds, &newval, 1);
if (newval != tstval)
return (0);
/* restore original contents of test location */
movedata(ds, &oldval, seg, os, 1);
return (1);
}
ega_info
───────────────────────────────────────────────────────────────────────────
/*
* ega_info -- gather information about an EGA;
* return a nonzero value if one is found
*/
#include <dos.h>
#include <local\bioslib.h>
#define EGA_INFO 0x10
#define NMODES 2
#define NMEMSIZ 4
int
ega_info(memsize, mode, features, switches)
int *memsize; /* EGA memory size indicator: 0 = 64K */
/* 1 = 128K; 2 = 192K; 3 = 256K */
int *mode; /* 0 = color mode; 1 = mono mode */
/* use getstate function to find out which mode */
unsigned int
*features, /* feature bit settings */
*switches; /* EGA switch settings */
{
int result = 0;
union REGS inregs, outregs;
/* request EGA information */
inregs.h.ah = ALT_FUNCTION;
inregs.h.bl = EGA_INFO;
int86(VIDEO_IO, &inregs, &outregs);
*memsize = outregs.h.bl;
*mode = outregs.h.bh;
*features = outregs.h.ch;
*switches = outregs.h.cl;
/* return nonzero if EGA installed */
if (*memsize >= 0 && *memsize < NMEMSIZ &&
*mode >= 0 && *mode < NMODES)
result = 1;
return (result);
}
cpblk
───────────────────────────────────────────────────────────────────────────
/*
* cpblk -- copy a block of characters and attributes
* while eliminating "snow" on a standard CGA display
*/
#include <conio.h>
#include <memory.h>
#define BLKCNT 10
#define VSTAT 0x3DA
#define VRBIT 8
#define WRDCNT 200
#define NBYTES (2 * WRDCNT)
/* macro to synchronize with vertical retrace period */
#define VSYNC while ((inp(VSTAT) & VRBIT) == VRBIT); \
while ((inp(VSTAT) & VRBIT) != VRBIT)
int
cpblk(src_os, src_seg, dest_os, dest_seg)
unsigned int src_os, src_seg, dest_os, dest_seg;
{
register int i;
int n;
register int delta;
n = 0;
delta = 0;
for (i = 0; i < BLKCNT ; ++i) {
/* copy a block of words during vertical retrace */
VSYNC;
movedata(src_seg, src_os + delta,
dest_seg, dest_os + delta, NBYTES);
n += WRDCNT;
/* adjust buffer offset */
delta += NBYTES;
}
return (n);
}
st
───────────────────────────────────────────────────────────────────────────
/*
* st -- screen test using cpblk function
*/
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <ctype.h>
#include <dos.h>
#include <local\std.h>
#include <local\video.h>
#define CG_SEG 0xB800
#define MONO_SEG 0xB000
#define NBYTES 0x1000
#define PAGESIZ (NBYTES / 2)
#define PG0_OS 0
#define PG1_OS PG0_OS + NBYTES
#define ESC 27
#define MAXSCAN 14
main(argc, argv)
int argc;
char *argv[];
{
int i;
int k; /* user command character */
int ca; /* character/attribute word */
int ch; /* character value read */
int row, col; /* cursor position upon entry */
int c_start, c_end; /* cursor scan lines */
unsigned char attr; /* saved video attribute */
unsigned dseg; /* destination buffer segment */
unsigned os; /* page offset in bytes */
static unsigned sbuf[NBYTES]; /* screen buffer */
unsigned *bp; /* screen element pointer */
unsigned sseg; /* source segment */
int special; /* use special copy routine */
int apg, vpg; /* active and visual display
/* pages */
/* segment register values */
struct SREGS segregs;
extern void swap_int(int *, int *);
static char pgm[] = { "st" }; /* program name */
/* save user's current video state */
getstate();
readcur(&row, &col, Vpage);
putcur(row - 1, 0, Vpage);
readca(&ch, &attr, Vpage);
getctype(&c_start, &c_end, Vpage);
setctype(MAXSCAN, c_end);
clrscrn(attr);
putcur(1, 1, Vpage);
/* initialize destination segment */
special = 1;
fputs("ScreenTest (ST): ", stderr);
if (Vmode == CGA_C80 || Vmode == CGA_M80) {
dseg = CG_SEG;
fprintf(stderr, "Using CGA mode %d", Vmode);
}
else if (Vmode == MDA_M80) {
dseg = MONO_SEG;
fprintf(stderr, "Using MDA (mode %d)", Vmode);
special = 0;
} else
fprintf(stderr, "%s: Requires 80-column text mode\n", pgm);
/* process command-line arguments */
if (argc > 2) {
fprintf(stderr, "Usage: %s [x]\n", pgm);
exit(2);
}
else if (argc == 2)
special = 0; /* bypass special block move */
/* get data segment value */
segread(&segregs);
sseg = segregs.ds;
/* set up "active" and "visual" display pages */
apg = 1; /* page being written to */
vpg = 0; /* page being viewed */
/* display buffers on the user's command */
fprintf(stderr, " -- Type printable text; Esc=exit");
while ((k = getkey()) != ESC) {
if (isascii(k) && isprint(k)) {
/* fill the buffer */
ca = ((k % 0xEF) << 8) | k;
for (bp = sbuf; bp - sbuf < PAGESIZ; ++bp)
*bp = ca;
if (Vmode == MDA_M80)
os = 0;
else
os = (apg == 0) ? PG0_OS : PG1_OS;
if (special)
cpblk(sbuf, sseg, os, dseg);
else
movedata(sseg, sbuf, dseg, os, NBYTES);
if (Vmode != MDA_M80) {
swap_int(&apg, &vpg);
setpage(vpg);
}
}
else {
clrscrn(attr);
putcur(0, 0, Vpage);
writestr(" Type printable text; Esc = exit ", vpg);
}
}
/* restore user's video conditions and return to DOS */
setpage(Vpage);
clrscrn(attr);
putcur(0, 0, Vpage);
setctype(c_start, c_end);
exit(0);
}
sbuf.h
───────────────────────────────────────────────────────────────────────────
/*
* sbuf.h -- header file for buffered screen interface
*/
#define SB_OK 0
#define SB_ERR (-1)
/* screen-buffer constants */
#define SB_ROWS 25
#define SB_COLS 80
#define SB_SIZ SB_ROWS * SB_COLS
/* screen character/attribute buffer element definition */
struct BYTEBUF {
unsigned char ch; /* character */
unsigned char attr; /* attribute */
};
union CELL {
struct BYTEBUF b;
unsigned short cap; /* character/attribute pair */
};
/* screen-buffer control structure */
struct BUFFER {
/* current position */
short row, col;
/* pointer to screen-buffer array */
union CELL *bp;
/* changed region per screen-buffer row */
short lcol[SB_ROWS]; /* left end of changed region */
short rcol[SB_ROWS]; /* right end of changed region */
/* buffer status */
unsigned short flags;
};
/* buffer flags values */
#define SB_DELTA 0x01
#define SB_RAW 0x02
#define SB_DIRECT 0x04
#define SB_SCROLL 0x08
/* coordinates of a window (rectangular region) on the screen buffer */
struct REGION {
/* current position */
short row, col;
/* window boundaries */
short r0, c0; /* upper left corner */
short r1, c1; /* lower right corner */
/* scrolling region boundaries */
short sr0, sc0; /* upper left corner */
short sr1, sc1; /* lower right corner */
/* window buffer flags */
unsigned short wflags;
};
sb_init
───────────────────────────────────────────────────────────────────────────
/*
* sb_init -- initialize the buffered screen interface
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <local\sbuf.h>
/* global data declarations */
struct BUFFER Sbuf; /* control information */
union CELL Scrnbuf[SB_ROWS][SB_COLS]; /* screen-buffer array */
int
sb_init()
{
int i;
char *um; /* update mode */
/* set initial parameter values */
Sbuf.bp = &Scrnbuf[0][0];
Sbuf.row = Sbuf.col = 0;
for (i = 0; i < SB_ROWS; ++i) {
Sbuf.lcol[i] = SB_COLS;
Sbuf.rcol[i] = 0;
}
Sbuf.flags = 0;
/* set screen update mode */
um = strupr(getenv("UPDATEMODE"));
if (um == NULL || strcmp(um, "BIOS") == 0)
Sbuf.flags &= ~SB_DIRECT;
else if (strcmp(um, "DIRECT") == 0)
Sbuf.flags |= SB_DIRECT;
else
return SB_ERR;
return SB_OK;
}
sb_show
───────────────────────────────────────────────────────────────────────────
/*
* sb_show -- copy the screen buffer to display memory
*/
#include <stdio.h>
#include <dos.h>
#include <memory.h>
#include <local\sbuf.h>
#include <local\video.h>
#define MDA_SEG 0xB000
#define CGA_SEG 0xB800
#define NBYTES (2 * SB_COLS)
/* macro to synchronize with vertical retrace period */
#define VSTAT 0x3DA
#define VRBIT 8
#define VSYNC while ((inp(VSTAT) & VRBIT) == VRBIT); \
while ((inp(VSTAT) & VRBIT) != VRBIT)
extern struct BUFFER Sbuf;
extern union CELL Scrnbuf[SB_ROWS][SB_COLS];
int
sb_show(pg)
short pg;
{
register short r, c;
short n;
short count, ncols;
unsigned int src_os, dest_os;
struct SREGS segregs;
if ((Sbuf.flags & SB_DIRECT) == SB_DIRECT) {
/* use the direct-screen interface */
segread(&segregs);
/* determine extent of changes */
n = 0;
for (r = 0; r < SB_ROWS; ++r)
if (Sbuf.lcol[r] <= Sbuf.rcol[r])
++n;
src_os = dest_os = 0;
if (n <= 2)
/* copy only rows that contain changes */
for (r = 0; r < SB_ROWS; ++r) {
if (Sbuf.lcol[r] <= Sbuf.rcol[r]) {
/* copy blocks during vertical
* retrace */
VSYNC;
movedata(segregs.ds, &Scrnbuf[0][0]
+ src_os,
CGA_SEG, dest_os, NBYTES);
Sbuf.lcol[r] = SB_COLS;
Sbuf.rcol[r] = 0;
}
src_os += NBYTES;
dest_os += NBYTES;
}
else {
/* copy the entire buffer */
count = 3 * NBYTES;
ncols = 3 * NBYTES;
for (r = 0; r < SB_ROWS - 1; r += 3) {
VSYNC;
movedata(segregs.ds, &Scrnbuf[0][0]
+src_os,
CGA_SEG, dest_os, count);
src_os += ncols;
dest_os += count;
}
VSYNC;
movedata(segregs.ds, &Scrnbuf[0][0] + src_os,
CGA_SEG, dest_os, NBYTES);
for (r = 0; r < SB_ROWS; ++r) {
Sbuf.lcol[r] = SB_COLS;
Sbuf.rcol[r] = 0;
}
}
}
else
/* use the BIOS video interface */
for (r = 0; r < SB_ROWS; ++r)
/* copy only changed portions of lines */
if (Sbuf.lcol[r] < SB_COLS && Sbuf.rcol[r] > 0) {
for (c = Sbuf.lcol[r]; c <= Sbuf.rcol[r];
++c) {
putcur(r, c, pg);
writeca(Scrnbuf[r][c].b.ch,
Scrnbuf[r][c].b.attr, 1,
pg);
}
Sbuf.lcol[r] = SB_COLS;
Sbuf.rcol[r] = 0;
}
/* the display now matches the buffer -- clear flag bit */
Sbuf.flags &= ~SB_DELTA;
return SB_OK;
}
sb_new
───────────────────────────────────────────────────────────────────────────
/*
* sb_new -- prepare a new window (rectangular region)
* and return a pointer to it
*/
#include <stdio.h>
#include <malloc.h>
#include <local\sbuf.h>
struct REGION *
sb_new(top, left, height, width)
int top; /* top row */
int left; /* left column */
int height; /* total rows */
int width; /* total columns */
{
struct REGION *new;
/* allocate the data-control structure */
new = (struct REGION *)malloc(sizeof (struct REGION));
if (new != NULL) {
new->r0 = new->sr0 = top;
new->r1 = new->sr1 = top + height - 1;
new->c0 = new->sc0 = left;
new->c1 = new->sc1 = left + width - 1;
new->row = new->col = 0;
new->wflags = 0;
}
return (new);
}
sb_move
───────────────────────────────────────────────────────────────────────────
/*
* sb_move -- move the screen-buffer "cursor"
*/
#include <local\sbuf.h>
extern struct BUFFER Sbuf;
extern union CELL Scrnbuf[SB_ROWS][SB_COLS];
int
sb_move(win, r, c)
struct REGION *win; /* window pointer */
register short r, c; /* buffer row and column */
{
/* don't change anything if request is out of range */
if (r < 0 || r > win->r1 - win->r0 || c < 0 || c > win->c1 -
win->c0)
return SB_ERR;
win->row = r;
win->col = c;
Sbuf.row = r + win->r0;
Sbuf.col = c + win->c0;
return SB_OK;
}
sb_fill
───────────────────────────────────────────────────────────────────────────
/*
* sb_fill -- fill region routines
*/
#include <local\sbuf.h>
extern struct BUFFER Sbuf;
extern union CELL Scrnbuf[SB_ROWS][SB_COLS];
/*
* sb_fill -- set all cells in a specified region
* to the same character/attribute value
*/
int
sb_fill(win, ch, attr)
struct REGION *win;
unsigned char ch; /* fill character */
unsigned char attr; /* fill attribute */
{
register int i, j;
unsigned short ca;
ca = (attr << 8) | ch;
for (i = win->sr0; i <= win->sr1; ++i) {
for (j = win->sc0; j <= win->sc1; ++j)
Scrnbuf[i][j].cap = ca;
if (win->sc0 < Sbuf.lcol[i])
Sbuf.lcol[i] = win->sc0;
if (win->sc1 > Sbuf.rcol[i])
Sbuf.rcol[i] = win->sc1;
}
Sbuf.flags |= SB_DELTA;
return SB_OK;
}
/*
* sb_fillc -- set all cells in a specified region
* to the same character value; leave attributes undisturbed
*/
int
sb_fillc(win, ch)
struct REGION *win;
unsigned char ch; /* fill character */
{
register int i, j;
for (i = win->sr0; i <= win->sr1; ++i) {
for (j = win->sc0; j <= win->sc1; ++j)
Scrnbuf[i][j].b.ch = ch;
if (win->sc0 < Sbuf.lcol[i])
Sbuf.lcol[i] = win->sc0;
if (win->sc1 > Sbuf.rcol[i])
Sbuf.rcol[i] = win->sc1;
}
Sbuf.flags |= SB_DELTA;
return SB_OK;
}
/*
* sb_filla -- set all cells in a specified region
* to the same attribute value; leave characters undisturbed
*/
int
sb_filla(win, attr)
struct REGION *win;
unsigned char attr; /* fill attribute */
{
register int i, j;
for (i = win->sr0; i <= win->sr1; ++i) {
for (j = win->sc0; j <= win->sc1; ++j)
Scrnbuf[i][j].b.attr = attr;
if (win->sc0 < Sbuf.lcol[i])
Sbuf.lcol[i] = win->sc0;
if (win->sc1 > Sbuf.rcol[i])
Sbuf.rcol[i] = win->sc1;
}
Sbuf.flags |= SB_DELTA;
return SB_OK;
}
sb_put
───────────────────────────────────────────────────────────────────────────
* sb_put -- routines to put characters and strings into the
* screen buffer; the cursor location is altered
*/
#include <local\sbuf.h>
#include <ctype.h>
extern struct BUFFER Sbuf;
extern union CELL Scrnbuf[SB_ROWS][SB_COLS];
/*
* sb_putc -- put a character into a screen-buffer window
*/
int
sb_putc(win, ch)
struct REGION *win;
unsigned char ch;
{
short cmax, rmax;
short lim;
short noscroll = 0, puterr = 0;
/* calculate screen-buffer position and limits */
cmax = win->c1 - win->c0;
rmax = win->r1 - win->r0;
Sbuf.row = win->r0 + win->row;
Sbuf.col = win->c0 + win->col;
/* process the character */
switch (ch) {
case '\b':
/* non-destructive backspace */
if (win->col > 0) {
--win->col;
Sbuf.col = win->c0 + win->col;
return SB_OK;
}
else
return SB_ERR;
case '\n':
/* clear trailing line segment */
while (win->col < cmax)
if (sb_putc(win, ' ') == SB_ERR)
++puterr;
break;
case '\t':
/* convert tab to required number of spaces */
lim = win->col + 8 - (win->col & 0x7);
while (win->col < lim)
if (sb_putc(win, ' ') == SB_ERR)
++puterr;
break;
default:
/* if printable ASCII, place the character in the buffer */
if (isascii(ch) && isprint(ch))
Scrnbuf[Sbuf.row][Sbuf.col].b.ch = ch;
if (Sbuf.col < Sbuf.lcol[Sbuf.row])
Sbuf.lcol[Sbuf.row] = Sbuf.col;
if (Sbuf.col > Sbuf.rcol[Sbuf.row])
Sbuf.rcol[Sbuf.row] = Sbuf.col;
break;
}
/* update the cursor position */
if (win->col < cmax)
++win->col;
else if (win->row < rmax) {
win->col = 0;
++win->row;
}
else if ((win->wflags & SB_SCROLL) == SB_SCROLL) {
sb_scrl(win, 1);
win->col = 0;
win->row = rmax;
}
else
++noscroll;
/* update screen-buffer position */
Sbuf.row = win->r0 + win->row;
Sbuf.col = win->c0 + win->col;
Sbuf.flags |= SB_DELTA;
return ((noscroll || puterr) ? SB_ERR : SB_OK);
} /* end sb_putc() */
/*
* sb_puts -- put a string into the screen buffer
*/
int
sb_puts(win, s)
struct REGION *win;
unsigned char *s;
{
while (*s)
if (sb_putc(win, *s++) == SB_ERR)
return SB_ERR;
return SB_OK;
} /* end sb_puts() */
sb_read
───────────────────────────────────────────────────────────────────────────
/*
* sb_read -- read character/attribute data
*/
#include <local\sbuf.h>
extern struct BUFFER Sbuf;
extern union CELL Scrnbuf[SB_ROWS][SB_COLS];
unsigned char
sb_ra(win)
struct REGION *win; /* window pointer */
{
return (Scrnbuf[win->r0 + win->row][win->c0 + win->col].b.attr);
} /* end sb_ra() */
/*
* sb_rc -- read character from current location in screen buffer
*/
unsigned char
sb_rc(win)
struct REGION *win; /* window pointer */
{
return (Scrnbuf[win->r0 + win->row][win->c0 + win->col].b.ch);
} /* end sb_rc() */
/*
* sb_rca -- read character/attribute pair from current
* location in screen buffer
*/
unsigned short
sb_rca(win)
struct REGION *win; /* window pointer */
{
return (Scrnbuf[win->r0 + win->row][win->c0 + win->col].cap);
} /* end sb_rca() */
sb_scrl
───────────────────────────────────────────────────────────────────────────
/*
* sb_scrl -- scrolling routines
*/
#include <local\sbuf.h>
extern struct BUFFER Sbuf;
extern union CELL Scrnbuf[SB_ROWS][SB_COLS];
/*
* sb_scrl -- scroll the specified window
* n lines (direction indicated by sign)
*/
int
sb_scrl(win, n)
struct REGION *win;
short n; /* number of rows to scroll */
{
register short r, c;
if (n == 0)
/* clear the entire region to spaces */
sb_fillc(win, ' ');
else if (n > 0) {
/* scroll n rows up */
for (r = win->sr0; r <= win->sr1 - n; ++r) {
for (c = win->sc0; c <= win->sc1; ++c)
Scrnbuf[r][c] = Scrnbuf[r + n][c];
if (win->sc0 < Sbuf.lcol[r])
Sbuf.lcol[r] = win->sc0;
if (win->sc1 > Sbuf.rcol[r])
Sbuf.rcol[r] = win->sc1;
}
for ( ; r <= win->sr1; ++r) {
for (c = win->sc0; c <= win->sc1; ++c)
Scrnbuf[r][c].b.ch = ' ';
if (win->sc0 < Sbuf.lcol[r])
Sbuf.lcol[r] = win->sc0;
if (win->sc1 > Sbuf.rcol[r])
Sbuf.rcol[r] = win->sc1;
}
}
else {
/* scroll n rows down */
n = -n;
for (r = win->sr1; r >= win->sr0 + n; --r) {
for (c = win->sc0; c <= win->sc1; ++c)
Scrnbuf[r][c] = Scrnbuf[r - n][c];
if (win->sc0 < Sbuf.lcol[r])
Sbuf.lcol[r] = win->sc0;
if (win->sc1 > Sbuf.rcol[r])
Sbuf.rcol[r] = win->sc1;
}
for ( ; r >= win->sr0; --r) {
for (c = win->sc0; c <= win->sc1; ++c)
Scrnbuf[r][c].b.ch = ' ';
if (win->sc0 < Sbuf.lcol[r])
Sbuf.lcol[r] = win->sc0;
if (win->sc1 > Sbuf.rcol[r])
Sbuf.rcol[r] = win->sc1;
}
}
Sbuf.flags |= SB_DELTA;
return SB_OK;
} /* end sb_scrl() */
/*
* sb_set_scrl -- set the scroll region boundaries
*/
int
sb_set_scrl(win, top, left, bottom, right)
struct REGION *win; /* window pointer */
short top, left; /* upper left corner */
short bottom, right; /* lower left corner */
{
if (top < 0 || left < 0 ||
bottom > win->r1 - win->r0 || right > win->c1 - win->c0)
return SB_ERR;
win->sr0 = win->r0 + top;
win->sc0 = win->c0 + left;
win->sr1 = win->r0 + bottom - 1;
win->sc1 = win->c0 + right - 1;
return SB_OK;
} /* end sb_set_scrl() */
sb_write
───────────────────────────────────────────────────────────────────────────
/*
* sb_write -- screen-buffer write routines
*/
#include <local\sbuf.h>
extern struct BUFFER Sbuf;
extern union CELL Scrnbuf[SB_ROWS][SB_COLS];
/*
* sb_wa -- write an attribute to a region of the screen buffer
*/
int
sb_wa(win, attr, n)
struct REGION *win; /* window pointer */
unsigned char attr; /* attribute */
short n; /* repetition count */
{
short i;
short row;
short col;
i = n;
row = win->r0 + win->row;
col = win->c0 + win->col;
while (i--)
Scrnbuf[row][col + i].b.attr = attr;
/* marked the changed region */
if (col < Sbuf.lcol[row])
Sbuf.lcol[row] = col;
if (col + n > Sbuf.rcol[row])
Sbuf.rcol[row] = col + n;
Sbuf.flags |= SB_DELTA;
return (i == 0) ? SB_OK : SB_ERR;
} /* end sb_wa() */
/*
* sb_wc -- write a character to a region of the screen buffer
*/
int
sb_wc(win, ch, n)
struct REGION *win; /* window pointer */
unsigned char ch; /* character */
short n; /* repetition count */
{
short i;
short row;
short col;
i = n;
row = win->r0 + win->row;
col = win->c0 + win->col;
while (i--)
Scrnbuf[row][col + i].b.ch = ch;
/* marked the changed region */
if (col < Sbuf.lcol[row])
Sbuf.lcol[row] = col;
if (col + n > Sbuf.rcol[row])
Sbuf.rcol[row] = col + n;
Sbuf.flags |= SB_DELTA;
return (i == 0 ? SB_OK : SB_ERR);
} /* end sb_wc() */
/*
* sb_wca -- write a character/attribute pair to a region
* of the screen buffer
*/
int
sb_wca(win, ch, attr, n)
struct REGION *win; /* window pointer */
unsigned char ch; /* character */
unsigned char attr; /* attribute */
short n; /* repetition count */
{
int i;
short row;
short col;
i = n;
row = win->r0 + win->row;
col = win->c0 + win->col;
while (i--)
Scrnbuf[row][col + i].cap = (attr << 8) | ch;
/* marked the changed region */
if (col < Sbuf.lcol[row])
Sbuf.lcol[row] = col;
if (col + n > Sbuf.rcol[row])
Sbuf.rcol[row] = col + n;
Sbuf.flags |= SB_DELTA;
return (i == 0 ? SB_OK : SB_ERR);
} /* end sb_wca() */
sb_box
───────────────────────────────────────────────────────────────────────────
/*
* sb_box -- draw a box around the perimeter of a window
* using the appropriate IBM graphics characters
*/
#include <local\sbuf.h>
#include <local\video.h>
#include <local\box.h>
int
sb_box(win, type, attr)
struct REGION *win;
short type;
unsigned char attr;
{
register short r; /* row index */
short x; /* interior horizontal line length */
short maxr, maxc; /* maximum row and col values */
BOXTYPE *boxp; /* pointer to box-drawing character struct */
static BOXTYPE box[] = {
'+', '+', '+', '+', '-', '-', '|', '|',
ULC11, URC11, LLC11, LRC11, HBAR1, HBAR1, VBAR1, VBAR1,
ULC22, URC22, LLC22, LRC22, HBAR2, HBAR2, VBAR2, VBAR2,
ULC12, URC12, LLC12, LRC12, HBAR1, HBAR1, VBAR2, VBAR2,
ULC21, URC21, LLC21, LRC21, HBAR2, HBAR2, VBAR1, VBAR1,
BLOCK, BLOCK, BLOCK, BLOCK, HBART, HBARB, BLOCK, BLOCK
};
boxp = &box[type];
maxc = win->c1 - win->c0;
maxr = win->r1 - win->r0;
x = maxc - 1;
/* draw top row */
sb_move(win, 0, 0);
sb_wca(win, boxp->ul, attr, 1);
sb_move(win, 0, 1);
sb_wca(win, boxp->tbar, attr, x);
sb_move(win, 0, maxc);
sb_wca(win, boxp->ur, attr, 1);
/* draw left and right sides */
for (r = 1; r < maxr; ++r) {
sb_move(win, r, 0);
sb_wca(win, boxp->lbar, attr, 1);
sb_move(win, r, maxc);
sb_wca(win, boxp->rbar, attr, 1);
}
/* draw bottom row */
sb_move(win, maxr, 0);
sb_wca(win, boxp->ll, attr, 1);
sb_move(win, maxr, 1);
sb_wca(win, boxp->bbar, attr, x);
sb_move(win, maxr, maxc);
sb_wca(win, boxp->lr, attr, 1);
return SB_OK;
}
makefile for SBUF.LIB
───────────────────────────────────────────────────────────────────────────
# makefile for SBUF.LIB (screen-buffer library)
LLIB = c:\lib\local
LINC = c:\include\local
OBJS = sb_box.obj sb_fill.obj sb_init.obj sb_move.obj sb_new.obj \
sb_put.obj sb_read.obj sb_scrl.obj sb_show.obj sb_write.obj
MODS = sb_box sb_fill sb_init sb_move sb_new.obj sb_put sb_read \
sb_scrl sb_show sb_write
$(LINC)\sbuf.h: sbuf.h
copy sbuf.h $(LINC)\sbuf.h
$(LINC)\box.h: box.h
copy box.h $(LINC)\box.h
sb_box.obj: sb_box.c $(LINC)\sbuf.h $(LINC)\video.h $(LINC)\box.h
sb_fill.obj: sb_fill.c $(LINC)\sbuf.h
sb_init.obj: sb_init.c $(LINC)\sbuf.h
sb_move.obj: sb_move.c $(LINC)\sbuf.h
sb_new.obj: sb_new.c $(LINC)\sbuf.h
sb_put.obj: sb_put.c $(LINC)\sbuf.h
sb_read.obj: sb_read.c $(LINC)\sbuf.h
sb_scrl.obj: sb_scrl.c $(LINC)\sbuf.h
sb_show.obj: sb_show.c $(LINC)\sbuf.h
sb_write.obj: sb_write.c $(LINC)\sbuf.h
sbuf.lib: $(OBJS)
del sbuf.lib
lib sbuf +$(MODS);
copy sbuf.lib $(LLIB)
sb_test
───────────────────────────────────────────────────────────────────────────
/*
* sb_test -- driver for screen-buffer interface functions
*/
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <local\std.h>
#include <local\keydefs.h>
#include <local\sbuf.h>
#include <local\video.h>
#include <local\box.h>
#include "sb_test.h"
#define BEL 7
extern struct BUFFER Sbuf;
main(argc, argv)
int argc;
char *argv[];
{
char *s, line[MAXLINE];
int k;
short i;
FILE *fp;
char fname[MAXPATH];
struct REGION *cmnd, *stat, *text, *help, *curwin;
unsigned char cmndattr, statattr, textattr, helpattr, curattr;
unsigned char ch, userattr;
/* function prototypes */
int sb_init();
int sb_move(struct REGION *, short, short);
struct REGION *sb_new(short, short, short, short);
int sb_putc(struct REGION *, unsigned char);
int sb_puts(struct REGION *, char *);
int sb_show(short);
int sb_fill(struct REGION *, unsigned char, unsigned char);
char *get_fname(struct REGION *, char *, short);
getstate();
readca(&ch, &userattr, Vpage);
/* set up the screen buffer */
if (sb_init() == SB_ERR) {
fprintf(stderr, "Bad UPDATEMODE value in environment\n");
exit(1);
}
/* set up windows and scrolling regions */
cmnd = sb_new(CMND_ROW, CMND_COL, CMND_HT, CMND_WID);
stat = sb_new(STAT_ROW, STAT_COL, STAT_HT, STAT_WID);
text = sb_new(TEXT_ROW, TEXT_COL, TEXT_HT, TEXT_WID);
help = sb_new(HELP_ROW, HELP_COL, HELP_HT, HELP_WID);
text->wflags |= SB_SCROLL;
sb_set_scrl(help, 1, 1, HELP_HT - 1, HELP_WID - 1);
/* display each primary window in its own attribute */
cmndattr = GRN;
statattr = (WHT << 4) | BLK;
textattr = (BLU << 4) | CYAN;
helpattr = (GRN << 4) | YEL;
sb_fill(cmnd, ' ', cmndattr);
if (sb_move(cmnd, 0, 0) == SB_OK)
sb_puts(cmnd, "SB_TEST (Version 1.0)");
sb_fill(stat, ' ', statattr);
if (sb_move(stat, 0, 0) == SB_OK)
sb_puts(stat, "*** STATUS AREA ***");
for (i = 0; i <= text->r1 - text->r0; ++i) {
sb_move(text, i, 0);
sb_wca(text, i + 'a', textattr,
text->c1 - text->c0 + 1);
}
if (sb_move(text, 10, 25) == SB_OK)
sb_puts(text, " *** TEXT DISPLAY AREA *** ");
sb_show(Vpage);
curwin = text;
curattr = textattr;
/* respond to user commands */
while ((k = getkey()) != K_ESC) {
switch (k) {
case K_UP:
sb_scrl(curwin, 1);
break;
case K_DOWN:
sb_scrl(curwin, -1);
break;
case K_PGUP:
sb_scrl(curwin, curwin->sr1 - curwin->sr0);
break;
case K_PGDN:
sb_scrl(curwin, -(curwin->sr1 - curwin->sr0));
break;
case K_ALTC:
/* clear the current window */
sb_fill(curwin, ' ', curattr);
break;
case K_ALTH:
/* display help */
curwin = help;
curattr = helpattr;
for (i = 0; i < help->r1 - help->r0; ++i) {
sb_move(help, i, 0);
sb_wca(help, i + 'a', helpattr,
help->c1 - help->c0 + 1);
}
sb_box(help, BOXBLK, helpattr);
break;
case K_ALTS:
/* fill the command area with letters */
curwin = stat;
curattr = statattr;
sb_fill(stat, 's', statattr);
break;
case K_ALTT:
/* fill the text area */
curwin = text;
curattr = textattr;
for (i = 0; i <= text->r1 - text->r0; ++i) {
sb_move(text, i, 0);
sb_wca(text, i + 'a', textattr,
text->c1 - text->c0 + 1);
}
break;
case K_ALTR:
/* read a file into the current window */
sb_fill(stat, ' ', statattr);
sb_move(stat, 0, 0);
sb_puts(stat, "File to read: ");
sb_show(Vpage);
(void)get_fname(stat, fname, MAXPATH);
if ((fp = fopen(fname, "r")) == NULL) {
sb_fill(stat, ' ', statattr);
sb_move(stat, 0, 0);
sb_puts(stat, "Cannot open ");
sb_puts(stat, fname);
}
else {
sb_fill(stat, ' ', statattr);
sb_move(stat, 0, 0);
sb_puts(stat, "File: ");
sb_puts(stat, fname);
sb_show(Vpage);
sb_fill(text, ' ', textattr);
sb_move(text, 0, 0);
putcur(text->r0, text->c0, Vpage);
while ((s = fgets(line, MAXLINE,
fp)) != NULL) {
if (sb_puts(text, s) == SB_ERR) {
clrscrn(userattr);
putcur(0, 0, Vpage);
fprintf(stderr,
"puts error\n");
exit(1);
}
sb_show(Vpage);
}
if (ferror(fp)) {
putcur(text->r0, text->c0, Vpage);
fprintf(stderr,
"Error reading file\n");
}
fclose(fp);
}
break;
default:
/* say what? */
fputc(BEL, stderr);
continue;
}
if ((Sbuf.flags & SB_DELTA) == SB_DELTA)
sb_show(Vpage);
}
clrscrn(userattr);
putcur(0, 0, Vpage);
exit(0);
}
/*
* get_fname -- get a filename from the user
*/
char *
get_fname(win, path, lim)
struct REGION *win;
char *path;
short lim;
{
int ch;
char *s;
s = path;
sb_show(Vpage);
while ((ch = getch()) != K_RETURN) {
if (ch == '\b')
--s;
else {
sb_putc(win, ch);
*s++ = ch;
}
sb_show(Vpage);
}
*s = '\0';
return (path);
}
ansi.h
───────────────────────────────────────────────────────────────────────────
/*
* ansi.h -- header file for ANSI driver information and
* macro versions of the ANSI control sequences
*/
/*****************/
/** ANSI macros **/
/*****************/
/* cursor position */
#define ANSI_CUP(r, c) printf("\x1B[%d;%dH", r, c)
#define ANSI_HVP(r, c) printf("\x1B[%d;%df", r, c)
/* cursor up, down, forward, back */
#define ANSI_CUU(n) printf("\x1B[%dA", n)
#define ANSI_CUD(n) printf("\x1B[%dB", n)
#define ANSI_CUF(n) printf("\x1B[%dC", n)
#define ANSI_CUB(n) printf("\x1B[%dD", n)
/* device status report (dumps position data into keyboard buffer) */
#define ANSI_DSR printf("\x1B[6n")
/* save and restore cursor position */
#define ANSI_SCP fputs("\x1B[s", stdout)
#define ANSI_RCP fputs("\x1B[u", stdout)
/* erase display and line */
#define ANSI_ED fputs("\x1B[2J", stdout);
#define ANSI_EL fputs("\x1B[K", stdout)
/* set graphic rendition */
#define ANSI_SGR(a) printf("\x1B[%dm", a)
/* set and reset modes */
#define ANSI_SM(m) printf("\x1B[=%dh", m)
#define ANSI_RM(m) printf("\x1B[=%dl", m)
/**********************/
/** ANSI color codes **/
/**********************/
/* special settings */
#define ANSI_NORMAL 0
#define ANSI_BOLD 1
#define ANSI_BLINK 5
#define ANSI_REVERSE 7
#define ANSI_INVISIBLE 8
/* shift values */
#define ANSI_FOREGROUND 30
#define ANSI_BACKGROUND 40
/* basic colors */
#define ANSI_BLACK 0
#define ANSI_RED 1
#define ANSI_GREEN 2
#define ANSI_BROWN 3
#define ANSI_BLUE 4
#define ANSI_MAGENTA 5
#define ANSI_CYAN 6
#define ANSI_WHITE 7
/*****************************/
/** modes for set and reset **/
/*****************************/
#define ANSI_M40 0
#define ANSI_C40 1
#define ANSI_M80 2
#define ANSI_C80 3
#define ANSI_C320 4
#define ANSI_M320 5
#define ANSI_M640 6
#define ANSI_WRAP 7
/* attribute "position" type */
typedef enum {
FGND, BKGND, BDR
} POSITION;
cpr
───────────────────────────────────────────────────────────────────────────
/*
* cpr -- report where the cursor is located
* The position information is placed in the keyboard buffer in the
* form ESC[rr;ccR, where ESC is the value of the ESCAPE character
* and r and c represent decimal values of row and column data.
*/
#include <local\ansi.h>
void ansi_cpr(row, col)
int *row,
*col;
{
int i;
/* request a cursor position report */
ANSI_DSR;
/* toss the ESC and '[' */
(void) getkey();
(void) getkey();
/* read the row number */
*row = 10 * (getkey() - '0');
*row = *row + getkey() - '0';
/* toss the ';' separator */
(void) getkey();
/* read the column number */
*col = 10 * (getkey() - '0');
*col = *col + getkey() - '0';
/* toss the trailing ('R') and return */
(void) getkey();
(void) getkey();
return;
}
ansi_tst
───────────────────────────────────────────────────────────────────────────
/*
* ansi_tst -- verify that the ANSI.SYS driver is loaded
* (prints message and exits if ANSI driver not working)
*/
#include <stdio.h>
#include <local\ansi.h>
#include <local\video.h>
#define TST_ROW 2
#define TST_COL 75
void
ansi_tst()
{
int row, col;
static char *help[] = {
"\n",
"ANSI.SYS device driver not loaded:\n",
" 1. Copy ANSI.SYS to your system disk.\n",
" 2. Add the line device=ansi.sys to your\n",
" CONFIG.SYS file and reboot your machine.\n",
NULL
};
char **msg;
extern int getstate();
extern int readcur(int *, int *, int);
getstate();
ANSI_CUP(TST_ROW, TST_COL);
readcur(&row, &col, Vpage);
if (row != TST_ROW - 1 || col != TST_COL - 1) {
for (msg = help; *msg != NULL; ++msg)
fputs(*msg, stderr);
exit(1);
}
return;
}
sc
───────────────────────────────────────────────────────────────────────────
/*
* SetColor (sc) -- set foreground, background, and border attributes
* on systems equipped with color display systems
*
* Usage: sc [foreground [background [border]]]
* sc [attribute]
*/
#include <stdio.h>
#include <dos.h>
#include <local\std.h>
#include <local\ansi.h>
#include <local\ibmcolor.h>
main(argc, argv)
int argc;
char *argv[];
{
void ansi_tst();
BOOLEAN iscolor();
extern void setattr();
extern void menumode();
ansi_tst();
if (iscolor() == FALSE) {
fprintf(stderr, "\n\nSystem not in a color text mode.\n");
fprintf(stderr, "Use the MODE command to set the mode.\n");
exit(2);
}
/* process either batch or interactive commands */
if (argc > 1)
/* batch mode processing */
parse(argc, argv);
else
/* no command-line args -- interactive mode */
menumode();
ANSI_ED;
exit (0);
} /* end main() */
/*
* iscolor -- return TRUE if a color display system is
* in use and is set to one of the text modes
*/
#include <local\std.h>
#include <local\video.h>
BOOLEAN
iscolor()
{
getstate();
if (Vmode != CGA_C40 || Vmode != CGA_C80)
return TRUE;
return FALSE;
}
parse
───────────────────────────────────────────────────────────────────────────
/*
* parse -- process a list of attribute specifications
*/
#include <stdio.h>
#include <local\ansi.h>
#include <local\std.h>
#include <local\ibmcolor.h>
/* buffer length for string comparisons */
#define NCHARS 3
#define C_MASK 0x7
void parse(nargs, argvec)
int nargs; /* number of argument vectors */
char *argvec[]; /* pointer to the argument vector array */
{
int i, intensity;
int attribute;
POSITION pos;
char str[NCHARS + 1];
extern int colornum();
extern void setattr();
/* clear all attributes */
ANSI_SGR(ANSI_NORMAL);
/* look for a single attribute specification */
if (nargs == 2) {
attribute = colornum(argvec[1]);
switch (attribute) {
case IBM_NORMAL:
palette(0, IBM_BLACK);
return;
case IBM_REVERSE:
ANSI_SGR(ANSI_REVERSE);
palette(0, IBM_WHITE);
return;
case IBM_INVISIBLE:
ANSI_SGR(ANSI_INVISIBLE);
return;
case IBM_BLINK:
ANSI_SGR(ANSI_BLINK);
return;
}
}
/* must be separate attribute specifications */
pos = FGND;
intensity = 0;
for (i = 1; i < nargs; ++i) {
attribute = colornum(argvec[i]);
if (attribute == -1) {
ANSI_ED;
fprintf(stderr, "\nIllegal parameter\n");
exit (2);
}
if (attribute == IBM_BRIGHT) {
intensity = IBM_BRIGHT;
continue;
}
setattr(pos, attribute | intensity);
if (pos == FGND)
pos = BKGND;
else if (pos == BKGND)
pos = BDR;
intensity = 0;
}
return;
}
ibmcolor.h
───────────────────────────────────────────────────────────────────────────
/*
* ibmcolor.h
*/
/* basic IBM color codes */
#define IBM_BLACK 0
#define IBM_BLUE 1
#define IBM_GREEN 2
#define IBM_CYAN 3
#define IBM_RED 4
#define IBM_MAGENTA 5
#define IBM_BROWN 6
#define IBM_WHITE 7
/* color modifiers */
#define IBM_BRIGHT 8
#define IBM_BLINK 16
/* special attribute codes */
#define IBM_NORMAL 7
#define IBM_REVERSE 112
#define IBM_INVISIBLE 128
colornum
───────────────────────────────────────────────────────────────────────────
/*
* colornum -- return the IBM number for the color
* presented as a string; return -1 if no match.
*/
#include <stdio.h>
#include <string.h>
#include <local\ibmcolor.h>
#define NCHARS 3
int colornum(name)
char *name;
{
int n;
static struct color_st {
char *c_name;
int c_num;
} colortab[] = {
"black", IBM_BLACK,
"blue", IBM_BLUE,
"green", IBM_GREEN,
"cyan", IBM_CYAN,
"red", IBM_RED,
"magenta", IBM_MAGENTA,
"brown", IBM_BROWN,
"white", IBM_WHITE,
"normal", IBM_NORMAL,
"bright", IBM_BRIGHT,
"light", IBM_BRIGHT,
"bold", IBM_BRIGHT,
"yellow", IBM_BROWN + IBM_BRIGHT,
"blink", IBM_BLINK,
"reverse", IBM_REVERSE,
"invisible", IBM_INVISIBLE,
NULL, (-1)
};
(void) strlwr(name);
for (n = 0; colortab[n].c_name != NULL; ++n)
if ((strncmp(name, colortab[n].c_name, NCHARS)) == 0)
return (colortab[n].c_num);
return (-1);
}
setattr
───────────────────────────────────────────────────────────────────────────
/*
* setattr -- execute an attribute update
*/
#include <stdio.h>
#include <local\ansi.h>
#include <local\ibmcolor.h>
#define C_MASK 0x7
void setattr(pos, attr)
POSITION pos; /* attribute position */
int attr; /* composite attribute number (base attr | intensity) */
{
static int ibm2ansi[] = {
ANSI_BLACK, ANSI_BLUE, ANSI_GREEN, ANSI_CYAN,
ANSI_RED, ANSI_MAGENTA, ANSI_BROWN, ANSI_WHITE
};
switch (pos) {
case FGND:
if (attr & IBM_BRIGHT)
ANSI_SGR(ANSI_BOLD);
ANSI_SGR(ibm2ansi[attr & C_MASK] + ANSI_FOREGROUND);
break;
case BKGND:
if (attr & IBM_BRIGHT)
ANSI_SGR(ANSI_BLINK);
ANSI_SGR(ibm2ansi[attr & C_MASK] + ANSI_BACKGROUND);
break;
case BDR:
palette(0, attr);
break;
}
return;
}
menumode
───────────────────────────────────────────────────────────────────────────
/*
* menumode -- process user commands interactively
*/
#include <stdio.h>
#include <local\ansi.h>
#include <local\ibmcolor.h>
#include <local\keydefs.h>
/* maximum color number */
#define MAX_CNUM 15
void menumode()
{
int ch;
int foreground, background, border;
extern void setattr();
extern void sc_cmds();
/* default attributes */
foreground = IBM_WHITE;
background = IBM_BLACK;
border = IBM_BLACK;
ANSI_SGR(ANSI_NORMAL);
setattr(FGND, foreground);
setattr(BKGND, background);
ANSI_ED;
palette(0, border);
sc_cmds(foreground, background, border);
while ((ch = getkey()) != K_RETURN) {
switch (ch) {
case K_F1:
/* decrement foreground color */
if (--foreground < 0)
foreground = MAX_CNUM;
break;
case K_F2:
/* increment foreground color */
if (++foreground > MAX_CNUM)
foreground = 0;
break;
case K_F3:
/* decrement background color */
if (--background < 0)
background = MAX_CNUM;
break;
case K_F4:
/* increment background color */
if (++background > MAX_CNUM)
background = 0;
break;
case K_F5:
/* decrement border color */
if (--border < 0)
border = MAX_CNUM;
break;
case K_F6:
/* increment border color number */
if (++border > MAX_CNUM)
border = 0;
break;
default:
continue;
}
ANSI_SGR(ANSI_NORMAL);
setattr(FGND, foreground);
setattr(BKGND, background);
palette(0, border);
ANSI_ED;
sc_cmds(foreground, background, border);
}
return;
}
sc_cmds
───────────────────────────────────────────────────────────────────────────
/*
* sc_cmds -- display command summary
*/
#include <stdio.h>
#include <local\ansi.h>
void sc_cmds(fg, bkg, bdr)
int fg, bkg, bdr;
{
static char *color_xlat[] = {
"Black (0)", "Blue (1)", "Green (2)", "Cyan (3)",
"Red (4)", "Magenta (5)", "Brown (6)", "White (7)",
"Grey (8)", "Light blue (9)", "Light green (10)",
"Light cyan (11)", "Light red (12)", "Light magenta (13)",
"Yellow (14)", "Bright white (15)"
};
ANSI_CUP(2, 29);
fputs("*** SetColor (SC) ***", stdout);
ANSI_CUP(4, 17);
fputs("Attribute Decrement Increment Current Value", stdout);
ANSI_CUP(5, 17);
fputs("--------- --------- --------- -------------", stdout);
ANSI_CUP(6, 17);
fputs("Foreground F1 F2", stdout);
ANSI_CUP(7, 17);
fputs("Background F3 F4", stdout);
ANSI_CUP(8, 17);
fputs("Border F5 F6", stdout);
ANSI_CUP(6, 50);
fputs(color_xlat[fg], stdout);
ANSI_CUP(7, 50);
fputs(color_xlat[bkg], stdout);
ANSI_CUP(8, 50);
fputs(color_xlat[bdr], stdout);
ANSI_CUP(10, 17);
fputs("Type RETURN to exit. SetColor/Version 2.2", stdout);
return;
} /* end sc_cmds() */
makefile for the SC program
───────────────────────────────────────────────────────────────────────────
# makefile for the SC program
LINC=c:\include\local
LLIB=c:\lib\local
iscolor.obj: iscolor.c
msc $*;
lib $(LLIB)\util -+$*;
colornum.obj: colornum.c $(LINC)\ansi.h $(LINC)\ibmcolor.h
msc $*;
lib color -+$*;
sc_cmds.obj: sc_cmds.c $(LINC)\ansi.h
msc $*;
lib color -+$*;
menumode.obj: menumode.c $(LINC)\ansi.h $(LINC)\ibmcolor.h
$(LINC)\keydefs.h
msc $*;
lib color -+$*;
parse.obj: parse.c $(LINC)\ansi.h $(LINC)\ibmcolor.h
msc $*;
lib color -+$*;
setattr.obj: setattr.c $(LINC)\ansi.h $(LINC)\ibmcolor.h
msc $*;
lib color -+$*;
sc.obj: sc.c $(LINC)\ansi.h $(LINC)\ibmcolor.h $(LINC)\keydefs.h
msc $*;
sc.exe: sc.obj color.lib $(LLIB)\ansi.lib $(LLIB)\util.lib
link $*, $*, nul, color $(LLIB)\ansi $(LLIB)\util $(LLIB)\bios
$(LLIB)\dos;
useattr
───────────────────────────────────────────────────────────────────────────
/*
* userattr -- set video attributes to user-specified values
* (DOS environment parameters) or to reasonable defaults and
* return success or a failure indication for bad attributes
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <local\std.h>
#include <local\ansi.h>
#include <local\ibmcolor.h>
int userattr(foreground, background, border)
char *foreground, *background, *border;
{
char *s;
static int attrset();
if ((s = getenv("FGND")) == NULL)
s = foreground;
if (attrset(FGND, s) == -1)
return FAILURE;
if ((s = getenv("BKGND")) == NULL)
s = background;
if (attrset(BKGND, s) == -1)
return FAILURE;
if ((s = getenv("BORDER")) == NULL)
s = border;
if (attrset(BDR, s) == -1)
return FAILURE;
return SUCCESS;
}
/*
* attrset -- parse the color spec and try to set it.
* return 0 if OK and -1 upon error (bad color number)
*/
static int attrset(apos, str)
POSITION apos;
char *str;
{
int attr;
extern int colornum();
extern void setattr();
if ((attr = colornum(strtok(str, " \t"))) == IBM_BRIGHT)
attr |= colornum(strtok(NULL, " \t"));
if (attr >= 0)
setattr(apos, attr);
else
return (-1);
return (0);
}
makefile for the VF program
───────────────────────────────────────────────────────────────────────────
# makefile for the ViewFile (VF) program
# (compile using "compact" memory model)
LIB = c:\lib
LLIB = c:\lib\local
OBJS = vf_list.obj vf_dspy.obj vf_srch.obj vf_util.obj vf_cmd.obj \
message.obj getstr.obj
vf_list.obj: vf_list.c vf.h
vf_dspy.obj: vf_dspy.c vf.h
vf_srch.obj: vf_srch.c vf.h
vf_util.obj: vf_util.c vf.h
vf_cmd.obj: vf_cmd.c vf.h
getstr.obj: getstr.c
message.obj: message.c message.h
cvf.lib: $(OBJS)
del cvf.lib
lib cvf +$(OBJS);
vf.obj: vf.c
vf.exe: vf.obj cvf.lib $(LLIB)\cutil.lib $(LLIB)\cdos.lib $(LLIB)\cbios.lib
link $* $(LIB)\csetargv, $*, NUL, cvf $(LLIB)\cutil $(LLIB)\cdos \
$(LLIB)\cbios;
vf
───────────────────────────────────────────────────────────────────────────
/*
* vf -- view a file using a full-screen window onto
* an in-memory text file buffer
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <local\std.h>
#include <local\video.h>
#include "vf.h"
extern int setctype(int, int);
int Startscan, Endscan; /* cursor scan line range */
unsigned char Attr; /* primary display attribute */
unsigned char Revattr; /* reverse video for highlighting */
unsigned char Usrattr; /* user's original attribute */
main(argc, argv)
int argc;
char **argv;
{
int ch;
static char pgm[MAXNAME + 1] = { "vf" };
BOOLEAN errflag;
BOOLEAN numbers;
int errcode;
FILE *fp;
extern char *optarg;
extern int optind;
void clean();
/* external function prototypes */
extern void getpname(char *, char *);
extern void fixtabs(int);
extern void initmsg(int, int, int, unsigned char, int);
extern int getopt(int, char **, char *);
extern int vf_cmd(FILE *, char *, BOOLEAN);
extern int getctype(int *, int *, int);
errcode = 0;
getstate();
fixtabs(TABWIDTH);
/* get program name from DOS (version 3.00 and later) */
if (_osmajor >= 3)
getpname(*argv, pgm);
/* be sure we have needed DOS support */
if (_osmajor < 2) {
fprintf(stderr, "%s requires DOS 2.00 or later\n", pgm);
exit(1);
}
/* process optional arguments first */
errflag = numbers = FALSE;
while ((ch = getopt(argc, argv, "n")) != EOF)
switch (ch) {
case 'n':
/* turn on line numbering */
numbers = TRUE;
break;
case '?':
/* bad option */
errflag = TRUE;
break;
}
/* check for command-line errors */
argc -= optind;
argv += optind;
if (errflag == TRUE || argc == 0) {
fprintf(stderr, "Usage: %s [-n] file...\n", pgm);
exit(1);
}
/* get current video attribute and set VF attributes */
getstate();
readca(&ch, &Usrattr, Vpage); /* save user's attribute settings */
Attr = (BLU << 4) | CYAN; /* basic text attributes */
Revattr = (CYAN << 4) | BLK; /* reverse video for highlighting */
clrscrn(Attr);
/* save user's cursor shape */
getctype(&Startscan, &Endscan, Vpage);
setctype(MAXSCAN, MAXSCAN);
/* set up the message line manager */
initmsg(MSGROW, MSGCOL, Maxcol[Vmode] - MSGCOL, Attr, Vpage);
/* display first screen page */
putcur(0, 0, Vpage);
putstr("ViewFile/1.0 H=Help Q=Quit Esc=Next", Vpage);
putcur(1, 0, Vpage);
writea(Revattr, Maxcol[Vmode], Vpage);
for (; argc-- > 0; ++argv) {
if ((fp = fopen(*argv, "r")) == NULL) {
fprintf(stderr, "%s: cannot open %s -- ", pgm, *argv);
perror("");
++errcode;
continue;
}
if (vf_cmd(fp, *argv, numbers) != 0)
break;
}
clean();
exit(errcode);
}
/*
* clean -- restore the user's original conditions
*/
void
clean()
{
/* set screen to user's attribute */
clrscrn(Attr);
putcur(0, 0, Vpage);
/* restore user's cursor shape */
setctype(Startscan, Endscan);
}
vf.h
───────────────────────────────────────────────────────────────────────────
/*
* vf.h -- header for ViewFile program
*/
#define OVERLAP 2
#define MAXSTR 40
#define MSGROW 0
#define MSGCOL 40
#define HEADROW 1
#define TOPROW 2
#define NROWS 23
#define SHIFTWIDTH 20
#define N_NODES 256
#define TABWIDTH 8
#define MAXSCAN 14
typedef enum {
FORWARD, BACKWARD
} DIRECTION;
/* doubly-linked list structure */
typedef struct dnode_st {
struct dnode_st *d_next;
struct dnode_st *d_prev;
unsigned int d_lnum; /* file-relative line number */
unsigned short d_flags; /* miscellaneous line flags */
char *d_line; /* pointer to text buffer */
} DNODE;
/* line flags */
#define STANDOUT 0x1
vf_cmd
───────────────────────────────────────────────────────────────────────────
/*
* vf_cmd -- ViewFile command processor
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <local\std.h>
#include <local\keydefs.h>
#include <local\video.h>
#include "vf.h"
#include "message.h"
extern unsigned char Attr;
int
vf_cmd(fp, fname, numbers)
FILE *fp;
char *fname;
BOOLEAN numbers;
{
register int i; /* general index */
unsigned int offset; /* horizontal scroll offset */
unsigned int n; /* relative line number */
int memerr; /* flag for memory allocation errors */
char *s, lbuf[MAXLINE]; /* input line buffer and pointer */
int k; /* key code (see keydefs.h) */
int radix = 10; /* base for number-to-character
* conversions */
char number[17]; /* buffer for conversions */
int errcount = 0; /* error counter */
DNODE *tmp; /* pointer to buffer control nodes */
DIRECTION srchdir; /* search direction */
char *ss; /* pointer to search string */
static char srchstr[MAXSTR] = { "" }; /* search string buffer */
DNODE *head; /* pointer to starting node of
* text buffer list */
DNODE *current; /* pointer to the current
* node (text line) */
static DNODE *freelist; /* pointer to starting node of
* "free" list */
/* initialized to 0 at runtime; retains
* value */
/* function prototypes */
static void prtshift(int, int);
extern DNODE *vf_mklst();
extern DNODE *vf_alloc(int);
extern DNODE *vf_ins(DNODE *, DNODE *);
extern DNODE *vf_del(DNODE *, DNODE *);
extern DNODE *search(DNODE *, DIRECTION, char *);
extern DNODE *gotoln(DNODE *);
extern char *getxline(char *, int, FILE *);
extern char *getsstr(char *);
extern int clrscrn(unsigned char);
extern void showhelp(unsigned char);
extern void clrmsg();
extern void vf_dspy(DNODE *, DNODE *, int, BOOLEAN);
extern int putstr(char *, int);
extern int writec(char, int, int);
extern char *nlerase(char *);
/* display the file name */
offset = 0;
putcur(HEADROW, 0, Vpage);
writec(' ', Maxcol[Vmode], Vpage);
putstr("File: ", Vpage);
putstr(fname, Vpage);
/* establish the text buffer */
memerr = 0;
if ((head = vf_mklst()) == NULL)
++memerr;
if (freelist == NULL && (freelist = vf_alloc(N_NODES)) == NULL)
++memerr;
if (memerr) {
clean();
fprintf(stderr, "Memory allocation error\n");
exit(1);
}
/* read the file into the buffer */
current = head;
n = 0;
while ((s = getxline(lbuf, MAXLINE, fp)) != NULL) {
/* add a node to the list */
if ((freelist = vf_ins(current, freelist)) == NULL)
++memerr;
current = current->d_next;
/* save the received text in a line buffer */
if ((current->d_line = strdup(nlerase(s))) == NULL)
++memerr;
if (memerr) {
clean();
fprintf(stderr, "File too big to load\n");
exit(1);
}
current->d_lnum = ++n;
current->d_flags = 0;
}
/* show the file size as a count of lines */
putstr(" (", Vpage);
putstr(itoa(current->d_lnum, number, radix), Vpage);
putstr(" lines)", Vpage);
prtshift(offset, Vpage);
current = head->d_next;
vf_dspy(head, current, offset, numbers);
/* process user commands */
while ((k = getkey()) != K_ESC) {
clrmsg();
switch (k) {
case 'b':
case 'B':
case K_HOME:
current = head->d_next;
break;
case 'e':
case 'E':
case K_END:
current = head->d_prev;
i = NROWS - 1;
while (i-- > 0)
if (current->d_prev != head->d_next)
current = current->d_prev;
break;
case K_PGUP:
case 'u':
case 'U':
i = NROWS - OVERLAP;
while (i-- > 0)
if (current != head->d_next)
current = current->d_prev;
break;
case K_PGDN:
case 'd':
case 'D':
i = NROWS - OVERLAP;
while (i-- > 0)
if (current != head->d_prev)
current = current->d_next;
break;
case K_UP:
case '-':
if (current == head->d_next)
continue;
current = current->d_prev;
break;
case K_DOWN:
case '+':
if (current == head->d_prev)
continue;
current = current->d_next;
break;
case K_RIGHT:
case '>':
case '.':
if (offset < MAXLINE - SHIFTWIDTH)
offset += SHIFTWIDTH;
prtshift(offset, Vpage);
break;
case K_LEFT:
case '<':
case ',':
if ((offset -= SHIFTWIDTH) < 0)
offset = 0;
prtshift(offset, Vpage);
break;
case K_ALTG:
case 'g':
case 'G':
if ((tmp = gotoln(head)) == NULL)
continue;
current = tmp;
break;
case K_ALTH:
case 'h':
case 'H':
case '?':
showhelp(Attr);
break;
case K_ALTN:
case 'n':
case 'N':
numbers = (numbers == TRUE) ? FALSE : TRUE;
break;
case K_ALTQ:
case 'q':
case 'Q':
clrscrn(Attr);
putcur(0, 0, Vpage);
return (-1);
case 'r':
case 'R':
case '\\':
srchdir = BACKWARD;
ss = getsstr(srchstr);
if (ss == NULL)
/* cancel search */
break;
if (strlen(ss) > 0)
strcpy(srchstr, ss);
if ((tmp = search(current, srchdir,
srchstr)) == NULL)
continue;
current = tmp;
break;
case 's':
case 'S':
case '/':
srchdir = FORWARD;
ss = getsstr(srchstr);
if (ss == NULL)
/* cancel search */
break;
if (strlen(ss) > 0)
strcpy(srchstr, ss);
if ((tmp = search(current, srchdir,
srchstr)) == NULL)
continue;
current = tmp;
break;
default:
/* ignore all other keys */
continue;
}
vf_dspy(head, current, offset, numbers);
}
clrmsg();
/* release the allocated text buffer memory */
while (head->d_next != head) {
/* release text buffer */
free(head->d_next->d_line);
/* put node back on the freelist */
freelist = vf_del(head->d_next, freelist);
}
/* release the list header node */
free((char *)head);
return (errcount);
}
/*
* prtshift -- display the number of columns of horizontal shift
*/
#define SHFTDSP 5
static void
prtshift(amt, pg)
int amt, pg;
{
char number[17];
int radix = 10;
/* clear the shift display area */
putcur(1, Maxcol[Vmode] - 1 - SHFTDSP, pg);
writec(' ', SHFTDSP, pg);
/* display the new shift amount, if any */
if (amt > 0) {
putstr(itoa(amt, number, radix), pg);
putstr("->", pg);
}
}
putstr
───────────────────────────────────────────────────────────────────────────
/*
* putstr -- display a character string in the
* prevailing video attribute and return number
* characters displayed
*/
int
putstr(s, pg)
register char *s;
int pg;
{
int r, c, c0;
readcur(&r, &c, pg);
for (c0 = c; *s != '\0'; ++s, ++c) {
putcur(r, c, pg);
writec(*s, 1, pg);
}
putcur(r, c, pg);
return (c - c0);
}
vf_list
───────────────────────────────────────────────────────────────────────────
/*
* vf_list -- linked list management functions
*/
#include <stdio.h>
#include <malloc.h>
#include "vf.h"
/*
* vf_mklst -- create a new list by allocating a node,
* making it point to itself, and setting its values
* to zero (appropriately cast)
*/
DNODE *
vf_mklst()
{
DNODE *new;
new = (DNODE *)malloc(sizeof (DNODE));
if (new != NULL) {
new->d_next = new;
new->d_prev = new;
new->d_lnum = new->d_flags = 0;
new->d_line = (char *)NULL;
}
return (new);
} /* end vf_mklst() */
/*
* vf_alloc -- create a pool of available nodes
*/
DNODE *
vf_alloc(n)
int n;
{
register DNODE *new;
register DNODE *tmp;
/* allocate a block of n nodes */
new = (DNODE *)malloc(n * sizeof (DNODE));
/* if allocation OK, string the nodes in one direction */
if (new != NULL) {
for (tmp = new; 1 + tmp - new < n; tmp = tmp->d_next)
tmp->d_next = tmp + 1;
tmp->d_next = (DNODE *)NULL;
}
return (new); /* pointer to free list */
} /* end vf_alloc() */
/*
* vf_ins -- insert a node into a list after the specified node
*/
DNODE *
vf_ins(node, avail)
DNODE *node, *avail;
{
DNODE *tmp;
DNODE *vf_alloc(int);
/*
* check freelist -- get another block of nodes
* if the list is almost empty
*/
if (avail->d_next == NULL)
if ((avail->d_next = vf_alloc(N_NODES)) == NULL)
/* not enough memory */
return (DNODE *)NULL;
/* get a node from the freelist */
tmp = avail;
avail = avail->d_next;
/* insert the node into the list after node */
tmp->d_prev = node;
tmp->d_next = node->d_next;
node->d_next->d_prev = tmp;
node->d_next = tmp;
/* point to next node in the freelist */
return (avail);
} /* end vf_ins() */
/*
* vf_del -- delete a node from a list
*/
DNODE *
vf_del(node, avail)
DNODE *node, *avail;
{
/* unlink the node from the list */
node->d_prev->d_next = node->d_next;
node->d_next->d_prev = node->d_prev;
/* return the deleted node to the freelist */
node->d_next = avail;
avail = node;
/* point to the new freelist node */
return (avail);
} /* end vf_del() */
vf_dspy
───────────────────────────────────────────────────────────────────────────
/*
* vf_dspy -- display a screen page
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <local\video.h>
#include <local\std.h>
#include <local\bioslib.h>
#include "vf.h"
/* number field width */
#define NFW 8
void
vf_dspy(buf, lp, os, numbers)
DNODE *buf;
register DNODE *lp;
int os;
BOOLEAN numbers;
{
register int i;
int j;
int textwidth;
char *cp;
char nbuf[NFW + 1];
textwidth = Maxcol[Vmode];
if (numbers == TRUE)
textwidth -= NFW;
for (i = 0; i < NROWS; ++i) {
putcur(TOPROW + i, 0, Vpage);
cp = lp->d_line;
if (numbers == TRUE) {
sprintf(nbuf, "%6u", lp->d_lnum);
putfld(nbuf, NFW, Vpage);
putcur(TOPROW + i, NFW, Vpage);
}
if (os < strlen(cp))
putfld(cp + os, textwidth, Vpage);
else
writec(' ', textwidth, Vpage);
if (lp == buf->d_prev) {
++i;
break; /* no more displayable lines */
}
else
lp = lp->d_next;
}
/* clear and mark any unused lines */
for ( ; i < NROWS; ++i) {
putcur(i + TOPROW, 0, Vpage);
writec(' ', Maxcol[Vmode], Vpage);
writec('~', 1, Vpage);
}
return;
}
putfld
───────────────────────────────────────────────────────────────────────────
/*
* putfld -- display a string in the prevailing
* video attribute while compressing runs of a
* single character, and pad the field to full width
* with spaces if necessary
*/
int
putfld(s, w, pg)
register char *s; /* string to write */
int w; /* field width */
int pg; /* screen page for writes */
{
int r, c, cols;
register int n;
extern int putcur(int, int, int);
extern int readcur(int *, int *, int);
extern int writec(unsigned char, int, int);
/* get starting (current) position */
readcur(&r, &c, pg);
/* write the string */
for (n = 0; *s != '\0' && n < w; s += cols, n += cols) {
putcur(r, c + n, pg);
/* compress runs to a single call on writec() */
cols = 1;
while (*(s + cols) == *s && n + cols < w)
++cols;
writec(*s, cols, pg);
}
/* pad the field, if necessary */
if (n < w) {
putcur(r, c + n, pg);
writec(' ', w - n, pg);
}
return (w - n);
}
vf_util
───────────────────────────────────────────────────────────────────────────
/*
* vf_util -- utility functions for ViewFile
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <local\std.h>
#include <local\video.h>
#include <local\keydefs.h>
#include "vf.h"
extern int Startscan, Endscan;
#define NDIGITS 6
/*
* gotoln -- jump to an absolute line number
*/
DNODE *
gotoln(buf)
DNODE *buf;
{
register int ln;
register DNODE *lp;
char line[NDIGITS + 1];
extern void showmsg(char *);
extern char *getstr(char *, int);
/* get line number from user */
showmsg("Line number: ");
setctype(Startscan, Endscan); /* cursor on */
ln = atoi(getstr(line, NDIGITS + 1));
setctype(MAXSCAN, MAXSCAN); /* cursor off */
/* check boundary conditions */
if (ln > buf->d_prev->d_lnum || ln <= 0) {
showmsg("Line out of range");
return ((DNODE *)NULL);
}
/* find the line */
for (lp = buf->d_next; ln != lp->d_lnum; lp = lp->d_next)
;
return (lp);
}
/*
* showhelp -- display a help frame
*/
#define HELPROW TOPROW + 3
#define HELPCOL 10
#define VBORDER 1
#define HBORDER 2
void
showhelp(textattr)
unsigned char textattr; /* attribute of text area */
{
register int i, n;
int nlines, ncols;
unsigned char helpattr;
static char *help[] = {
"PgUp (U) Scroll up in the file one screen page",
"PgDn (D) Scroll down in the file one screen page",
"Up arrow (-) Scroll up in the file one line",
"Down arrow (+) Scroll down in the file one line",
"Right arrow (>) Scroll right by 20 columns",
"Left arrow (<) Scroll left by 20 columns",
"Home (B) Go to beginning of file buffer",
"End (E) Go to end of file buffer",
"Alt-g (G) Go to a specified line in the buffer",
"Alt-h (H or ?) Display this help frame",
"Alt-n (N) Toggle line-numbering feature",
"\\ (R) Reverse search for a literal
text string",
"/ (S) Search forward for a literal text string",
"Esc Next file from list (quits if none)",
"Alt-q (Q) Quit",
"--------------------------------------------------------",
" << Press a key to continue >>",
(char *)NULL
};
/* prepare help window */
ncols = 0;
for (i = 0; help[i] != (char *)NULL; ++i)
if ((n = strlen(help[i])) > ncols)
ncols = n;
nlines = i - 1;
--ncols;
helpattr = (RED << 4) | BWHT;
clrw(HELPROW - VBORDER, HELPCOL - HBORDER,
HELPROW + nlines + VBORDER, HELPCOL + ncols + HBORDER,
helpattr);
drawbox(HELPROW - VBORDER, HELPCOL - HBORDER,
HELPROW + nlines + VBORDER, HELPCOL + ncols + HBORDER,
Vpage);
/* display the help text */
for (i = 0; help[i] != (char *)NULL; ++i) {
putcur(HELPROW + i, HELPCOL, Vpage);
putstr(help[i], Vpage);
}
/* pause until told by a keypress to proceed */
getkey();
/* restore help display area to the text attribute */
clrw(HELPROW - VBORDER, HELPCOL - HBORDER,
HELPROW + nlines + VBORDER, HELPCOL + ncols + HBORDER,
textattr);
}
getstr
───────────────────────────────────────────────────────────────────────────
/*
* getstr -- get a string from the keyboard
*/
#include <stdio.h>
#include <string.h>
#include <local\std.h>
#include <local\video.h>
#include <local\keydefs.h>
char *
getstr(buf, width)
char *buf;
int width;
{
int row, col;
char *cp;
/* function prototypes */
extern int putcur(int, int, int);
extern int readcur(int *, int *, int);
extern int writec(char, int, int);
extern int getkey();
/* gather keyboard input into a string buffer */
cp = buf;
while ((*cp = getkey()) != K_RETURN && cp - buf < width) {
switch (*cp) {
case K_CTRLH:
/* destructive backspace */
if (cp > buf) {
readcur(&row, &col, Vpage);
putcur(row, col - 1, Vpage);
writec(' ', 1, Vpage);
--cp;
}
continue;
case K_ESC:
/* cancel string input operation */
return (char *)NULL;
}
put_ch(*cp, Vpage);
++cp;
}
*cp = '\0';
return (buf);
}
message
───────────────────────────────────────────────────────────────────────────
/*
* message -- routines used to display and clear
* messages in a reserved message area
*/
#include "message.h"
MESSAGE Ml;
extern int writec(char, int, int);
/*
* set up the message-line manager
*/
void
initmsg(r, c, w, a, pg)
int r; /* message row */
int c; /* message column */
int w; /* width of message field */
unsigned char a; /* message field video attribute */
int pg; /* active page for messages */
{
MESSAGE *mp;
void clrmsg();
mp = &Ml;
mp->m_row = r;
mp->m_col = c;
mp->m_wid = w;
mp->m_attr = a;
mp->m_pg = pg;
mp->m_flag = 1;
clrmsg();
}
/*
* showmsg -- display a message and set the message flag
*/
void
showmsg(msg)
char *msg;
{
MESSAGE *mp;
mp = &Ml;
putcur(mp->m_row, mp->m_col, mp->m_pg);
writec(' ', mp->m_wid, mp->m_pg);
putstr(msg, mp->m_pg);
mp->m_flag = 1;
return;
}
/*
* clrmsg -- erase the message area and reset the message flag
*/
void
clrmsg()
{
MESSAGE *mp;
mp = &Ml;
if (mp->m_flag != 0) {
putcur(mp->m_row, mp->m_col, mp->m_pg);
writec(' ', mp->m_wid, mp->m_pg);
mp->m_flag = 0;
}
return;
}
vf_srch
───────────────────────────────────────────────────────────────────────────
/*
* vf_srch -- search functions
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <local\std.h>
#include "vf.h"
/*
* search -- search for a literal string in the buffer
*/
DNODE * search(buf, dir, str)
DNODE *buf;
DIRECTION dir;
char *str;
{
int n;
register DNODE *lp;
register char *cp;
extern void showmsg(char *);
/* try to find a match -- wraps around buffer boundaries */
n = strlen(str);
lp = (dir == FORWARD) ? buf->d_next : buf->d_prev;
while (lp != buf) {
if ((cp = lp->d_line) != NULL) /* skip over header node */
while (*cp != '\n' && *cp != '\0') {
if (strncmp(cp, str, n) == 0)
return (lp);
++cp;
}
lp = (dir == FORWARD) ? lp->d_next : lp->d_prev;
}
showmsg("Not found");
return ((DNODE *)NULL);
}
/*
* getsstr -- prompt the user for a search string
*/
extern int Startscan, Endscan;
char *getsstr(str)
char *str;
{
char line[MAXSTR];
char *resp;
extern int putstr(char *, int);
extern char *getstr(char *, int);
extern int put_ch(char, int);
extern void showmsg(char *);
extern void clrmsg();
static char prompt[] = { "Search for: " };
/* get search string */
showmsg(prompt);
setctype(Startscan, Endscan); /* cursor on */
resp = getstr(line, MAXSTR - strlen(prompt));
setctype(MAXSCAN, MAXSCAN); /* cursor off */
if (resp == NULL)
return (char *)NULL;
if (strlen(resp) == 0)
return (str);
showmsg(resp);
return (resp);
}
getxline
───────────────────────────────────────────────────────────────────────────
/*
* getxline -- get a line of text while expanding tabs,
* put text into an array, and return a pointer to the
* resulting null-terminated line
*/
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <local\std.h>
char *getxline(buf, size, fin)
char *buf;
int size;
FILE *fin;
{
register int ch; /* input character */
register char *cp; /* character pointer */
extern BOOLEAN tabstop(int);
cp = buf;
while (--size > 0 && (ch = fgetc(fin)) != EOF) {
if (ch == '\n') {
*cp++ = ch;
break;
}
else if (ch == '\t')
do {
*cp = ' ';
} while (--size > 0 && (tabstop(++cp - buf) == FALSE));
else
*cp++ = ch & ASCII;
}
*cp = '\0';
return ((ch == EOF && cp == buf) ? NULL : buf);
}
Index
Page numbers for illustrations are in italics
Symbols
──────────────────────────────────────────────────────────────────────
#
%var%
/ (DOS option switch)
\ (DOS pathname separator)
\\ (C escape)
\include
\include\local
\lib
\lib\local
\x
|
A
──────────────────────────────────────────────────────────────────────
Advanced MS-DOS
affirm()
Alt key status
American National Standards Institute (ANSI)
basics
color attributes
control sequences
device driver (see ANSI.SYS)
display memory
local library summary
proposed standard for C language
ansi_cpr()
ANSI_CUP
ANSI_DSR
ansi.h
ANSI_SGR
ANSI.SYS
control codes
numeric parameters
selective parameters
installing
interface package
pros and cons of
SetColor program
set graphic rendition (SGR)
user-attribute function
ansi_tst()
SetColor program
arguments
argc
argv
fixed/variable-list
pathname and type
video row and col
ASCII codes. See also displaying non-ASCII text
and ANSI controls
block characters
character code data bytes
control character table
for Epson MX/FX printer control
extended
line-drawing characters
printable character table
video attributes
asctime()
Aspen Scientific
Assembler
AT&T
PC6300
UNIX System V
atoi()
attributes. See characters and attributes
AUTOEXEC.BAT file
automatic program configuration
printer control functions
selection functions
using configuration files
using the program name
B
──────────────────────────────────────────────────────────────────────
batch processing
SetColor program
bdos()
beep()
binary numbers, converting
bios.lib
bioslib.h
BIOS library routines
demonstration program
equipment determination functions
keyboard status
library summary
makefile
video access
bios.mk
blink bit
block characters
block-copy routine
synchronized
blocking read
box-drawing functions
box.h header file
buffer(s)
input/output
screen (see buffered screen interface; screen access routines)
text (see text buffer)
buffered screen interface
box-drawing functions
buffer management functions
general window functions
interface package
screen buffer library
byte2hex()
C
──────────────────────────────────────────────────────────────────────
call by reference
call by value
Caps Lock key status
CAT program
cells, buffer
character I/O functions
characters and attributes.
See also ASCII codes; SetColor (SC) program
display memory
reading from screen buffers
writing to windows
char data type
C language
DOS-to-, (see DOS-to-C connection)
language details
Microsoft, version 4.00
PC interfaces (see PC operating system interfaces)
standard routines (see standard libraries)
standards/compatibility
technical highlights
user interface (see user interface)
CL control program
clean()
clearerr()
clock. See PC timer
close()
clrmsg()
clrprnt()
clrscrn()
clrw()
CodeView
color graphics adapter (CGA)
display adapter basics
memory interference
colornum()
color numbers
colors. See SetColor (SC) program
command-line processing
accessing the command line
ambiguous filenames
arguments
pointers and indirection
program name
standardizing, to improve user interface
compact memory model
compatibility. See portability
compiler
Microsoft version 4.00
other compilers for DOS
Computer Innovations C86
configuration. See automatic program configuration
configuration files
confirm()
conversion functions, numbers
cpblk()
screen updates
ST program
CP program
The C Programming Language
"C Reference Manual"
CR/LF
ctime()
Ctrl-Break
Ctrl key status
Ctrl-Z
Curses
cursor
control functions
position report
restoring
split
window
cursor.c
cursor.mk
CURSOR program
makefile
output
pseudocode
source code
D
──────────────────────────────────────────────────────────────────────
DEBUG program. See also DUMP program
debugging programs. See also CodeView
decimal numbers, converting
delay()
device driver. See ANSI.SYS
device status report
directories
disk
list (see LS utility program)
pathname (see PWD utility program)
direct screen access
alternative solutions
display adapter basics
programming considerations
disk routine numbers, BIOS
display adapter basics
displaying non-ASCII text
conversion functions
DUMP
SHOW
display memory
display system type, determining
do_rm()
DOS
compared to UNIX and XENIX
error messages
library (see DOS library routines)
link (see LINK (3.51) program)
-to-C (see DOS-to-C connection)
version number
DOS environment
pointer
reserved space
setting values correctly
variables
dos.h
dos.lib
doslib.h
DOS library routines
demonstration program
DOS version number
keyboard functions
summary
DOS-to-C connection
command-line processing
DOS environment variables
input/output redirection and piping
double buffering
drawbox()
drvpath()
DSPYTYPE program
dump.c
dump.mk
DUMP program
makefile
manual page
output
pseudocode
source code
Duncan, Ray
E
──────────────────────────────────────────────────────────────────────
ECHO command
EDITOR
EDLIN program
ega_info()
element, in display memory
enhanced graphics adapter (EGA)
enum type specifier
environment pointer
environment variables
ENVSIZE program
EOF (end of file) indicator
epoch time
Epson dot-matrix printer
interface routines
MX program
equipchk()
equip.h
equipment
determination functions
standards
ERASE/DEL command
errno
error functions
ambiguous return values
getreply()
operating system interrupts
ERRORLEVEL
exception handling
exec()
execl()
EXEMOD
EXEPACK
exit()
external storage for large files
F
──────────────────────────────────────────────────────────────────────
fatal()
fclose()
fcloseall()
fconfig()
fcopy()
feof()
ferror()
fgetc()
fgetchar()
fgets()
file(s)
basic functions
configuration (see configuration files)
names (see filenames, ambiguous)
network sharing
nontext (see displaying non-ASCII text)
printing (see file printing)
utilities (see file utilities)
viewing (see ViewFile (VF) program)
filenames, ambiguous
wildcards
file printing
file handling
option handling
possible enhancements to
program maintenance
program specification
file utilities
filter
DUMP as
PR as
first_fm()
fit()
fixtabs()
floating point
fopen()
FORTRAN
fprintf()
fputc()
fputs()
free()
fseek()
function(s). See also names of individual functions
classified by environment
error
file and character I/O
keyboard
library management of
local library summary
number conversion
printer control
process control
selection
suffix meanings
system interface
time/date
timing
user attribute
window
function keys
G
──────────────────────────────────────────────────────────────────────
game port
getc()
getch()
getchar()
getctype()
getcwd()
getdrive()
getenv()
getkey()
getname()
getopt()
pseudocode
source
getpname()
getreply()
getstate()
to determine video mode
getstr()
getticks()
getxline()
global variables
gmtime()
gotoln()
graphic rendition
Greenwich mean time (GMT)
H
──────────────────────────────────────────────────────────────────────
hardware cursor
hexadecimal character constants
hexadecimal numbers, converting
hex.c
hexdump()
horizontal sweep signal
I
──────────────────────────────────────────────────────────────────────
ibmcolor.h
initmsg()
input/output (I/O)
buffered
character functions
redirection and piping
with TEE
terminating
user input
Ins key status
intdos()
intdosx()
int86()
int86x()
interactive mode, SETCOLOR program
interlanguage calls
interrupts
BIOS
PC operating system
interval()
intra-application communications area (ICA)
iscolor()
K
──────────────────────────────────────────────────────────────────────
kbd_stat()
kbhit()
Kernighan, Brian. See also Ritchie, Dennis M.
keybdlib.h
keyboard
BIOS routine numbers
functions (DOS)
status
stdin stream
keydefs.h
keyready()
L
──────────────────────────────────────────────────────────────────────
language(s)
details, Microsoft C
interlanguage calls
last_ch()
Lattice-MS-DOS C Compiler
LF (linefeed) character
LIB, object-file maintainer
libraries. See local library summary; screen buffer library;
standard libraries
linebuf.h
line-drawing characters
lines()
LINK (3.51) program
list directories. See LS utility program
local library summary
ANSI
BIOS
DOS
screen buffer
utility
localtime()
ls.c
ls_dirx()
lseek()
ls_fcomp()
ls_list()
ls.mk
ls_multi()
ls_single()
LS utility program
makefile
manual page
pseudocode
source code
M
──────────────────────────────────────────────────────────────────────
macros. See function(s)
"magic cookies" attribute positions
mainmenu()
MAKE command program maintainer. See also makefile
makefile
ANSI
BIOS
CURSOR
DUMP
LS
MX
PR
PRTSTR
REPLAY
sbuf.lib
SetColor
SHOW
SHOWTABS
ST
ViewFile
malloc()
Mark Williams C Programming System (MWC)
MASM macro assembler
math coprocessor
math libraries, multiple
memchk()
memcpy()
memory. See also buffer(s)
checking, to determine screen type
compact model
determining total
display (see display memory)
intra-application communications area (ICA)
models
memsize()
message.c
message.h
Microsoft C, version 4.00
language details
technical highlights
Microsoft C Compiler Library Reference Manual
mkslist()
pseudocode
MODE command
modeflag
monochrome adapter
MORE
movedata()
MSC control program
MULTICOLUMN variable
mx.mk
MX program
makefile
manual page
source code
N
──────────────────────────────────────────────────────────────────────
next_fm()
NL (newline) character
nlerase()
non-ASCII text. See displaying non-ASCII text
nonblocking read
NOTES program
NOTES2 program
_nullcheck()
numbers, conversion functions
Num Lock key status
O
──────────────────────────────────────────────────────────────────────
open()
operating system
interrupts
PC interfaces (see PC operating system interfaces)
optarg
opterr
Optimizing C86
optind
option flag(s)
option switches
_osmajor
_osminor
OUTBUF
overlays
P
──────────────────────────────────────────────────────────────────────
page(s)
active/visual
"flipping"
formatting/copying text
layout
Pagelist
palette()
parse()
Pascal
pathname
display
and type argument
PC operating system interfaces
BIOS library routines
demonstration program (CURSOR)
DOS library routines
functions
library management issues
PC timer
p_dest
perror()
pointer(s)
environment
and indirection
PolyMake
portability
P_OVERLAY
pr.c
pr_cpy()
pseudocode
source code
preprocessor directives
pr_file()
pr_gcnf()
pseudocode
source code
pr_getln()
pr_help()
PRINT
printer control functions
MX program
PRTSTR program
printer.c
printer.h
printf()
print.h
printing files. See file printing
print working directory path. See PWD utility program
prlib.lib
pr_line()
pr.mk
pr.obj
process control functions
program development
development cycle
guiding principles
local environment
programmable peripheral interface (PPI)
programming languages
interlanguage calls
program name
using, to alter program behavior
program size
PR program
calling hierarchy
file handling
as filter
folded lines
formatting/copying text pages
makefile
manual page
option handling
page layout
possible enhancements
program maintenance
as a sink
source code
specification
prtshift()
prtstr.c
prtstr.mk
PRTSTR program
makefile
manual page
source code
pseudocode
for configuration layers
CURSOR
DUMP
getopt()
mkslist()
pr_cpy()
pr_gcnf()
RM
save_range
showit()
TEE
TOUCH
putc()
put_ch()
putcur()
putenv()
putfld()
putstr()
pwd.c
PWD utility program
manual page
source code
Q
──────────────────────────────────────────────────────────────────────
qsort()
R
──────────────────────────────────────────────────────────────────────
read
blocking
characters/attributes from screen buffers
from the DOS environment
nonblocking
video characters
read()
readca()
readcur()
readdot()
realloc()
REGION
registers
structures
word/byte
remove files. See RM utility program
replay.c
replay.mk
REPLAY program
makefile
return values, ambiguous
rewind()
Ritchie, Dennis M.. See also Kernighan, Brian
rm.c
RM utility program
manual page
pseudocode
source code
routines. See function(s)
rows and columns, video
RUN_ONCE program
S
──────────────────────────────────────────────────────────────────────
SAMPLE.BAT file
save_range()
pseudocode
sb_box()
sb_fill()
sb_init()
sb_move()
sb_new()
sb_putc()
sb_puts()
sb_ra()
sb_rc()
sb_rca()
sb_read()
sb_scrl()
sb_set_scrl()
sb_show()
sb_test.c
SB_TEST driver program
header file
makefile
source code
sb_test.mk
sbuf.h
sbuf.lib
makefile
sb_wa()
sb_wc()
sb_wca()
sb_write()
scanf()
sc.c
sc_cmds()
Schinnell, Rich
sc.mk
screen(s)
access (see buffered screen interface;
screen access routines; video access)
color/graphics adapter basics
determining type of
scrolling/clearing commands
stderr/stdout streams
updates
updates with cpblk()
user input
screen access routines. See also buffered screen interface
demonstration program ST
design considerations
determining display system type
direct screen access
double buffering
synchronized block-copy routine
screen buffer library
scroll()
scrolling windows/screens
commands
Scroll Lock key status
search()
segread()
select.c
selected()
selection functions
SELECT module routines
setargv()
setattr()
SetColor (SC) program
batch mode
function keys in
interactive mode
makefile
manual page
pseudocode
source code
using
SET command
setctype()
setdta()
_setenvp()
SETENV program
setfont()
setfreq()
set graphic rendition (SGR)
SETMYDIR program
setpage()
setprnt()
setvmode()
sflag
shift key status
show.c
SHOWENV program
showhelp()
showit()
show.mk
showmsg()
SHOW program
makefile
manual page
pseudocode
source code
SHOWTABS program
makefile
output
signal()
signal catching
sink. See also filter
Slist
sorting
sound()
sound.c
sound generation
sound.h
SOUNDS program
spaces()
spawn()
spawnvp()
SPKR program
standard data streams
standard libraries. See also function(s)
basic file and character I/O functions
BIOS routines
DOS routines
exception handling
functions management
LIB
other library functions
process control functions
reasons for using
system interface functions
time functions
st.c
stderr (standard error) stream
std.h
stdin (standard input) stream
stdio.h
stdlib.h
stdout (standard output) stream
st.mk
ST (screen test) program
makefile
source code
strdup()
string(s)
creating time
into windows
strlwr()
strtok()
struct BYTEREGS
struct pr_st
struct tm
structure handling
struct WORDREGS
strupr()
support tools for C compiler
swap_int()
sweep.c
switches. See option switches
SYMDEB program
system()
T
──────────────────────────────────────────────────────────────────────
tabs.c
tabstop()
tab stops, setting
tee.c
TEE utility program
manual page
pseudocode
source code
Termcap
testing programs
text buffer
management
TICKRATE
time()
time conversions
creating strings
time zones
timedata.c
TIMEDATA program
time/date functions
time delays
time.h
TIMER program
manual page
timing functions
audible feedback
PC timer and time delays
TONE program
tools.ini
touch.c
TOUCH utility program
manual page
pseudocode
source code
TSTREPLY program
tstsel.c
TSTSEL program
output
typedef
TZ
tzset()
U
──────────────────────────────────────────────────────────────────────
Universal time (UT)
"UNIX 1984 Standard"
UNIX operating system
command-line processing
compared to DOS
control program (cc)
ctime()
getopt() error
MAKE program
portability
standardization of
starting optional arguments (-)
System V
unlink()
UPDATEMODE
userattr()
user interface
command-line processing
timing functions
user input
utility programs
library summary
util.lib
V
──────────────────────────────────────────────────────────────────────
variables
DOS environment
global (see global variables)
time
vartabs()
ver()
vf_alloc()
vf_cmd()
vf_cmd.c
vf_del()
vf_dspy()
vf.h
vf_ins()
vflag
vf_list.c
vf.mk
vf_mklst()
vf_srch.c
vf_util.c
video access. See also screen(s)
video attributes
video.h
video routine numbers, BIOS
ViewFile (VF) program
external storage
features
header file
help frame
implementation details
makefile
manual page
source code
void
W
──────────────────────────────────────────────────────────────────────
warn()
wflag
wildcard characters in ambiguous filenames
window(s)
general functions
REGION
word2hex()
write
characters/attributes to windows
to the DOS environment
video characters
write()
writea()
writec()
writeca()
writedot()
writemsg()
writestr()
writetty()
X
──────────────────────────────────────────────────────────────────────
XENIX
compared to DOS
control program (cc)
getopt() error
portability