Victor R. Volkman received a BS in computer science from Michigan Technological University in 1986. Mr. Volkman is the new Products Editor for The C Users Journal. He is currently employed as software engineer at Cimage Corporation of Ann Arbor, Michigan. He can be reached at the HAL 9000 BBS, 313-663-4173, 1200/2400/9600 baud or any BBS in the W-Net Network.
CodeRunneR by Microsystems Software, Inc. (Framingham, Mass.) is a library designed to help you write Terminate-and-Stay-Resident programs (TSRs). TSR programs are able to run in the background and later be awakened and brought to the foreground by an external event, such as an interrupt, hot-key, or timer. A TSR program can then pop-up windows, accept keyboard input, perform processing and become the foreground program. Every version of MS-DOS is able to install TSRs. Without expert guidance, writing your own TSR program can be a difficult and frustrating task. You can either scour technical books and articles for hints and tricks, or you can start with a complete packaged library solution. The CodeRunner library provides a large set of functions designed to operate in the hostile environment that a resident program must live in. Using the TSR template provided with CodeRunneR, you can get your first TSR application up and running in days instead of weeks.
CodeRunneR version 1.05A is shipped on both a 5¼-inch 360K diskette and 3½-inch 720K diskette in the same package. The version I received also included the Professional Developer's Kits #1 and #3 (PDK). The PDKs are additional library modules which provide such services as complete Expanded Memory Specification (EMS) support and interrupt-driven serial I/O. The CodeRunneR library is available in object form only; source is priced seperately. The library is compatible with both Microsoft C, Borland Turbo C, Watcom C and Zortec C. CodeRunneR is available in Small and mixed models only (code <64K, data <64K). No support is available for Tiny, Compact, Medium, Large or Huge memory models. The list price for CodeRunneR without PDKs is $149.
Although the documentation does not mention hardware requirements, CodeRunneR should work well on any system from an 8088 to an 80486. The documentation doesn't mention operating system requirements either. However, I had no difficulty using CodeRunneR with DOS 3.3 on a 20Mhz 80386 computer and 4Mb RAM. A subsequent call to MSI technical support revealed that CodeRunneR-based products will run well on any computer with DOS 2.0 or later.
Developing products with CodeRunneR requires only a modest C background. However, I recommend that only software developers with detailed knowledge of MS-DOS and PC architecture attempt writing their own TSRs. You will need to learn this background material to climb the learning curve quickly.
In addition to producing RAM-resident programs, the CodeRunneR library offers several other features, including function-level granularity, hot-key entry points, a Binary Coded Decimal (BCD) math library, a multitasking scheduler, a replacement runtime library for C and elimination of initialization code and data segments. Still more features are available through the PDKs which must be purchased separately.
Library Granularity
The CodeRunneR library is touted as having function-level granularity. Function-level granularity means that none of the functions in the library require support from any other function. Normally, if you invoked a print f() call against the run-time library supplied with your compiler, you would expect it to call in a host of other support functions. Each of these functions might reference another set of functions. Soon the linker could be pulling in the greater part of the runtime library, even if some of the functions are not used by your application. CodeRunneR avoids this phenomenon by coding so that each function requires no additional support functions. Coding in this fashion ensures that only minimum library support is linked into your program. The disadvantage is that you lose opportunities for size reductions in similar library functions containing redundant code. Nevertheless, in many cases a library with function-level granularity will impose less of a burden on the application than a library without it.
Hot-Key Activation
Many TSR programs require the ability to be invoked at any time by the press of a hot-key. The CodeRunneR hot-key facility provides a simple method for managing up to 256 hot-key entry points simultaneously. Many keystroke combinations can be specified. Hot-key support must be installed in the initialization section of your application via the install_hk() function. A sample invocation might be:
install_hk(hk_list, service, stack_size, lock_mask);The first argument of install_hk() is a pointer to an array of hot-keys which will activate the TSR, for example:
int hk_list[] : {KEY_W + MASK_LEFT_SHIFT + MASK_LEFT_CTRL, KEY_T + MASK_LEFT_ALT + MASK_RIGHT_ALT, 0};The keyboard mask for keys on the left and right sides must be specified individually. Note that only two masks may be combined with any other non-shifting key.The second argument is a pointer to the dispatching function which will be called whenever a key in the hk_list[] is pressed. The function service() must be of type void and must receive the scan code of the actual keypress as its argument. The service() function is supplied by the application but usually follows a standard sequence of operation. First, service() checks the current video mode and changes the mode if neccessary. Next, service() dispatches the actual function associated with the hot-key. After returning, service() checks the stack to determine how much has been used. Last, if TSR removal has been requested (perhaps by a hot-key), then service() calls uninstall() to remove itself.
The third argument to install_hk() is the local stack size for this invocation of the TSR. The stack size is in bytes and must always be an even number. The local stack is allocated from the global stack area designated in the call to stay_resident() which established this TSR. If the TSR is to be reentrant, then the sum of the local stack sizes from all execution threads must be less than the size of the global stack area.
The final argument specifies the reentry control mask. This mask allows you to selectively disable reentrancy due to keyboard or scheduler interrupts. For example, you may want to prevent the user from popping up your TSR via a hot-key if it is still processing the previous hot-key request. The actual reentrancy check is a semaphore which must be clear prior to calling the service() function.
Since the install_hk() function may be invoked only once per TSR, some additional hot-key management functions are needed after the code becomes resident. The _hk_add() and _hk_remove() functions allow hot-keys to be added and removed at any time. The _hk_used() function tells whether a given hot-key is in the current active list. Last, the _hkey_again variable is true if the same hot-key was hit twice in a row.
BCD Math Library
Since CodeRunneR does not support floating point arithmetic, a Binary Coded Decimal (BCD) library is provided for fixed-point arithmetic. The primary advantage of BCD is that the storage format is more accurate than binary approximations. Also, BCD numbers can be converted to and from strings more readily than floating point numbers can. The primary disadvantage is that BCD arithmetic is always slower than floating point arithmetic. Also, because BCD is not a native C data type, every expression must be broken down into a set of function calls employing at most three operands. Breaking down expressions reduces readability since parenthetical grouping and operator precedence are not syntactically available.CodeRunneR is initially shipped with the BCD library precision of 16 digits. After users register the product, the company mails 3 new copies of the library configured for 12, 24 and 248 digits of precision. The new library provides the following operator functions: add, subtract, multiply, divide, square-root, divide-by-2, compare, round, and truncate. It does not provide trigonometric or exponential functions. MSI claims that the entire BCD library adds less than 1K to the size of your application.
Replacement Runtime Library
The runtime library supplied with every C compiler sold is not suitable for use in the harsh environment that a TSR program must live in. For example, the familiar heap model where malloc() and free() can be used indiscriminately does not apply. A TSR must allocate all the contiguous memory it ever plans to use before becoming resident. The TSR must also remember to set its Program Segment Prefix (PSP) every time it is invoked. Additionally, it must respect the InDOS flag to avoid making reentrant requests to DOS functions already in progress. Rather than building fire-walls around your existing library, it is easier to start over with an alternate runtime library. CodeRunneR takes this approach.Using CodeRunneR requires giving up nearly all of the functions supplied with your compiler's runtime library. Only functions which do not access code or data in the startup module may be used. (CodeRunneR requires that you use its startup module, which is incompatible with most of the compiler's runtime library.) Unfortunately, this means runtime library functions involving file I/O, console I/O, memory allocation and floating point may not be used. Using any of these functions results in unresolved externals at link time.
The good news is that CodeRunneR provides a replacement library for many I/O and string functions. To achieve source-level compatability, you must include the file SIO.H. This file specifies macro replacements for the runtime library functions. For example, the printf() function is replaced by _printf(), the strlen() function is replaced by str_len() and so on. The actual amount of source-level compatability varies by function (see Figure 1) . (I will elaborate on this in the case study accompanying this report.)
Event Scheduler
CodeRunneR includes two distinct schedulers for timed activation of your TSR. The Master scheduler is the more sophisticated of the two and can support more than 10,000 events. The Tiny scheduler may be used for applications that never need to schedule more than one event in advance. The activation time of an event is specified as number of clock ticks that the scheduler will wait. The smallest interval is one clock tick, which is about 1/18th of a second (55 ms). The longest interval is 232 ticks, which is about 7.6 years.Scheduler support must be installed in the initialization section of your application. The Master scheduler is initialized via the install_sch() function. A sample invocation might be:
install_sch(service, stack_size, lock_mask);The first argument is a pointer to a dispatching function that is called each time an event is due to be processed. This is similar to activating hot-keys as described earlier. The service function will be expected to receive a single parameter, which is a pointer to the event that triggered its activation. Events used with the Master scheduler are defined by an event_rec structure as follows:
struct event_rec { int link; /* pointer to next event */ unsigned long tick_cnt; /* number of ticks before event */ };The second argument is the stack size for the activation of the service function. The same type of stack requirements described in the hot-key service function apply here as well.The last argument is the reentry control mask. This argument also acts identically to its counterpart in the install_hk() function mentioned earlier. A lock_mask value of 01h prevents another event from becoming due and reentering the service function before it is finished with the current event. If an event is locked-out because of the reentry control mask, it will try again on each subsequent clock tick until it is successful.
The library provides a number of event-management functions to allow dynamic manipulation of the event list. The add_event() function accepts a pointer to an event record and sorts the event list. The del_event() function allows any event record to be deleted. The _transfer_time() function suspends the scheduler so the event list can be manipulated directly by the application. The _prep_nx_event() function resumes the scheduler after being suspended by _transfer_time(). The no_event variable is always updated to indicate the number of events remaining in the list.
Eliminating Initialization Code And Data
MSI introduces the concepts of disposable code and disposable data to allow TSRs to occupy the minimum possible resident space. Disposable code refers to the set functions that are called only during program initialization. Functions that are not disposable are referred to as permanent code. Typically, disposable code is used for parsing command lines, reading configuration files and displaying sign-on screens. All disposable functions must be physically grouped into modules containing only this type of function.Disposable data is defined as global variables that are only referenced during program initialization. Data which is not disposable is referred to as permanent data. Uses of disposable data include sign-on screens, error messages and keyword lists for parsing command lines. All disposable data must be physically grouped into modules containing only this type of data.
The link stream for your CodeRunneR application must be precisely configured so that disposable code and data can operate. Each type of module must appear in a specific order (see Figure 2) . The actual compaction of the program takes place after your initialization code has been executed and the main() function exits. The startup module copies the permanent data down into the area formerly occupied by the initialization code and data. Next, the startup module calls DOS INT 21h, function 31h to become resident. The parameter to this DOS call is the amount of memory to reserve for the TSR in paragraphs (16-byte blocks). This memory is reserved starting with the PSP of the application requesting residency. The CodeRunneR startup module subtracts the size of the disposable code and data areas when calculating the memory to reserve. Thus the actual resident size is decreased by the combined size of the disposable areas (see Figure 4) .
The amount of space actually gained by this technique depends upon the application. The results of my case study showed a total size reduction of 1K. More dramatic savings are possible by installing or swapping the entire TSR into EMS memory until needed. However, EMS usage requires purchase of one PDK as well as CodeRunneR.
PDK Add-Ons
The MSI Professional Developer's Kits (PDKs) provide important support for features which are not available in the CodeRunneR base product. You must already have an existing copy of CodeRunneR to use any of these extensions. PDK#1 includes extensions for serial I/O, Expanded Memory Specification (EMS 4.0), a print spooler, and registration copy protection for your applications. Example programs, such as a complete RAM-resident terminal program, are also included.The PDK#1 serial I/O support allows interrupt-driven or polled communication at speeds up to 115K baud. Low-level service routines may be defined for all four of the 8250 and 16550 UART interrupts: Modem Status, Transmit Ready, Received Byte and Receive Error. Other serial I/O functions allow you to compute CRCs against buffers, emulate VT-100 terminals (ANSI) directly, and manage hardware and software flow control.
The EMS support in PDK#1 provides several services. First, the move_to_lim() function provides your TSR with a single-step operation to install its code and/or data into EMS memory. When a TSR is installed in EMS memory, as little as 1K of resident code in DOS memory is required. TSR programs which require far heap allocation, interrupt-driven communication or re-vectoring software interrupts cannot be loaded into EMS memory. Second, the EMS functions enable you to allocate, deallocate and manipulate 16K EMS pages and page mappings. The PDK#1 documentation for these functions assumes that you are already familiar with EMS addressing schemes.
Last, PDK#1 provides a registration copy-protection scheme for applications that you develop. This set of functions allows you to create a shareware application that may be partially or wholly crippled until registered by the end-user. The registration process can encode the user's name and company right into your executable. A checksum and debugger detection mechanism can prevent users or viruses from modifying your executable.
Since the PDK#3 package was still in Beta testing during this evaluation, I will remark on its contents only briefly. PDK#3 introduces several ways to swap out either the TSR or foreground application. These programs may be swapped out to EMS or disk to make room for spawning other DOS applications. After the spawned application exits, the TSR or foreground program swaps itself back into DOS memory. The other major feature in PDK#3 is the ability to save and restore graphics displays. You can save and restore the mouse status as well.
Documentation And Support
The CodeRunneR documentation consists of a 300-page paperback book. This manual must be considered as only a reference guide since nearly all of the text is devoted to describing each individual function in the CodeRunneR library. Some limited tutorial information is scattered in various sections, but comprises less than one-fifth of the text. It seems that the author intends you to simply scan the source code for the pop-up utilites provided with CodeRunneR to deduce their operation.An introductory section entitled "Hints for Efficient C Programming" is a jumble of general C programming techniques and specific hints for writing CodeRunneR applications. Some of the hints amount to a quixotic indictment of the efficiency of C compilers. For example, hint #24 admonishes the reader that modular, readable, structured and object-oriented coding practices are a rich source of inefficiency.
The CodeRunneR manual is written for seasoned MS-DOS programmers. For example, if you don't understand the structure of the Program Segment Prefix (PSP), this book won't explain it for you. You will need a reference book, such as Ray Duncan's Advanced MS-DOS (2nd Ed.), which details the parameters of every BIOS and DOS interrupt. The text makes frequent references to operating system calls, such as "See BIOS INT 10h" and "See DOS INT 21h, function 3Eh". The "File I/O" function descriptions are especially terse, often mentioning little more than the INT 21h function code.
In addition to the highly technical treatment, the lack of illustration adds to the complexity. Not a single graphic illustration appears in the entire text. Illustrations would make the text less dense and make abstractions, such as the memory map and scheduler operations, easier to comprehend.
The documentation is accurate for the most part, but does contain numerous typographical errors and has some problems with the example code fragments. Many function descriptions advise you to turn to a different page number for an example. However, in many cases, the example code fragment referenced does not include a call to the function in question. For instance, each of the six function descriptions in the "Software Interrupt" section advise you to see page 4-142 for an example. Contrary to this statement, there are examples for only two out of the six functions in this section.
There is also some confusion involved with the descriptions of the BCD library. Page 1-3 states that the BCD package has "variable precision to 248 digits". Later in the same paragraph, it states the BCD package has "248 bits of precision". These are not the same thing when base-10 or BCD arithmetic is considered. This kind of ambiguity occurs on page 1-7. It is finally made clear in on page 5-1, stating that there are 248 digits of precision rather than 248 bits of precision.
Each PDK is sold as a separate product and comes with its own set of documentation. The documentation for the PDK #1 extensions consists of a 100-page spiral-bound book. Unlike the CodeRunneR manual, the PDK #1 book does not have an index. The PDK #3 extensions are still in beta testing so its documentation will not be considered. If you were using all of the PDK extensions with CodeRunner, you might have some difficulty locating the reference for a particular function. (There is not a master index across all of the books.)
MSI provides support through two different channels. First, they offer a customer support hot-line and fax, which is available during business hours. This support is available for an unlimited time after registering the software. Second, the company offers support through its own Bulletin Board System (BBS).
MSI has one of the best BBS support lines I have seen yet. The BBS runs 22 hours/day with eleven phone lines; eight of these support up to 2,400 baud and three support 9,600 baud and 14,400 baud. Registered users of MSI products have unlimited download privileges and are allowed up to three hours per day on the BBS. Most importantly, all new releases and beta versions of MSI software are posted immediately on the BBS. During the course of my review, I took advantage of this and upgraded from CodeRunner v1.05 to v1.06. Users without a modem can upgrade to the latest version of CodeRunneR by having a diskette mailed to them for a $10 service charge. In addition to the very latest versions of MSI products, the BBS also has approximately 1,000 megabytes of public domain software online via CD-ROM. The response time to the questions I posted on the BBS was excellent. The questions I called in on a Saturday morning were answered before noon on the same day.
Case Study: Porting Gnomon
In order to measure the effectiveness of CodeRunneR as a development tool, I decided to port a node monitoring utility I had developed to use with PCBoard bulletin boards. This program, named Gnomon, was released into the public domain in December 1989. Gnomon displays the activity of each BBS node on a network that has several different workstations (nodes) all running PCBoard. Gnomon reads the BBS status log files every few seconds and displays the latest information in a window. Gnomon's usefulness was hampered because I had to tie up an entire workstation with it or else drop what I was working on to bring it up. Consequently, Gnomon was an excellent candidate to be converted from a standalone program to a TSR program.For lack of a better method, I began the porting process by first building the program template provided in the CodeRunneR examples directory. Next, I broke up my source code to provide a hot-key pop-up entry point besides the initialization entry point in main(). Then I replaced the initialization and pop-up functions in the template program with those from my own application. The remainder of my effort was spent reconciling the CodeRunneR library with the MSC 5.1 runtime library.
The first incompatibility I came across was the handling of the main() function in the startup module. Unlike the standard runtime startup module, the CodeRunneR startup module does not pass argc and argv to the main() function. Instead, the program arguments are left in an externally defined null-terminated string called cmd_line. If your program relies on argc and argv, then you will have to parse the command line and initialize them yourself.
The next compatibility hurdle was dealing with the file I/O functions. Coderunner provides some substitute functions which can be enabled by including the SIO.H file. However, these are only stub functions for the DOS INT 21h, file access calls. This means that only block I/O file functions (open (), read(), etc.) are supported and stream I/O functions are not. Luckily, my application used only one significant stream I/O function, fgets (), which I then had to rewrite for myself. The CodeRunneR I/O functions do not match the standard naming conventions. Instead, the block I/O functions are named like the stream I/O functions (fopen(), fread(), etc.) are supposed to be.
Besides the stream I/O functions, there were a few block I/O functions missing which my application required (see Figure 1) . First, the lseek() function was not included in SIO.H even though I discovered later it could have been mapped to the CodeRunneR fpos() function. Next, I found I could also use fpos() to replace the filelength() function. The only block I/O function I had to provide for myself was sopen(). I rewrote the sopen() call, which allows files to be opened with sharing permissions, as a stub that calls INT 21h, function 3Dh.
I found the remainder of the runtime functions that were not directly replaced by SIO.H to be readily replaceable by other CodeRunneR functions. For example, memset() was replaced by filchr() and int86() was replaced by intn(). In many cases, the parameters were identical to those in the MSC runtime library. If the CodeRunneR function names were more similar to those of the runtime library, this step could have been reduced.
After completing the porting process, I found the end-product to be a reliable and well-behaved TSR. I made note of the memory requirements at each stage of the port to determine the effect of the CodeRunneR library (see Figure 3) . The CodeRunneR library was more compact than the MSC 5.1 library and reduced the memory requirement from 19K to 15K. Next, I isolated the disposable initialization code, which consisted largely of command-line parameter parsing, into a separate module. Isolating the initialization code realized an additional 1K savings to the resident size of the program. After optimization, my newly-converted TSR used 5K less RAM than in its original non-resident incarnation. Finally, I used PDK#1 to load the TSR in EMS memory and reduced the total DOS memory requirement to 1K.
I tested my CodeRunneR TSR application in conjunction with the Quarterdeck Extended Memory Manager (QEMM) v5.0 and DESQview 2.26. The test was designed to see under what circumstances the TSR could successfully determine if it was already resident when invoked a second time from the DOS command line. By detecting a second load attempt, the TSR avoids memory that would be wasted by another copy of itself as well as possible interrupt-handler conflicts. For completeness, the TSR must check both low and high areas of DOS memory.
The CodeRunneR function second_load() does this check and also returns a pointer to the PSP of the original copy of the TSR. The second_load() function operates by searching the DOS internal memory allocation lists and ferreting out memory blocks whose PSP points to a CodeRunneR TSR. Each CodeRunneR TSR contains an eight-byte header with a unique four-character ID and version number assigned by the application developer.
The ability to load TSR programs into "high" DOS memory is one of the primary features of QEMM and other 386 control programs such as 386-to-the-MAX by Qualitas, Inc. High DOS memory is defined as the unused portion of the 384K RAM above the 640K barrier. High DOS memory must be divided between BIOS ROMs, display adapter memory, EMS page frames, and other memory-mapped I/O areas. Any gaps remaining after these required areas have been filled may be occupied by DOS drivers and TSRs. Loading TSRs into high memory is performed by the LOADHI.COM facility of QEMM and the corresponding 386LOAD.COM program of 386MAX.
The results of my test proved CodeRunneR able to detect the second load in all but one of the cases tested (see Figure 4) . I was only able to fool CodeRunneR by first installing the TSR in high DOS memory (via LOADHI.COM) and then installing the TSR in low DOS memory. With v1.05 of the library, a CodeRunneR TSR only searches for PSPs in DOS memory earlier than its current location. This allowed a high load of the TSR to pass undetected from the second load. This problem has been resolved in a subsequent release.
Since the GNOMON program also used the DESQview Applications Programming Interface (API) for C, this provided another compatibility test for CodeRunneR. CodeRunneR did not interfere with the DESQview API calls. I had no difficulty loading the TSR before DESQview and invoking it with a hot-key later in a DOS window. Similarly, I could also load it in a DESQview window after DESQview had already been loaded.
Conclusion
CodeRunneR is an effective tool for creating new TSRs and porting existing applications to a TSR environment. However, CodeRunneR could be even more effective with two additional improvements. First, crucial functions available only in the PDKs should be moved to the base CodeRunneR product. Features such as EMS handling and graphics mode support are so basic that they should not require the user to buy addon packages. Second, the replacement runtime library should be enhanced to cover the full range of functions expected in the C runtime library. At a minimum, the I/O functions should be expanded to encompass the stream mode functions as well. CodeRunneR functions that nearly duplicate C runtime library functions in name or in usage should be made fully compatible.The CodeRunneR product excels in its TSR capabilities, coexistance with other DOS applications and technical support. If you are an experienced MS-DOS developer, then you should consider CodeRunneR for your TSR needs.
References
Duncan, Ray. Advanced MS-DOS Programming, 2nd Edition. Redmond, Washington: Microsoft Press. 1988. (A comprehensive catalog of DOS and BIOS interrupts. Includes many low-level details of the MS-DOS environment. A reference such as this is neccessary for CodeRunneR developement).Davidson, Mark. "TSR Libraries: Pop-up Programming". Computer Language, May 1990. pp. 127-136. (A round-up of four TSR building libraries, including CodeRunneR).
Volkman, Victor R. "Multitasking with the DESQview API Library". The C Users Journal, July 1990, pp. 99-109. (Another application developed using the DESQview API library).
Credits
PCBoard is a registered trademark of the Clark Development Corporation, Salt Lake City, UT.QEMM and DESQview are registered trademarks of Quarderdeck Office Systems, Inc.
386-to-the-MAX is a registered trademark of Qualitas, Inc.
CodeRunneR is a registered trademark of Microsystems Software, Inc.
CodeRunneR
Micro Systems Software, Inc.
600 Worcester Rd.
Framingham, MA 01701
(508) 626-8511