Columns


Questions & Answers

Using TLIB, The Turbo C Librarian

Ken Pugh


Kenneth Pugh, a principal in Pugh-Killeen Associates, teaches C language courses for corporations. He is the author of C Language for Programmers and All On C, and was a member on the ANSI C committee. He also does custom C programming for communications, graphics, image databases, and hypertext. His address is 4201 University Dr., Suite 102, Durham, NC 27707. You may fax questions for Ken to (919) 489-5239. Ken also receives email at kpuqh@dukemvs.ac.duke.edu (Internet).

Q

I am a recreational novice Turbo C user and reader of your column in The C Users Journal. Occasionally, I use Turbo C to write small utility programs for my work. In so doing, I have managed to develop some specialized calculating and graphics functions that I use over and over. I can do this easily enough by either incorporating them in the text of my particular project or linking them as OBJ files. However, for strictly educational reasons (and maybe a touch of stubbornness) I would like to incorporate them into a library.

My problem is that I don't know how to do that, and the user manuals that I have are not making things click for me. Can you please elaborate on what I need to be thinking about to prepare functions for inclusion in libraries. Most of my functions make use of each other and of routines used in Turbo C's libraries.

Thank you for your assistance.

Sam LeFevre
Idaho Falls, Idaho

A

The librarian is the solution to your problems. Each compiler or operating system has its own version of this program. It gathers multiple object files into a single library file. Options usually include the ability to add, remove, replace, extract, and list the object modules.

Turbo C's librarian is named TLIB. A simple way to run it is

tlib libname + onefile
   + twofile + threefile
This creates a library called libname with three object files in it: onefile.obj, twofile.obj, and threefile.obj.

Alternatively you could list the names of the object files in a text file. Suppose libfiles contained

+ onefile
+ twofile
+ threefile
Then

tlib libname @libfiles
would create the same library. You can get elaborate in updating libraries. Usually I make up one file that lists all the modules to be included in the library and rebuild the library every time a module changes.

Creating a package or a set of functions that is usable takes a slight bit of work. Let me list some guidelines. Not all of these may be applicable if you are only creating the libraries for yourself.

1. Name the functions with understandable function names. If there are several functions in a package, they should each have a common suffix or prefix.

2. The functions should receive all the values they need from the parameter list. There may be a need for the functions to communicate with each other. Use static global variables for values that need to be accessed by more than one function.

3. Keep related functions together in a single source file. This is required if they communicate via static global variables. This simplifies the maintenance issues. When a program requires one of the functions, the whole object module will be loaded. This will increase the size of the executable. However chances are the program will require most of the functions in the file anyway.

4. Create a header file that contains prototypes for all functions in a package. This file should include any structure templates and #defines required by the user to access the functions.

To illustrate these, I'll use an example from my talk on Data and Function Abstraction — The First Step to Object-Oriented Programming. Suppose you wanted to create a printer package that would allow you to print text in a number of fonts. You might have the files in Listing 1.

The suffix for the package is pr. The header file print.h contains everything necessary for the user. Related subroutines (pt_set_format and pr_output_string) communicate through current_print_font. There are no global variables that are settable by the user.

Q

I recently learned that the ANSI C standard requires a compiler to be able to generate code for recursive calls to main. I developed a trivial example of recursive calls to main (Listing 2 with output in Listing 3) , using VAX-C v3.1, to satisfy my curiosity. This is an interesting requirement yet, I cannot readily see any use for this functionality. Can you give some examples where this recursion might be beneficial.

I enjoy reading your articles in The C Users Journal. Thank you for your time.

Jason B. Mawhinney,
Fredericksburg, VA

A

I can't think of any example where this might be used, unless you were implementing a recursive algorithm that you wanted to be able to call from the command line. As far as I can figure, it just shows that main is like every other function. Note that you can also have a return statement in main, which works just like calling exit.

Perhaps our readers have some ideas on this.

Q

I am a new convert from Microsoft QuickBasic to Microsoft QuickC and also a new user of The C Users Journal (which I find most helpful).

One of the few things from QuickBasic that I miss using in QuickC is the ON KEY interrupt functions which allow for softkeys to be used within the program.

Is it possible to perform a similar function with QuickC and, if so, is it also possible to prioritize the function of various keys from the selection available?

I have attempted to implement this function by hooking the keyboard interrupt and returning the function to call next to the executing program. This works in a limited manner however, it eventually gives a stack overflow and locks the system. This happens with the listed software after a few rapid keystrokes, however on a program with a non-trivial main loop, it overflows the stack almost immediately.

I am using the CXL libraries for the windowing output and compiling in Quick V2.5 in the small mode.

I need to have these keyboard interrupts for process control applications involving mechanical testing equipment.

Peter Nunn
Victoria, Australia

A

I've simplified the code you provided, since I don't have the CXL libraries. The code as modified with most of your original flow is shown in Listing 4. It is shown all in a single file, to simplify the listing, although you originally had it broken out to several files, as good programming practice dictates.

There was one item that was an interesting slip of the keyboard. Originally you had off_key looking like

void off_key(void) {
   ....
   _disable;
   _dos_setvect(PJN_INTER, old_handler);
   _enable; }
Note that those are not calls to the functions, but simply evaluation of the addresses themselves. A good lint would have solved the problem.

The basic problem with your code is using the interrupt incorrectly. You used the keyboard interrupt and then tried calling the BIOS routines to read the keyboard. However you did not chain the keyboard interrupt to the old handler, so the keyboard buffer will never get filled up.

The keyboard interrupt (0X9) is the one that the keyboard itself uses to tell the processor a key has been struck. The interrupt vector for this has to go get the scan codes from the keyboard and process them.

A better method is to use the software interrupt (0x1C), that is called by the BIOS code that services the timer. It invokes the user routine at that vector location every clock tick. You can then call the _bios_keybrd routine to see if there is a key waiting in the buffer.

You could have simplified the code by having the interrupt routine simply return the key that was pressed. The main program would compare the new key to the current key and switch the function pointers around if necessary. That keeps the interrupt routine more general and reusable.

I'm not sure what you meant by prioritizing the keys. The flow which you used will simply switch the function whenever a key is pressed. If you want to interrupt one of the functions whenever another key is pressed, you could set up everything to work off software interrupts. But that gets a little bit complicated under MS-DOS.

Readers' Replies

Placement of main

Not to pick nits, but in the "Questions & Answers" column of the July CUJ, regarding the placement of the procedure main as the first or last procedure in the source file, your answer was only 99.44 percent correct: "It doesn't matter to the compiler...the order in which you list your functions" unless the procedures are not of type integer! If main appears at the top of the source, you may have to declare some of the functions it calls, which you could omit if main were last. (Me, I always put it first unless I put it last — depends on my mood.)

J. Laurino

I always declare functions that return non-integers, regardless of their placement in the source file. I try to avoid any other return type for functions, so it's fairly easy to do this. (KP).

Extended Keys

Concerning Mr. Richard Jodoin's letter about the extended keys and how to use them. The enclosed function (Listing 5) and include file (portion shown in Listing 6) should be useful for what he wants. The function gets a single key and returns it as an integer. The include file defines all of the extended keys, along with some of the "normal" keys.

Geoffrey Probert Jr.
Broomfield, CO

In the April 1991 issue of The C Users Journal, Mr. Tim Riley submitted a C program, all_true, which seemed to defy reasonable operation. Mr. Riley's desire was to always return an exit status of 0 so that the calling program would not report an error.

The coding and operation of the all_true program is correct. The mystery lies in the action of the Bourne shell. The Bourne shell is designed to detect the interrupt key (UNIX signal 2) and perform an interrupt processing routine that sets a bit in the exit status only if the exit status is 0!

The following code illustrates how to control the internal interrupt processing features of /bin/sh:

$ trap " 2
This trap command instructs the shell to do nothing when it receives an interrupt (signal 2).

$ all_true sleep 5
^C
$ echo $?
0
The all_true processing will run normally, and if interrupted, the shell will report an exit status of 0.

$ trap 2
This trap command restores the Bourne shell's default interrupt processing.

If the program being used by Mr. Riley does not permit execution of more than a single shell command, this arrangement will work as well:

$ trap " 2 ; all_true sleep 5 ; trap 2
James A. Capp
Harrisburg, PA

Thanks for your suggestion. That explains the reason for the exit status. (KP)